public Dictionary<string, EngineConfigEntry> GetEngineConfigOptions(SpringPaths paths, string engine) { Trace.TraceInformation("Extracting configuration from Spring located in {0}", paths.GetEngineFolderByVersion(engine)); var sb = new StringBuilder(); var p = new Process(); p.StartInfo.CreateNoWindow = true; p.StartInfo.UseShellExecute = false; p.StartInfo.Arguments += string.Format("--list-config-vars"); p.StartInfo.EnvironmentVariables["SPRING_DATADIR"] = paths.WritableDirectory; p.StartInfo.EnvironmentVariables.Remove("SPRING_ISOLATED"); p.StartInfo.FileName = paths.GetSpringExecutablePath(engine); p.StartInfo.WorkingDirectory = Path.GetDirectoryName(paths.GetSpringExecutablePath(engine)); p.StartInfo.RedirectStandardOutput = true; p.OutputDataReceived += (sender, args) => sb.AppendLine(args.Data); p.Start(); p.BeginOutputReadLine(); p.WaitForExit(3000); sb.AppendLine(); //append terminator var text = sb.ToString(); int whereIsTable = text.IndexOf('{'); text = text.Substring(whereIsTable); // skip empty line or other info (if exist). Compatibility with Spring 94+ var data = JsonConvert.DeserializeObject<Dictionary<string, EngineConfigEntry>>(text); return data; }
public string Start(SpringPaths paths, TestCase test, Benchmark benchmark) { LogLines = new StringBuilder(); paths.SetEnginePath(paths.GetEngineFolderByVersion(test.Engine)); var optirun = Environment.GetEnvironmentVariable("OPTIRUN"); process = new Process(); process.StartInfo.CreateNoWindow = true; List<string> arg = new List<string>(); if (string.IsNullOrEmpty(optirun)) { process.StartInfo.FileName = paths.Executable; } else { Trace.TraceInformation("Using optirun {0} to start the game (OPTIRUN env var defined)", optirun); process.StartInfo.FileName = optirun; arg.Add(string.Format("\"{0}\"", (paths.Executable))); } process.StartInfo.WorkingDirectory = Path.GetDirectoryName(paths.Executable); arg.Add(string.Format("--config \"{0}\"", Path.Combine(test.Config.ConfigPath, "springsettings.cfg"))); if (test.BenchmarkArg > 0) arg.Add("--benchmark " + test.BenchmarkArg); var dataDirList = new List<string>() { test.Config.ConfigPath, Directory.GetParent(benchmark.BenchmarkPath).Parent.FullName, paths.WritableDirectory, }; dataDirList.AddRange(paths.DataDirectories); dataDirList.Add(Path.GetDirectoryName(paths.Executable)); if (Environment.OSVersion.Platform == PlatformID.Unix) dataDirList = dataDirList.Distinct().Select(x => x.Replace(" ", "\\ ")).ToList(); else dataDirList = dataDirList.Distinct().ToList(); var datadirs = string.Join(Environment.OSVersion.Platform == PlatformID.Unix ? ":" : ";", dataDirList); process.StartInfo.EnvironmentVariables["SPRING_DATADIR"] = datadirs; process.StartInfo.EnvironmentVariables["SPRING_ISOLATED"] = test.Config.ConfigPath; process.StartInfo.EnvironmentVariables["SPRING_WRITEDIR"] = test.Config.ConfigPath; var scriptPath = Path.GetTempFileName(); File.WriteAllText(scriptPath, test.StartScript.GetScriptForTestCase(test, benchmark)); arg.Add(string.Format("\"{0}\"", scriptPath)); process.StartInfo.Arguments = string.Join(" ", arg); process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.ErrorDataReceived += (sender, args) => { LogLines.AppendLine(args.Data); LineAdded(args.Data); }; process.OutputDataReceived += (sender, args) => { LogLines.AppendLine(args.Data); LineAdded(args.Data); }; process.EnableRaisingEvents = true; try { process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); process.WaitForExit(); } catch (Exception ex) { Trace.TraceError("Error waiting for process: {0}", ex); } var lines = LogLines.ToString(); File.Delete(scriptPath); return lines; }
//[STAThread] private static void Main(params string[] args) { var startupPath = Path.GetDirectoryName(Path.GetFullPath(Application.ExecutablePath)); var springPaths = new SpringPaths(null, startupPath); Spring runningSpring = null; springPaths.MakeFolders(); TcpTransport connection = null; // speed up spring start springPaths.SpringVersionChanged += (sender, eventArgs) => { Utils.StartAsync( () => { UnitSync unitSync = null; try { unitSync = new UnitSync(springPaths); // 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.GetAndSwitchEngine(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.GetEngineFolderByVersion(engine), springPaths.WritableDirectory); downloader.GetAndSwitchEngine(engine, path); }); 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.GetEngineFolderByVersion(engineVer), springPaths.WritableDirectory); runningSpring = new Spring(path); runningSpring.SpringExited += (obj, evt) => { CefWrapper.ExecuteJavascript("on_spring_exit(" + (evt.Data ? "true" : "false") + ");"); runningSpring = null; }; try { runningSpring.StartSpring(script); 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(); }