public static void Update(OuiLoggedProgress progress, Entry version = null) { if (version == null) { version = Newest; } if (version == null) { // Exit immediately. progress.Init <OuiModOptions>(Dialog.Clean("updater_title"), new Task(() => { }), 1).Progress = 1; progress.LogLine("No update - cancelling."); return; } progress.Init <OuiHelper_Shutdown>(Dialog.Clean("updater_title"), new Task(() => _UpdateStart(progress, version)), 0); }
public static void Update(OuiLoggedProgress progress, Entry version = null) { if (!Flags.SupportUpdatingEverest) { progress.Init <OuiModOptions>(Dialog.Clean("updater_title"), new Task(() => { }), 1).Progress = 1; progress.LogLine(Dialog.Clean("EVERESTUPDATER_NOTSUPPORTED")); return; } if (version == null) { version = Newest; } if (version == null) { // Exit immediately. progress.Init <OuiModOptions>(Dialog.Clean("updater_title"), new Task(() => { }), 1).Progress = 1; progress.LogLine(Dialog.Clean("EVERESTUPDATER_NOUPDATE")); return; } progress.Init <OuiHelper_Shutdown>(Dialog.Clean("updater_title"), new Task(() => _UpdateStart(progress, version)), 0); }
private static void _UpdateStart(OuiLoggedProgress progress, Entry version) { // Last line printed on error. const string errorHint = "\nPlease create a new issue on GitHub @ https://github.com/EverestAPI/Everest\nor join the #game_modding channel on Discord (invite in the repo).\nMake sure to upload your log.txt"; // Check if we're on an OS which supports manipulating Celeste.exe while it's used. bool canModWhileAlive = Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX; string zipPath = Path.Combine(PathGame, "everest-update.zip"); string extractedPath = canModWhileAlive ? PathGame : Path.Combine(PathGame, "everest-update"); progress.LogLine($"Updating to {version.Name} (branch: {version.Branch}) @ {version.URL}"); progress.LogLine($"Downloading"); DateTime timeStart = DateTime.Now; try { if (File.Exists(zipPath)) { File.Delete(zipPath); } // Manual buffered copy from web input to file output. // Allows us to measure speed and progress. using (WebClient wc = new WebClient()) using (Stream input = wc.OpenRead(version.URL)) using (FileStream output = File.OpenWrite(zipPath)) { long length; if (input.CanSeek) { length = input.Length; } else { length = _ContentLength(version.URL); } progress.Progress = 0; progress.ProgressMax = (int)length; byte[] buffer = new byte[4096]; DateTime timeLastSpeed = timeStart; int read; int readForSpeed = 0; int pos = 0; int speed = 0; TimeSpan td; while (pos < length) { read = input.Read(buffer, 0, (int)Math.Min(buffer.Length, length - pos)); output.Write(buffer, 0, read); pos += read; readForSpeed += read; td = DateTime.Now - timeLastSpeed; if (td.TotalMilliseconds > 100) { speed = (int)((readForSpeed / 1024D) / td.TotalSeconds); readForSpeed = 0; timeLastSpeed = DateTime.Now; } progress.Lines[progress.Lines.Count - 1] = $"Downloading: {((int) Math.Floor(100D * (pos / (double) length)))}% @ {speed} KiB/s"; progress.Progress = pos; } } } catch (Exception e) { progress.LogLine("Download failed!"); progress.LogLine(e.ToString()); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine("Download finished."); progress.LogLine("Extracting update .zip"); try { if (extractedPath != PathGame && Directory.Exists(extractedPath)) { Directory.Delete(extractedPath, true); } // Don't use zip.ExtractAll because we want to keep track of the progress. using (ZipFile zip = new ZipFile(zipPath)) { progress.LogLine($"{zip.Entries.Count} entries"); progress.Progress = 0; progress.ProgressMax = zip.Entries.Count; foreach (ZipEntry entry in zip.Entries) { if (entry.FileName.Replace('\\', '/').EndsWith("/")) { progress.Progress++; continue; } string fullPath = Path.Combine(extractedPath, entry.FileName); string fullDir = Path.GetDirectoryName(fullPath); if (!Directory.Exists(fullDir)) { Directory.CreateDirectory(fullDir); } if (File.Exists(fullPath)) { File.Delete(fullPath); } progress.LogLine($"{entry.FileName} -> {fullPath}"); entry.Extract(extractedPath); // Confusingly enough, this takes the base directory. progress.Progress++; } } } catch (Exception e) { progress.LogLine("Extraction failed!"); progress.LogLine(e.ToString()); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine("Extraction finished."); // Load MiniInstaller and run it in the current app domain on systems supporting this. Assembly installerAssembly = null; Type installerType = null; if (canModWhileAlive) { progress.LogLine("Starting MiniInstaller"); progress.Progress = 0; progress.ProgressMax = 0; Directory.SetCurrentDirectory(PathGame); try { installerAssembly = Assembly.LoadFrom(Path.Combine(extractedPath, "MiniInstaller.exe")); installerType = installerAssembly.GetType("MiniInstaller.Program"); // Set up any fields which we can set up by Everest. FieldInfo f_AsmMonoMod = installerType.GetField("AsmMonoMod"); if (f_AsmMonoMod != null) { f_AsmMonoMod.SetValue(null, typeof(MonoModder).Assembly); } FieldInfo f_LogLine = installerType.GetField("LogLine"); if (f_LogLine != null) { f_LogLine.SetValue(null, new Action <string>(_ => progress.LogLine(_)).CastDelegate(f_LogLine.FieldType)); } // Let's just run the mod installer... from our mod... while we're running the mod... object exitObject = installerAssembly.EntryPoint.Invoke(null, new object[] { new string[] { } }); if (exitObject != null && exitObject is int && ((int)exitObject) != 0) { throw new Exception($"Return code != 0, but {exitObject}"); } } catch (Exception e) { progress.LogLine("Installer failed!"); progress.LogLine(e.ToString()); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } } progress.Progress = 1; progress.ProgressMax = 1; progress.LogLine("Restarting"); for (int i = 5; i > 0; --i) { progress.Lines[progress.Lines.Count - 1] = $"Restarting in {i}"; Thread.Sleep(1000); } progress.Lines[progress.Lines.Count - 1] = $"Restarting"; // Start MiniInstaller in a separate process on systems that don't support modding the game while it'S alive. if (!canModWhileAlive) { try { // We're on Windows or another OS which doesn't support manipulating Celeste.exe while it's used. // Run MiniInstaller "out of body." Process installer = new Process(); if (Type.GetType("Mono.Runtime") != null) { installer.StartInfo.FileName = "mono"; installer.StartInfo.Arguments = "\"" + Path.Combine(extractedPath, "MiniInstaller.exe") + "\""; } else { installer.StartInfo.FileName = Path.Combine(extractedPath, "MiniInstaller.exe"); } installer.StartInfo.WorkingDirectory = extractedPath; installer.Start(); } catch (Exception e) { progress.LogLine("Starting installer failed!"); progress.LogLine(e.ToString()); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; } } else { // On Linux / macOS, Events.Celeste.OnShutdown += () => { // if the installer ships with an exposed StartGame method, run it. MethodInfo m_StartGame = installerType.GetMethod("StartGame"); if (m_StartGame != null) { m_StartGame.Invoke(null, new object[0]); } else { // Otherwise run our own restart code on shutdown. Process game = new Process(); // If the game was installed via Steam, it should restart in a Steam context on its own. if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX) { // The Linux and macOS versions come with a wrapping bash script. game.StartInfo.FileName = "bash"; game.StartInfo.Arguments = "\"" + Path.Combine(PathGame, "Celeste") + "\""; } else { game.StartInfo.FileName = Path.Combine(PathGame, "Celeste.exe"); } game.StartInfo.WorkingDirectory = PathGame; game.Start(); } }; } }
private static void _UpdateStart(OuiLoggedProgress progress, Entry version) { // Last line printed on error. const string errorHint = "\nPlease create a new issue on GitHub @ https://github.com/EverestAPI/Everest\nor join the #game_modding channel on Discord (invite in the repo).\nMake sure to upload your log.txt"; string zipPath = Path.Combine(PathGame, "everest-update.zip"); string extractedPath = Path.Combine(PathGame, "everest-update"); progress.LogLine($"Updating to {version.Name} (branch: {version.Branch}) @ {version.URL}"); progress.LogLine($"Downloading"); try { DownloadFileWithProgress(version.URL, zipPath, (position, length, speed) => { if (length > 0) { progress.Lines[progress.Lines.Count - 1] = $"Downloading: {((int)Math.Floor(100D * (position / (double)length)))}% @ {speed} KiB/s"; progress.Progress = position; } else { progress.Lines[progress.Lines.Count - 1] = $"Downloading: {((int)Math.Floor(position / 1000D))}KiB @ {speed} KiB/s"; } progress.ProgressMax = (int)length; }); } catch (Exception e) { progress.LogLine("Download failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine("Download finished."); progress.LogLine("Extracting update .zip"); try { if (extractedPath != PathGame && Directory.Exists(extractedPath)) { Directory.Delete(extractedPath, true); } // Don't use zip.ExtractAll because we want to keep track of the progress. using (ZipFile zip = new ZipFile(zipPath)) { progress.LogLine($"{zip.Entries.Count} entries"); progress.Progress = 0; progress.ProgressMax = zip.Entries.Count; foreach (ZipEntry entry in zip.Entries) { if (entry.FileName.Replace('\\', '/').EndsWith("/")) { progress.Progress++; continue; } string entryName = entry.FileName; if (entryName.StartsWith("main/")) { entryName = entryName.Substring(5); } string fullPath = Path.Combine(extractedPath, entryName); string fullDir = Path.GetDirectoryName(fullPath); if (!Directory.Exists(fullDir)) { Directory.CreateDirectory(fullDir); } if (File.Exists(fullPath)) { File.Delete(fullPath); } progress.LogLine($"{entry.FileName} -> {fullPath}"); using (Stream stream = File.OpenWrite(fullPath)) entry.Extract(stream); progress.Progress++; } } } catch (Exception e) { progress.LogLine("Extraction failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine("Extraction finished."); progress.Progress = 1; progress.ProgressMax = 1; progress.LogLine("Restarting"); for (int i = 3; i > 0; --i) { progress.Lines[progress.Lines.Count - 1] = $"Restarting in {i}"; Thread.Sleep(1000); } progress.Lines[progress.Lines.Count - 1] = $"Restarting"; // Start MiniInstaller in a separate process. try { Process installer = new Process(); string installerPath = Path.Combine(extractedPath, "MiniInstaller.exe"); installer.StartInfo.FileName = installerPath; if (Type.GetType("Mono.Runtime") != null) { installer.StartInfo.FileName = "mono"; installer.StartInfo.Arguments = $"\"{installerPath}\""; if (File.Exists("/bin/sh")) { installer.StartInfo.FileName = "/bin/sh"; installer.StartInfo.Arguments = $"-c \"cd '{extractedPath}'; mono MiniInstaller.exe\""; } } installer.StartInfo.WorkingDirectory = extractedPath; installer.Start(); } catch (Exception e) { progress.LogLine("Starting installer failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; } }
private static void _UpdateStart(OuiLoggedProgress progress, Entry version) { // Last line printed on error. string errorHint = $"\n{Dialog.Clean("EVERESTUPDATER_ERRORHINT1")}\n{Dialog.Clean("EVERESTUPDATER_ERRORHINT2")}\n{Dialog.Clean("EVERESTUPDATER_ERRORHINT3")}"; string zipPath = Path.Combine(PathGame, "everest-update.zip"); string extractedPath = Path.Combine(PathGame, "everest-update"); progress.LogLine(string.Format(Dialog.Get("EVERESTUPDATER_UPDATING"), version.Name, version.Branch, version.URL)); progress.LogLine(Dialog.Clean("EVERESTUPDATER_DOWNLOADING")); try { DownloadFileWithProgress(version.URL, zipPath, (position, length, speed) => { if (length > 0) { progress.Lines[progress.Lines.Count - 1] = $"{Dialog.Clean("EVERESTUPDATER_DOWNLOADING_PROGRESS")} {((int) Math.Floor(100D * (position / (double) length)))}% @ {speed} KiB/s"; progress.Progress = position; } else { progress.Lines[progress.Lines.Count - 1] = $"{Dialog.Clean("EVERESTUPDATER_DOWNLOADING_PROGRESS")} {((int) Math.Floor(position / 1000D))}KiB @ {speed} KiB/s"; } progress.ProgressMax = (int)length; return(true); // continue downloading }); } catch (Exception e) { progress.LogLine(Dialog.Clean("EVERESTUPDATER_DOWNLOADFAILED")); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine(Dialog.Clean("EVERESTUPDATER_DOWNLOADFINISHED")); progress.LogLine(Dialog.Clean("EVERESTUPDATER_EXTRACTING")); try { if (extractedPath != PathGame && Directory.Exists(extractedPath)) { Directory.Delete(extractedPath, true); } // Don't use zip.ExtractAll because we want to keep track of the progress. using (ZipFile zip = new ZipFile(zipPath)) { progress.LogLine($"{zip.Entries.Count} {Dialog.Clean("EVERESTUPDATER_ZIPENTRIES")}"); progress.Progress = 0; progress.ProgressMax = zip.Entries.Count; foreach (ZipEntry entry in zip.Entries) { if (entry.FileName.Replace('\\', '/').EndsWith("/")) { progress.Progress++; continue; } string entryName = entry.FileName; if (entryName.StartsWith("main/")) { entryName = entryName.Substring(5); } string fullPath = Path.Combine(extractedPath, entryName); string fullDir = Path.GetDirectoryName(fullPath); if (!Directory.Exists(fullDir)) { Directory.CreateDirectory(fullDir); } if (File.Exists(fullPath)) { File.Delete(fullPath); } progress.LogLine($"{entry.FileName} -> {fullPath}"); using (Stream stream = File.OpenWrite(fullPath)) entry.Extract(stream); progress.Progress++; } } } catch (Exception e) { progress.LogLine(Dialog.Clean("EVERESTUPDATER_EXTRACTIONFAILED")); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine(Dialog.Clean("EVERESTUPDATER_EXTRACTIONFINISHED")); progress.Progress = 1; progress.ProgressMax = 1; string action = Dialog.Clean("EVERESTUPDATER_RESTARTING"); progress.LogLine(action); for (int i = 3; i > 0; --i) { progress.Lines[progress.Lines.Count - 1] = string.Format(Dialog.Get("EVERESTUPDATER_RESTARTINGIN"), i); Thread.Sleep(1000); } progress.Lines[progress.Lines.Count - 1] = action; // Start MiniInstaller in a separate process. try { Process installer = new Process(); string installerPath = Path.Combine(extractedPath, "MiniInstaller.exe"); installer.StartInfo.FileName = installerPath; if (Type.GetType("Mono.Runtime") != null) { installer.StartInfo.FileName = "mono"; installer.StartInfo.Arguments = $"\"{installerPath}\""; if (File.Exists("/bin/sh")) { string pid = Process.GetCurrentProcess().Id.ToString(); installer.StartInfo.FileName = "/bin/sh"; string pathToMono = "mono"; if (File.Exists("/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono")) { pathToMono = "/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono"; } installer.StartInfo.Arguments = $"-c \"kill -0 {pid}; while [ $? = \\\"0\\\" ]; do sleep 1; kill -0 {pid}; done; unset MONO_PATH LD_LIBRARY_PATH LC_ALL MONO_CONFIG; {pathToMono} MiniInstaller.exe\""; } } installer.StartInfo.WorkingDirectory = extractedPath; if (Environment.OSVersion.Platform == PlatformID.Unix) { installer.StartInfo.UseShellExecute = false; installer.Start(); } else { installer.Start(); } } catch (Exception e) { progress.LogLine(Dialog.Clean("EVERESTUPDATER_STARTINGFAILED")); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; } }
private static void _UpdateStart(OuiLoggedProgress progress, Entry version) { // Last line printed on error. const string errorHint = "\nPlease create a new issue on GitHub @ https://github.com/EverestAPI/Everest\nor join the #game_modding channel on Discord (invite in the repo).\nMake sure to upload your log.txt"; // Check if we're on an OS which supports manipulating Celeste.exe while it's used. bool canModWhileAlive = System.Environment.OSVersion.Platform == PlatformID.Unix; if (canModWhileAlive) { // Check if we can even read-write the file. Exception eLast = null; string path = typeof(Celeste).Assembly.Location; for (int i = 2048; i > -1; --i) { try { using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite)) break; } catch (Exception e) { eLast = e; } } if (eLast != null) { progress.LogLine("Note: You're on a platform that should support\nread-writing Celeste while running, but it doesn't.\nCheck your log.txt to find out why.\n"); Logger.Log(LogLevel.Warn, "updater", $"Failed read-writing {path} on platform that should support it: " + eLast.ToString()); canModWhileAlive = false; } } string zipPath = Path.Combine(PathGame, "everest-update.zip"); string extractedPath = canModWhileAlive ? PathGame : Path.Combine(PathGame, "everest-update"); progress.LogLine($"Updating to {version.Name} (branch: {version.Branch}) @ {version.URL}"); progress.LogLine($"Downloading"); DateTime timeStart = DateTime.Now; try { if (File.Exists(zipPath)) { File.Delete(zipPath); } // Manual buffered copy from web input to file output. // Allows us to measure speed and progress. using (WebClient wc = new WebClient()) using (Stream input = wc.OpenRead(version.URL)) using (FileStream output = File.OpenWrite(zipPath)) { long length; if (input.CanSeek) { length = input.Length; } else { length = _ContentLength(version.URL); } progress.Progress = 0; progress.ProgressMax = (int)length; byte[] buffer = new byte[4096]; DateTime timeLastSpeed = timeStart; int read; int readForSpeed = 0; int pos = 0; int speed = 0; TimeSpan td; while (pos < length) { read = input.Read(buffer, 0, (int)Math.Min(buffer.Length, length - pos)); output.Write(buffer, 0, read); pos += read; readForSpeed += read; td = DateTime.Now - timeLastSpeed; if (td.TotalMilliseconds > 100) { speed = (int)((readForSpeed / 1024D) / td.TotalSeconds); readForSpeed = 0; timeLastSpeed = DateTime.Now; } progress.Lines[progress.Lines.Count - 1] = $"Downloading: {((int) Math.Floor(100D * (pos / (double) length)))}% @ {speed} KiB/s"; progress.Progress = pos; } } } catch (Exception e) { progress.LogLine("Download failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine("Download finished."); progress.LogLine("Extracting update .zip"); try { if (extractedPath != PathGame && Directory.Exists(extractedPath)) { Directory.Delete(extractedPath, true); } // Don't use zip.ExtractAll because we want to keep track of the progress. using (ZipFile zip = new ZipFile(zipPath)) { progress.LogLine($"{zip.Entries.Count} entries"); progress.Progress = 0; progress.ProgressMax = zip.Entries.Count; foreach (ZipEntry entry in zip.Entries) { if (entry.FileName.Replace('\\', '/').EndsWith("/")) { progress.Progress++; continue; } string fullPath = Path.Combine(extractedPath, entry.FileName); string fullDir = Path.GetDirectoryName(fullPath); if (!Directory.Exists(fullDir)) { Directory.CreateDirectory(fullDir); } if (File.Exists(fullPath)) { File.Delete(fullPath); } progress.LogLine($"{entry.FileName} -> {fullPath}"); entry.Extract(extractedPath); // Confusingly enough, this takes the base directory. progress.Progress++; } } } catch (Exception e) { progress.LogLine("Extraction failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine("Extraction finished."); // Load MiniInstaller and run it in a new app domain on systems supporting this. if (canModWhileAlive) { progress.LogLine("Starting MiniInstaller"); progress.Progress = 0; progress.ProgressMax = 0; Directory.SetCurrentDirectory(PathGame); try { AppDomainSetup nestInfo = new AppDomainSetup(); nestInfo.ApplicationBase = Path.GetDirectoryName(extractedPath); AppDomain nest = AppDomain.CreateDomain( AppDomain.CurrentDomain.FriendlyName + " - MiniInstaller", AppDomain.CurrentDomain.Evidence, nestInfo, AppDomain.CurrentDomain.PermissionSet ); // nest.DoCallBack(Boot); ((MiniInstallerProxy)nest.CreateInstanceFromAndUnwrap( typeof(MiniInstallerProxy).Assembly.Location, typeof(MiniInstallerProxy).FullName )).Boot(new MiniInstallerBridge { Progress = progress, ExtractedPath = extractedPath }); AppDomain.Unload(nest); } catch (Exception e) { progress.LogLine("MiniInstaller failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } } progress.Progress = 1; progress.ProgressMax = 1; progress.LogLine("Restarting"); for (int i = 5; i > 0; --i) { progress.Lines[progress.Lines.Count - 1] = $"Restarting in {i}"; Thread.Sleep(1000); } progress.Lines[progress.Lines.Count - 1] = $"Restarting"; // Start MiniInstaller in a separate process on systems that don't support modding the game while it'S alive. if (!canModWhileAlive) { try { // We're on Windows or another OS which doesn't support manipulating Celeste.exe while it's used. // Run MiniInstaller "out of body." Process installer = new Process(); installer.StartInfo.FileName = Path.Combine(extractedPath, "MiniInstaller.exe"); if (Type.GetType("Mono.Runtime") != null) { installer.StartInfo.Arguments = $"\"{installer.StartInfo.FileName}\""; installer.StartInfo.FileName = "mono"; if (File.Exists("/bin/sh")) { installer.StartInfo.Arguments = $"-c {installer.StartInfo.FileName} {installer.StartInfo.Arguments}"; installer.StartInfo.FileName = "/bin/sh"; } } installer.StartInfo.WorkingDirectory = extractedPath; installer.Start(); } catch (Exception e) { progress.LogLine("Starting installer failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; } } else { // On Linux / macOS, restart the game after it shuts down. Events.Celeste.OnShutdown += () => { Process game = new Process(); // If the game was installed via Steam, it should restart in a Steam context on its own. if (System.Environment.OSVersion.Platform == PlatformID.Unix || System.Environment.OSVersion.Platform == PlatformID.MacOSX) { // The Linux and macOS versions come with a wrapping bash script. game.StartInfo.FileName = "bash"; game.StartInfo.Arguments = "\"" + Path.Combine(PathGame, "Celeste") + "\""; } else { game.StartInfo.FileName = Path.Combine(PathGame, "Celeste.exe"); } game.StartInfo.WorkingDirectory = PathGame; game.Start(); }; } }
private static void _UpdateStart(OuiLoggedProgress progress, Entry version) { // Last line printed on error. const string errorHint = "\nPlease create a new issue on GitHub @ https://github.com/EverestAPI/Everest\nor join the #modding_help channel on Discord (invite in the repo).\nMake sure to upload your log.txt"; string zipPath = Path.Combine(PathGame, "everest-update.zip"); string extractedPath = Path.Combine(PathGame, "everest-update"); progress.LogLine($"Updating to {version.Name} (branch: {version.Branch}) @ {version.URL}"); progress.LogLine($"Downloading"); try { DownloadFileWithProgress(version.URL, zipPath, (position, length, speed) => { if (length > 0) { progress.Lines[progress.Lines.Count - 1] = $"Downloading: {((int) Math.Floor(100D * (position / (double) length)))}% @ {speed} KiB/s"; progress.Progress = position; } else { progress.Lines[progress.Lines.Count - 1] = $"Downloading: {((int) Math.Floor(position / 1000D))}KiB @ {speed} KiB/s"; } progress.ProgressMax = (int)length; return(true); // continue downloading }); } catch (Exception e) { progress.LogLine("Download failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine("Download finished."); progress.LogLine("Extracting update .zip"); try { if (extractedPath != PathGame && Directory.Exists(extractedPath)) { Directory.Delete(extractedPath, true); } // Don't use zip.ExtractAll because we want to keep track of the progress. using (ZipFile zip = new ZipFile(zipPath)) { progress.LogLine($"{zip.Entries.Count} entries"); progress.Progress = 0; progress.ProgressMax = zip.Entries.Count; foreach (ZipEntry entry in zip.Entries) { if (entry.FileName.Replace('\\', '/').EndsWith("/")) { progress.Progress++; continue; } string entryName = entry.FileName; if (entryName.StartsWith("main/")) { entryName = entryName.Substring(5); } string fullPath = Path.Combine(extractedPath, entryName); string fullDir = Path.GetDirectoryName(fullPath); if (!Directory.Exists(fullDir)) { Directory.CreateDirectory(fullDir); } if (File.Exists(fullPath)) { File.Delete(fullPath); } progress.LogLine($"{entry.FileName} -> {fullPath}"); using (Stream stream = File.OpenWrite(fullPath)) entry.Extract(stream); progress.Progress++; } } } catch (Exception e) { progress.LogLine("Extraction failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine("Extraction finished."); progress.Progress = 1; progress.ProgressMax = 1; String action = "Restarting"; if (Environment.OSVersion.Platform == PlatformID.Unix) { action = "Updating"; } progress.LogLine(action); for (int i = 3; i > 0; --i) { progress.Lines[progress.Lines.Count - 1] = $"{action} in {i}"; Thread.Sleep(1000); } progress.Lines[progress.Lines.Count - 1] = action; // Start MiniInstaller in a separate process. try { Process installer = new Process(); string installerPath = Path.Combine(extractedPath, "MiniInstaller.exe"); installer.StartInfo.FileName = installerPath; if (Type.GetType("Mono.Runtime") != null) { installer.StartInfo.FileName = "mono"; installer.StartInfo.Arguments = $"\"{installerPath}\""; if (File.Exists("/bin/sh")) { string pid = Process.GetCurrentProcess().Id.ToString(); installer.StartInfo.FileName = "/bin/sh"; string pathToMono = "mono"; if (File.Exists("/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono")) { pathToMono = "/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono"; } installer.StartInfo.Arguments = $"-c \"kill -0 {pid}; while [ $? = \\\"0\\\" ]; do sleep 1; kill -0 {pid}; done; unset MONO_PATH LD_LIBRARY_PATH LC_ALL MONO_CONFIG; {pathToMono} MiniInstaller.exe\""; } } installer.StartInfo.WorkingDirectory = extractedPath; if (Environment.OSVersion.Platform == PlatformID.Unix) { installer.StartInfo.UseShellExecute = false; installer.Start(); progress.LogLine("Patching the game in-place"); progress.LogLine("Restarting"); } else { installer.Start(); } } catch (Exception e) { progress.LogLine("Starting installer failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; } }
private static void _UpdateStart(OuiLoggedProgress progress, Entry version) { // Last line printed on error. const string errorHint = "\nPlease create a new issue on GitHub @ https://github.com/EverestAPI/Everest\nor join the #game_modding channel on Discord (invite in the repo).\nMake sure to upload your log.txt"; string zipPath = Path.Combine(PathGame, "everest-update.zip"); string extractedPath = Path.Combine(PathGame, "everest-update"); progress.LogLine($"Updating to {version.Name} (branch: {version.Branch}) @ {version.URL}"); progress.LogLine($"Downloading"); DateTime timeStart = DateTime.Now; try { if (File.Exists(zipPath)) { File.Delete(zipPath); } // Manual buffered copy from web input to file output. // Allows us to measure speed and progress. using (WebClient wc = new WebClient()) using (Stream input = wc.OpenRead(version.URL)) using (FileStream output = File.OpenWrite(zipPath)) { long length; if (input.CanSeek) { length = input.Length; } else { length = _ContentLength(version.URL); } progress.Progress = 0; progress.ProgressMax = (int)length; byte[] buffer = new byte[4096]; DateTime timeLastSpeed = timeStart; int read; int readForSpeed = 0; int pos = 0; int speed = 0; TimeSpan td; while (pos < length) { read = input.Read(buffer, 0, (int)Math.Min(buffer.Length, length - pos)); output.Write(buffer, 0, read); pos += read; readForSpeed += read; td = DateTime.Now - timeLastSpeed; if (td.TotalMilliseconds > 100) { speed = (int)((readForSpeed / 1024D) / td.TotalSeconds); readForSpeed = 0; timeLastSpeed = DateTime.Now; } progress.Lines[progress.Lines.Count - 1] = $"Downloading: {((int) Math.Floor(100D * (pos / (double) length)))}% @ {speed} KiB/s"; progress.Progress = pos; } } } catch (Exception e) { progress.LogLine("Download failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine("Download finished."); progress.LogLine("Extracting update .zip"); try { if (extractedPath != PathGame && Directory.Exists(extractedPath)) { Directory.Delete(extractedPath, true); } // Don't use zip.ExtractAll because we want to keep track of the progress. using (ZipFile zip = new ZipFile(zipPath)) { progress.LogLine($"{zip.Entries.Count} entries"); progress.Progress = 0; progress.ProgressMax = zip.Entries.Count; foreach (ZipEntry entry in zip.Entries) { if (entry.FileName.Replace('\\', '/').EndsWith("/")) { progress.Progress++; continue; } string fullPath = Path.Combine(extractedPath, entry.FileName); string fullDir = Path.GetDirectoryName(fullPath); if (!Directory.Exists(fullDir)) { Directory.CreateDirectory(fullDir); } if (File.Exists(fullPath)) { File.Delete(fullPath); } progress.LogLine($"{entry.FileName} -> {fullPath}"); entry.Extract(extractedPath); // Confusingly enough, this takes the base directory. progress.Progress++; } } } catch (Exception e) { progress.LogLine("Extraction failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; return; } progress.LogLine("Extraction finished."); // Load MiniInstaller and run it in a new app domain on systems supporting this. progress.Progress = 1; progress.ProgressMax = 1; progress.LogLine("Restarting"); for (int i = 3; i > 0; --i) { progress.Lines[progress.Lines.Count - 1] = $"Restarting in {i}"; Thread.Sleep(1000); } progress.Lines[progress.Lines.Count - 1] = $"Restarting"; // Start MiniInstaller in a separate process. try { Process installer = new Process(); string installerPath = Path.Combine(extractedPath, "MiniInstaller.exe"); installer.StartInfo.FileName = installerPath; if (Type.GetType("Mono.Runtime") != null) { installer.StartInfo.FileName = "mono"; installer.StartInfo.Arguments = $"\"{installerPath}\""; if (File.Exists("/bin/sh")) { installer.StartInfo.FileName = "/bin/sh"; installer.StartInfo.Arguments = $"-c \"cd '{extractedPath}'; mono MiniInstaller.exe\""; } } installer.StartInfo.WorkingDirectory = extractedPath; installer.Start(); } catch (Exception e) { progress.LogLine("Starting installer failed!"); e.LogDetailed(); progress.LogLine(errorHint); progress.Progress = 0; progress.ProgressMax = 1; } }