// heavily copy-pasted from Everest.Updater! private static void downloadMod(ModUpdateInfo update, TextMenu.Button button, string zipPath) { Logger.Log("UpdateChecker", $"Downloading {update.URL} to {zipPath}"); DateTime timeStart = DateTime.Now; 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(update.URL)) using (FileStream output = File.OpenWrite(zipPath)) { long length; if (input.CanSeek) { length = input.Length; } else { length = _ContentLength(update.URL); } byte[] buffer = new byte[4096]; DateTime timeLastSpeed = timeStart; int read = 1; int readForSpeed = 0; int pos = 0; int speed = 0; int count = 0; TimeSpan td; while (read > 0) { count = length > 0 ? (int)Math.Min(buffer.Length, length - pos) : buffer.Length; read = input.Read(buffer, 0, count); 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; } if (length > 0) { button.Label = $"{update.Name} ({((int)Math.Floor(100D * (pos / (double)length)))}% @ {speed} KiB/s)"; } else { button.Label = $"{update.Name} ({((int)Math.Floor(pos / 1000D))}KiB @ {speed} KiB/s)"; } } } }
private static void installMod(ModUpdateInfo update, EverestModuleMetadata mod, string zipPath) { // let's close the zip, as we will replace it now. foreach (ModContent content in Everest.Content.Mods) { if (content.GetType() == typeof(ZipModContent) && (content as ZipModContent).Mod.Name == mod.Name) { ZipModContent modZip = content as ZipModContent; Logger.Log("UpdateChecker", $"Closing mod .zip: {modZip.Path}"); modZip.Dispose(); } } // delete the old zip, and move the new one. Logger.Log("UpdateChecker", $"Deleting mod .zip: {mod.PathArchive}"); File.Delete(mod.PathArchive); Logger.Log("UpdateChecker", $"Moving {zipPath} to {mod.PathArchive}"); File.Move(zipPath, mod.PathArchive); }
private void downloadModUpdate(ModUpdateInfo update, EverestModuleMetadata mod, TextMenu.Button button) { task = new Task(() => { // we will download the mod to Celeste_Directory/mod-update.zip at first. string zipPath = Path.Combine(Everest.PathGame, "mod-update.zip"); try { // download it... button.Label = $"{update.Name} ({Dialog.Clean("UPDATECHECKER_DOWNLOADING")})"; downloadMod(update, button, zipPath); // verify its checksum string actualHash = BitConverter.ToString(Everest.GetChecksum("mod-update.zip")).Replace("-", "").ToLowerInvariant(); string expectedHash = update.xxHash[0]; Logger.Log("UpdateChecker", $"Verifying checksum: actual hash is {actualHash}, expected hash is {expectedHash}"); if (expectedHash != actualHash) { throw new IOException($"Checksum error: expected {expectedHash}, got {actualHash}"); } // mark restarting as required, as we will do weird stuff like closing zips afterwards. if (!shouldRestart) { shouldRestart = true; subHeader.TextColor = Color.OrangeRed; subHeader.Title = $"{Dialog.Clean("UPDATECHECKER_MENU_HEADER")} ({Dialog.Clean("UPDATECHECKER_WILLRESTART")})"; } // install it button.Label = $"{update.Name} ({Dialog.Clean("UPDATECHECKER_INSTALLING")})"; installMod(update, mod, zipPath); // done! button.Label = $"{update.Name} ({Dialog.Clean("UPDATECHECKER_UPDATED")})"; // select another enabled option: the next one, or the last one if there is no next one. if (menu.Selection + 1 > menu.LastPossibleSelection) { menu.Selection = menu.LastPossibleSelection; } else { menu.Selection++; } } catch (Exception e) { // update failed button.Label = $"{update.Name} ({Dialog.Clean("UPDATECHECKER_FAILED")})"; Logger.Log("UpdateChecker", $"Updating {update.Name} failed"); Logger.LogDetailed(e); button.Disabled = false; // try to delete mod-update.zip if it still exists. if (File.Exists(zipPath)) { try { Logger.Log("UpdateChecker", $"Deleting temp file {zipPath}"); File.Delete(zipPath); } catch (Exception) { Logger.Log("UpdateChecker", $"Removing {zipPath} failed"); } } } // give the menu control back to the player menu.Focused = true; }); task.Start(); }