From b3c79ad9ae005f0dd6d795eff264a8a297090967 Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Mon, 1 Aug 2022 20:32:00 +0200 Subject: [PATCH 01/74] Draw parallax stylegrounds using single Draw call --- Celeste.Mod.mm/Patches/BackdropRenderer.cs | 59 +++++++++++++++++++++ Celeste.Mod.mm/Patches/Monocle/MTexture.cs | 9 +++- Celeste.Mod.mm/Patches/Parallax.cs | 60 ++++++++++++++++++++++ 3 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 Celeste.Mod.mm/Patches/BackdropRenderer.cs create mode 100644 Celeste.Mod.mm/Patches/Parallax.cs diff --git a/Celeste.Mod.mm/Patches/BackdropRenderer.cs b/Celeste.Mod.mm/Patches/BackdropRenderer.cs new file mode 100644 index 000000000..001836755 --- /dev/null +++ b/Celeste.Mod.mm/Patches/BackdropRenderer.cs @@ -0,0 +1,59 @@ +using Celeste; +using Microsoft.Xna.Framework.Graphics; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Monocle; +using MonoMod; +using MonoMod.Cil; +using MonoMod.Utils; +using System; + +namespace Celeste { + class patch_BackdropRenderer : BackdropRenderer { + private bool usingSpritebatch; + + public void StartSpritebatchLooping(BlendState blendState) { + if (!usingSpritebatch) { + Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, blendState, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, null, Matrix); + } + usingSpritebatch = true; + } + + [MonoModIgnore] + [PatchBackdropRendererRender] + public override extern void Render(Scene scene); + } +} + +namespace MonoMod { + /// + /// Patches the method to begin a wrapping spritebatch for parallaxes. + /// + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchBackdropRendererRender))] + class PatchBackdropRendererRenderAttribute : Attribute { } + + static partial class MonoModRules { + + public static void PatchBackdropRendererRender(ILContext context, CustomAttribute attrib) { + MethodReference m_BackDropRenderer_StartSpritebatchLooping = context.Method.DeclaringType.FindMethod("StartSpritebatchLooping"); + TypeReference t_Parallax = context.Module.GetType("Celeste.Parallax"); + + ILCursor cursor = new ILCursor(context); + + /* Change: StartSpritebatch(blendState); + to: backdrop is Parallax ? StartSpritebatchLooping(blendState) : StartSpritebatch(blendState); */ + cursor.GotoNext(instr => instr.MatchCallvirt("Celeste.BackdropRenderer", "StartSpritebatch")); + ILLabel beforeStartSpritebatch = cursor.MarkLabel(); + cursor.MoveBeforeLabels(); + cursor.Emit(OpCodes.Ldloc_2); // load backdrop + cursor.Emit(OpCodes.Isinst, t_Parallax); + cursor.Emit(OpCodes.Brfalse_S, beforeStartSpritebatch); + cursor.Emit(OpCodes.Callvirt, m_BackDropRenderer_StartSpritebatchLooping); + ILLabel nextIf = cursor.DefineLabel(); + cursor.GotoNext(MoveType.After, instr => instr.MatchCallvirt("Celeste.BackdropRenderer", "StartSpritebatch")); + cursor.MarkLabel(nextIf); + cursor.Index--; + cursor.Emit(OpCodes.Br, nextIf); + } + } +} \ No newline at end of file diff --git a/Celeste.Mod.mm/Patches/Monocle/MTexture.cs b/Celeste.Mod.mm/Patches/Monocle/MTexture.cs index 8fb06d1f9..2f31384b2 100644 --- a/Celeste.Mod.mm/Patches/Monocle/MTexture.cs +++ b/Celeste.Mod.mm/Patches/Monocle/MTexture.cs @@ -68,7 +68,7 @@ private void SetUtil() { } /// - /// Override the given MTexutre with the given VirtualTexture and parameters. + /// Override the given MTexture with the given VirtualTexture and parameters. /// public void SetOverride(VirtualTexture texture, Vector2 drawOffset, int frameWidth, int frameHeight) { if (!_HasOrig) { @@ -89,7 +89,7 @@ public void SetOverride(VirtualTexture texture, Vector2 drawOffset, int frameWid } /// - /// Override the given MTexutre with the given mod asset. + /// Override the given MTexture with the given mod asset. /// public void SetOverride(ModAsset asset) { if (!_HasOrig && Texture.GetMetadata() == asset) { @@ -291,6 +291,11 @@ public float ScaleFix { Monocle.Draw.SpriteBatch.Draw(Texture.Texture, position, GetRelativeRect(clip), color, rotation, (origin - DrawOffset) / scaleFix, scale * scaleFix, SpriteEffects.None, 0f); } + public void Draw(Vector2 position, Vector2 origin, Color color, float scale, float rotation, SpriteEffects flip, Rectangle absoluteClip) { + float scaleFix = ScaleFix; + Monocle.Draw.SpriteBatch.Draw(Texture.Texture, position, absoluteClip, color, rotation, (origin - DrawOffset) / scaleFix, scale * scaleFix, flip, 0f); + } + #endregion #region DrawCentered diff --git a/Celeste.Mod.mm/Patches/Parallax.cs b/Celeste.Mod.mm/Patches/Parallax.cs new file mode 100644 index 000000000..e0b0fd4be --- /dev/null +++ b/Celeste.Mod.mm/Patches/Parallax.cs @@ -0,0 +1,60 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Monocle; +using MonoMod; +using MonoMod.Cil; +using MonoMod.Utils; +using System; + +namespace Celeste { + class patch_Parallax : Parallax { + + public patch_Parallax(MTexture texture) : base(texture) { + // no-op, ignored by MonoMod + } + + private void DrawParallax(Vector2 position, Color color, SpriteEffects flip) { + Rectangle rect = new Rectangle(0, 0, LoopX ? Celeste.GameWidth : Texture.Width, LoopY ? Celeste.GameHeight : Texture.Height); + ((patch_MTexture) Texture).Draw(position, Vector2.Zero, color, 1f, 0f, flip, rect); + } + + [MonoModIgnore] + [PatchParallaxRender] + public override extern void Render(Scene scene); + } +} + +namespace MonoMod { + /// + /// Patches the method to replace looped Draw calls with single call. + /// + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchParallaxRender))] + class PatchParallaxRenderAttribute : Attribute { } + + static partial class MonoModRules { + + public static void PatchParallaxRender(ILContext context, CustomAttribute attrib) { + MethodDefinition m_Parallax_DrawParallax = context.Method.DeclaringType.FindMethod("DrawParallax"); + + ILCursor cursor = new ILCursor(context); + + cursor.GotoNext(MoveType.After, instr => instr.MatchLdfld("Celeste.Backdrop", "FlipY"), + instr => instr.MatchBrfalse(out _), + instr => instr.MatchLdcI4(2), + instr => instr.MatchStloc(4), + instr => instr.MatchLdloc(1), + instr => instr.MatchLdfld("Microsoft.Xna.Framework.Vector2", "X")); + cursor.Index -= 2; + cursor.MoveAfterLabels(); + cursor.RemoveRange(cursor.Instrs.Count - cursor.Index - 1); // delete rest of method except ret instruction + cursor.MoveAfterLabels(); + cursor.Emit(OpCodes.Ldarg_0); + cursor.Emit(OpCodes.Ldloc_1); // position + cursor.Emit(OpCodes.Ldloc_3); // color + cursor.Emit(OpCodes.Ldloc_S, (byte) 4); // flip + cursor.Emit(OpCodes.Callvirt, m_Parallax_DrawParallax); + } + } +} \ No newline at end of file From 4b8e7b1e872b53a7d4d3c4418c9fe094e6a1b9ad Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Mon, 1 Aug 2022 21:07:15 +0200 Subject: [PATCH 02/74] Remove unnecessary using --- Celeste.Mod.mm/Patches/BackdropRenderer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Celeste.Mod.mm/Patches/BackdropRenderer.cs b/Celeste.Mod.mm/Patches/BackdropRenderer.cs index 001836755..51b959337 100644 --- a/Celeste.Mod.mm/Patches/BackdropRenderer.cs +++ b/Celeste.Mod.mm/Patches/BackdropRenderer.cs @@ -1,5 +1,4 @@ -using Celeste; -using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Graphics; using Mono.Cecil; using Mono.Cecil.Cil; using Monocle; From 3bf00131febf3e25ef50d922c4998f8f37cdb5cc Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Tue, 16 Aug 2022 02:42:53 +0200 Subject: [PATCH 03/74] Improve atlas support for fast looping stylegrounds Implemented some ideas from Viv. --- Celeste.Mod.mm/Patches/Monocle/MTexture.cs | 35 ++++++++++++++++++---- Celeste.Mod.mm/Patches/Parallax.cs | 2 +- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Celeste.Mod.mm/Patches/Monocle/MTexture.cs b/Celeste.Mod.mm/Patches/Monocle/MTexture.cs index 2f31384b2..6d4c7acd5 100644 --- a/Celeste.Mod.mm/Patches/Monocle/MTexture.cs +++ b/Celeste.Mod.mm/Patches/Monocle/MTexture.cs @@ -7,6 +7,7 @@ using MonoMod; using System; using System.Collections.Generic; +using System.Reflection; namespace Monocle { class patch_MTexture : MTexture { @@ -44,6 +45,8 @@ public Atlas Atlas { private List _ModAssets; + private Texture2D unpacked; + // Patching constructors is ugly. public extern void orig_ctor(patch_MTexture parent, int x, int y, int width, int height); [MonoModConstructor] @@ -210,6 +213,23 @@ public void UndoOverride(ModAsset asset) { return new Rectangle(x, y, w, h); } + private static Texture2D CreateUnpackedTexture(Texture2D src, Rectangle rect) + { + Texture2D tex = new Texture2D(src.GraphicsDevice, rect.Width, rect.Height); + int count = rect.Width * rect.Height; + Color[] data = new Color[count]; + src.GetData(0, rect, data, 0, count); + tex.SetData(data); + return tex; + } + + private Texture2D Unpacked { + get { + unpacked ??= CreateUnpackedTexture(Texture.Texture, ClipRect); + return unpacked; + } + } + #region Drawing Methods #region Draw-related fixes @@ -291,11 +311,6 @@ public float ScaleFix { Monocle.Draw.SpriteBatch.Draw(Texture.Texture, position, GetRelativeRect(clip), color, rotation, (origin - DrawOffset) / scaleFix, scale * scaleFix, SpriteEffects.None, 0f); } - public void Draw(Vector2 position, Vector2 origin, Color color, float scale, float rotation, SpriteEffects flip, Rectangle absoluteClip) { - float scaleFix = ScaleFix; - Monocle.Draw.SpriteBatch.Draw(Texture.Texture, position, absoluteClip, color, rotation, (origin - DrawOffset) / scaleFix, scale * scaleFix, flip, 0f); - } - #endregion #region DrawCentered @@ -807,6 +822,16 @@ public void Draw(Vector2 position, Vector2 origin, Color color, float scale, flo #endregion + #region DrawWithWrappingSupport + + public void DrawWithWrappingSupport(Vector2 position, Vector2 origin, Color color, float scale, float rotation, SpriteEffects flip, Rectangle absoluteClip) { + float scaleFix = ScaleFix; + // TODO does this have to use reflection? + Monocle.Draw.SpriteBatch.Draw(typeof(SpriteBatch).GetField("samplerState", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(Monocle.Draw.SpriteBatch) != SamplerState.PointWrap ? Texture.Texture : Unpacked, position, absoluteClip, color, rotation, (origin - DrawOffset) / scaleFix, scale * scaleFix, flip, 0f); + } + + #endregion + #endregion } diff --git a/Celeste.Mod.mm/Patches/Parallax.cs b/Celeste.Mod.mm/Patches/Parallax.cs index e0b0fd4be..1a0d0d30a 100644 --- a/Celeste.Mod.mm/Patches/Parallax.cs +++ b/Celeste.Mod.mm/Patches/Parallax.cs @@ -17,7 +17,7 @@ public patch_Parallax(MTexture texture) : base(texture) { private void DrawParallax(Vector2 position, Color color, SpriteEffects flip) { Rectangle rect = new Rectangle(0, 0, LoopX ? Celeste.GameWidth : Texture.Width, LoopY ? Celeste.GameHeight : Texture.Height); - ((patch_MTexture) Texture).Draw(position, Vector2.Zero, color, 1f, 0f, flip, rect); + ((patch_MTexture) Texture).DrawWithWrappingSupport(position, Vector2.Zero, color, 1f, 0f, flip, rect); } [MonoModIgnore] From a47dfe8799b89ab169d9c62435afb5ff5c9aa34b Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Wed, 17 Aug 2022 01:42:22 +0200 Subject: [PATCH 04/74] Crop looping stylegrounds correctly --- Celeste.Mod.mm/Patches/Parallax.cs | 81 ++++++++++++++---------------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/Celeste.Mod.mm/Patches/Parallax.cs b/Celeste.Mod.mm/Patches/Parallax.cs index 1a0d0d30a..703469b10 100644 --- a/Celeste.Mod.mm/Patches/Parallax.cs +++ b/Celeste.Mod.mm/Patches/Parallax.cs @@ -1,60 +1,53 @@ -using Microsoft.Xna.Framework; +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using Mono.Cecil; -using Mono.Cecil.Cil; using Monocle; using MonoMod; -using MonoMod.Cil; -using MonoMod.Utils; using System; namespace Celeste { class patch_Parallax : Parallax { + + private float fadeIn; public patch_Parallax(MTexture texture) : base(texture) { // no-op, ignored by MonoMod } - private void DrawParallax(Vector2 position, Color color, SpriteEffects flip) { - Rectangle rect = new Rectangle(0, 0, LoopX ? Celeste.GameWidth : Texture.Width, LoopY ? Celeste.GameHeight : Texture.Height); + [MonoModReplace] + public override void Render(Scene scene) { + Vector2 camera = ((scene as Level).Camera.Position + CameraOffset).Floor(); + Vector2 position = (Position - camera * Scroll).Floor(); + float alpha = fadeIn * Alpha * FadeAlphaMultiplier; + if (FadeX != null) { + alpha *= FadeX.Value(camera.X + 160f); + } + if (FadeY != null) { + alpha *= FadeY.Value(camera.Y + 90f); + } + Color color = Color; + if (alpha < 1f) { + color *= alpha; + } + if (color.A <= 1) { + return; + } + if (LoopX) { + position.X = (position.X % Texture.Width - Texture.Width) % Texture.Width; + } + if (LoopY) { + position.Y = (position.Y % Texture.Height - Texture.Height) % Texture.Height; + } + SpriteEffects flip = SpriteEffects.None; + if (FlipX) { + flip |= SpriteEffects.FlipHorizontally; + } + if (FlipY) { + flip |= SpriteEffects.FlipVertically; + } + Rectangle rect = new Rectangle(0, 0, LoopX ? (int) Math.Ceiling(Celeste.GameWidth - position.X) : Texture.Width, LoopY ? (int) Math.Ceiling(Celeste.GameHeight - position.Y) : Texture.Height); ((patch_MTexture) Texture).DrawWithWrappingSupport(position, Vector2.Zero, color, 1f, 0f, flip, rect); } - - [MonoModIgnore] - [PatchParallaxRender] - public override extern void Render(Scene scene); - } -} - -namespace MonoMod { - /// - /// Patches the method to replace looped Draw calls with single call. - /// - [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchParallaxRender))] - class PatchParallaxRenderAttribute : Attribute { } - - static partial class MonoModRules { - - public static void PatchParallaxRender(ILContext context, CustomAttribute attrib) { - MethodDefinition m_Parallax_DrawParallax = context.Method.DeclaringType.FindMethod("DrawParallax"); - - ILCursor cursor = new ILCursor(context); - - cursor.GotoNext(MoveType.After, instr => instr.MatchLdfld("Celeste.Backdrop", "FlipY"), - instr => instr.MatchBrfalse(out _), - instr => instr.MatchLdcI4(2), - instr => instr.MatchStloc(4), - instr => instr.MatchLdloc(1), - instr => instr.MatchLdfld("Microsoft.Xna.Framework.Vector2", "X")); - cursor.Index -= 2; - cursor.MoveAfterLabels(); - cursor.RemoveRange(cursor.Instrs.Count - cursor.Index - 1); // delete rest of method except ret instruction - cursor.MoveAfterLabels(); - cursor.Emit(OpCodes.Ldarg_0); - cursor.Emit(OpCodes.Ldloc_1); // position - cursor.Emit(OpCodes.Ldloc_3); // color - cursor.Emit(OpCodes.Ldloc_S, (byte) 4); // flip - cursor.Emit(OpCodes.Callvirt, m_Parallax_DrawParallax); - } } } \ No newline at end of file From b6ba3a0e2bbd7cc63a8e01f7d52562668d246b34 Mon Sep 17 00:00:00 2001 From: coloursofnoise Date: Tue, 16 Aug 2022 17:30:45 -0700 Subject: [PATCH 05/74] Add URIHelper for making query string from NameValuePair Could be replaced with multi-line string interpolation in C#11 --- Celeste.Mod.mm/Mod/Helpers/URIHelper.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Celeste.Mod.mm/Mod/Helpers/URIHelper.cs diff --git a/Celeste.Mod.mm/Mod/Helpers/URIHelper.cs b/Celeste.Mod.mm/Mod/Helpers/URIHelper.cs new file mode 100644 index 000000000..47efdafc3 --- /dev/null +++ b/Celeste.Mod.mm/Mod/Helpers/URIHelper.cs @@ -0,0 +1,23 @@ +using System.Collections.Specialized; +using System.Linq; + +namespace Celeste.Mod.Helpers { + public class URIHelper { + + public readonly string Uri; + + public URIHelper(string uri, NameValueCollection queryParams) { + Uri = uri + '?' + ToQueryString(queryParams); + } + + public override string ToString() => Uri; + + public static string ToQueryString(NameValueCollection nvc) { + return string.Join("&", + from key in nvc.AllKeys + from value in nvc.GetValues(key) + select string.Format("{0}={1}", key, value)); + } + + } +} \ No newline at end of file From f48c201d4e6e111e3f35d5152a9aafbc1d63ba24 Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Wed, 17 Aug 2022 02:33:13 +0200 Subject: [PATCH 06/74] Create unpacked texture only when necessary --- Celeste.Mod.mm/Patches/Monocle/MTexture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Celeste.Mod.mm/Patches/Monocle/MTexture.cs b/Celeste.Mod.mm/Patches/Monocle/MTexture.cs index 6d4c7acd5..e13d009cf 100644 --- a/Celeste.Mod.mm/Patches/Monocle/MTexture.cs +++ b/Celeste.Mod.mm/Patches/Monocle/MTexture.cs @@ -225,7 +225,7 @@ private static Texture2D CreateUnpackedTexture(Texture2D src, Rectangle rect) private Texture2D Unpacked { get { - unpacked ??= CreateUnpackedTexture(Texture.Texture, ClipRect); + unpacked ??= Width == Texture.Width && Height == Texture.Height ? Texture.Texture : CreateUnpackedTexture(Texture.Texture, ClipRect); return unpacked; } } From b500093c47e155046e0aef2f874800040bf2e4d0 Mon Sep 17 00:00:00 2001 From: coloursofnoise Date: Tue, 16 Aug 2022 17:33:37 -0700 Subject: [PATCH 07/74] Add TextMenu BatchMode context and SubHeaderExt AlwaysCenter --- Celeste.Mod.mm/Mod/UI/TextMenuExt.cs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs index a1cf6b62c..753c4d8fb 100644 --- a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs +++ b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs @@ -99,6 +99,7 @@ public class SubHeaderExt : patch_TextMenu.patch_SubHeader, IItemExt { public Vector2 Offset { get; set; } public float Alpha { get; set; } = 1f; + public bool AlwaysCenter { get; set; } public float HeightExtra { get; set; } = 48f; @@ -116,11 +117,11 @@ public override void Render(Vector2 position, bool highlighted) { Color strokeColor = Color.Black * (alpha * alpha * alpha); Vector2 textPosition = position + ( - Container.InnerContent == TextMenu.InnerContentMode.TwoColumn ? + Container.InnerContent == TextMenu.InnerContentMode.TwoColumn && !AlwaysCenter ? new Vector2(0f, MathHelper.Max(0f, 32f - 48f + HeightExtra)) : new Vector2(Container.Width * 0.5f, MathHelper.Max(0f, 32f - 48f + HeightExtra)) ); - Vector2 justify = new Vector2(Container.InnerContent == TextMenu.InnerContentMode.TwoColumn ? 0f : 0.5f, 0.5f); + Vector2 justify = new Vector2(Container.InnerContent == TextMenu.InnerContentMode.TwoColumn && !AlwaysCenter ? 0f : 0.5f, 0.5f); DrawIcon( position, @@ -1337,5 +1338,20 @@ private static void DrawIcon(Vector2 position, MTexture icon, Vector2 justify, b #endregion } + + public class BatchModeContext : IDisposable { + + patch_TextMenu menu; + + public BatchModeContext(patch_TextMenu menu) { + menu.BatchMode = true; + this.menu = menu; + } + + public void Dispose() { + menu.BatchMode = false; + } + } + } } From 31ee193974ebcf6c7e734b8b04e23004b7531500 Mon Sep 17 00:00:00 2001 From: coloursofnoise Date: Tue, 16 Aug 2022 17:35:51 -0700 Subject: [PATCH 08/74] Redo Everest updater and version selection to separate branches Each branch is a different source, allowing beta and stable releases published on GitHub, and better support for adding feature branches in future. --- Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs | 146 ++++++++++-------- Celeste.Mod.mm/Mod/UI/OuiVersionList.cs | 140 ++++++++++------- 2 files changed, 165 insertions(+), 121 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs index 571ea46a3..bb7a30073 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs @@ -1,4 +1,5 @@ using Celeste.Mod.Core; +using Celeste.Mod.Helpers; using Celeste.Mod.UI; using Ionic.Zip; using MonoMod.Utils; @@ -6,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Net; @@ -18,32 +20,38 @@ public static partial class Everest { // TODO: General purpose updater for both Everest itself and any runtime mods. internal static class Updater { + public enum UpdatePriority { + High, Low, None + } + public class Entry { public readonly string Name; - public readonly string Branch; + public string Description; public readonly string URL; public readonly int Build; - public Entry(string name, string branch, string url, int version) { + public readonly Source Source; + public Entry(string name, string url, int version, Source source) { Name = name; - Branch = branch ?? ""; URL = url; Build = version; + Source = source; } } public class Source { + public string DisplayName; + public string NameDialog; - public string Index; + public UpdatePriority UpdatePriority = UpdatePriority.Low; - public Func IsCurrent; + public string Index; - public Func> ParseData; + public Func> ParseData; #pragma warning disable CS0649 public Func ParseLine; #pragma warning restore CS0649 - public virtual ReadOnlyCollection Entries { get; protected set; } public string ErrorDialog { get; protected set; } @@ -57,7 +65,7 @@ public Task Request() { return _RequestStart(); } catch (Exception e) { ErrorDialog = "updater_versions_err_download"; - Logger.Log(LogLevel.Warn, "updater", "Uncaught exception while loading Everest version list"); + Logger.Log(LogLevel.Warn, "updater", "Uncaught exception while loading version list"); Logger.LogDetailed(e); return this; } @@ -71,8 +79,11 @@ private Source _RequestStart() { string data; try { - using (WebClient wc = new WebClient()) + Logger.Log(LogLevel.Debug, "updater", "Attempting to download update list from source: " + Index); + using (WebClient wc = new WebClient()) { + wc.Headers.Add("User-Agent", "Everest/" + Everest.VersionString); data = wc.DownloadString(Index); + } } catch (Exception e) { ErrorDialog = "updater_versions_err_download"; Logger.Log(LogLevel.Warn, "updater", "Failed requesting index: " + e.ToString()); @@ -82,7 +93,7 @@ private Source _RequestStart() { List entries = new List(); if (ParseData != null) { try { - entries.AddRange(ParseData(data)); + entries.AddRange(ParseData(this, data)); } catch (Exception e) { ErrorDialog = "updater_versions_err_format"; Logger.Log(LogLevel.Warn, "updater", "Failed parsing index: " + e.ToString()); @@ -107,40 +118,6 @@ private Source _RequestStart() { } } - // Highly convoluted scientific method to determine the entry order: - // - Order by first occurence of branch - // - Order by version inside branch - Dictionary branchFirsts = new Dictionary(); - // Force stable, then beta, then dev branches to appear first. - branchFirsts["stable"] = int.MaxValue; - branchFirsts["beta"] = int.MaxValue - 3; - branchFirsts["dev"] = int.MaxValue - 4; - - // Make sure that the branch we're on appears between stable and beta. - // This ensures that people don't miss out on important stability updates, - // but don't get dragged onto another branch by accident. - foreach (Entry entry in entries) { - if (entry.Build == Build) { - CoreModule.Settings.CurrentBranch = entry.Branch; - break; - } - } - - if (CoreModule.Settings.CurrentBranch != null) { - branchFirsts[CoreModule.Settings.CurrentBranch] = int.MaxValue - 2; - } - - for (int i = 0; i < entries.Count; i++) { - Entry entry = entries[i]; - if (!branchFirsts.ContainsKey(entry.Branch)) - branchFirsts[entry.Branch] = i; - } - - entries.Sort((a, b) => { - if (a.Branch != b.Branch) - return -(branchFirsts[a.Branch].CompareTo(branchFirsts[b.Branch])); - return -a.Build.CompareTo(b.Build); - }); Entries = new ReadOnlyCollection(entries); return this; } @@ -159,14 +136,34 @@ public void Clear() { public static List Sources = new List() { new Source { - NameDialog = "updater_src_buildbot", + DisplayName = "updater_src_stable", + NameDialog = "updater_src_release_github", - Index = "https://dev.azure.com/EverestAPI/Everest/_apis/build/builds?definitions=3&api-version=5.0", + UpdatePriority = UpdatePriority.High, - IsCurrent = () => VersionSuffix.StartsWith("azure-"), + Index = "https://api.github.com/repos/EverestAPI/Everest/releases", + ParseData = GitHubDataParser(offset: 700) + }, + new Source { + DisplayName = "updater_src_beta", + NameDialog = "updater_src_release_github", - ParseData = AzureDataParser("https://dev.azure.com/EverestAPI/Everest/_apis/build/builds/{0}/artifacts?artifactName=main&api-version=5.0&%24format=zip", 700) - } + Index = "https://api.github.com/repos/EverestAPI/Everest/releases", + ParseData = GitHubDataParser(offset: 700, prerelease: true) + }, + new Source { + DisplayName = "updater_src_dev", + NameDialog = "updater_src_buildbot_azure", + + Index = new URIHelper("https://dev.azure.com/EverestAPI/Everest/_apis/build/builds", new NameValueCollection() { + {"definitions", "3"}, + {"branchName", "refs/heads/dev"}, + {"statusFilter", "completed"}, + {"resultsFilter", "succeeded"}, + {"api-version", "5.0"}, + }).ToString(), + ParseData = AzureDataParser("https://dev.azure.com/EverestAPI/Everest/_apis/build/builds/{0}/artifacts?artifactName=main&api-version=5.0&%24format=zip", offset: 700) + }, }; public static Task RequestAll() { @@ -180,7 +177,7 @@ public static Task RequestAll() { return Task.Factory.ContinueWhenAll(tasks, finished => { List all = new List(); foreach (Source source in Sources) { - if (source.Entries == null || !source.IsCurrent()) + if (source.Entries == null || source.DisplayName != CoreModule.Settings.CurrentBranch) continue; all.AddRange(source.Entries); } @@ -203,8 +200,8 @@ public static Task RequestAll() { }); } - private static Func CommonLineParser(string root) - => (line) => { + private static Func CommonLineParser(string root) + => (source, line) => { string[] split = line.Split(' '); if (split.Length < 2 || split.Length > 3) throw new Exception("Version list format incompatible!"); @@ -232,27 +229,47 @@ private static Func CommonLineParser(string root) name = name.Substring(0, indexOfBranch); } - return new Entry(name, branch, url, int.Parse(Regex.Match(split[1], @"\d+").Value)); + return new Entry(name, url, int.Parse(Regex.Match(split[1], @"\d+").Value), source); }; - private static Func> AzureDataParser(string artifactFormat, int offset) - => (dataRaw) => { + private static Func> AzureDataParser(string artifactFormat, int offset) + => (source, dataRaw) => { List entries = new List(); JObject root = JObject.Parse(dataRaw); JArray list = root["value"] as JArray; foreach (JObject build in list) { - if (build["status"].ToObject() != "completed" || build["result"].ToObject() != "succeeded") - continue; + int id = build["id"].ToObject(); + string url = string.Format(artifactFormat, id); + Entry entry = new Entry((id + offset).ToString(), url, id + offset, source); + try { entry.Description = build.SelectToken("triggerInfo['ci.message']").ToString().Split('\n')[0]; } catch {} + entries.Add(entry); + } + + return entries; + }; + + private static Func> GitHubDataParser(int offset, bool prerelease=false) + => (source, dataRaw) => { + List entries = new List(); - string reason = build["reason"].ToObject(); - if (reason != "manual" && reason != "individualCI") + JArray list = JArray.Parse(dataRaw); + foreach (JObject release in list) { + if (prerelease != release["prerelease"].ToObject()) continue; - int id = build["id"].ToObject(); - string branch = build["sourceBranch"].ToObject().Replace("refs/heads/", ""); - string url = string.Format(artifactFormat, id); - entries.Add(new Entry((id + offset).ToString(), branch, url, id + offset)); + string build = Regex.Match(release["name"].ToString(), @"\d+$").Value; + string url = null; + foreach (JObject asset in release["assets"] as JArray) { + if (asset["name"].ToString() == "main.zip") { + url = asset["url"].ToString(); + break; + } + } + if (url is null) + throw new Exception("main.zip asset not found for release"); + + entries.Add(new Entry(build, url, int.Parse(build), source)); } return entries; @@ -277,6 +294,7 @@ public static void Update(OuiLoggedProgress progress, Entry version = null) { return; } + CoreModule.Settings.CurrentBranch = version.Source.DisplayName; progress.Init(Dialog.Clean("updater_title"), new Task(() => _UpdateStart(progress, version)), 0); } private static void _UpdateStart(OuiLoggedProgress progress, Entry version) { @@ -286,7 +304,7 @@ private static void _UpdateStart(OuiLoggedProgress progress, Entry version) { 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(string.Format(Dialog.Get("EVERESTUPDATER_UPDATING"), version.Name, version.Source.DisplayName.DialogClean(), version.URL)); progress.LogLine(Dialog.Clean("EVERESTUPDATER_DOWNLOADING")); try { diff --git a/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs b/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs index d591037fd..42c5220e9 100644 --- a/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs @@ -1,4 +1,6 @@ -using FMOD.Studio; +using Celeste.Mod.Core; +using Celeste.Mod.Helpers; +using FMOD.Studio; using Microsoft.Xna.Framework; using Monocle; using System.Collections; @@ -9,6 +11,8 @@ public class OuiVersionList : Oui, OuiModOptions.ISubmenu { private TextMenu menu; + private TextMenu.SubHeader currentBranchName; + private const float onScreenX = 960f; private const float offScreenX = 2880f; @@ -22,80 +26,102 @@ public OuiVersionList() { } public TextMenu CreateMenu(bool inGame, EventInstance snapshot) { - menu = new TextMenu(); + menu = new patch_TextMenu() { + CompactWidthMode = true + }; items.Clear(); menu.Add(new TextMenu.Header(Dialog.Clean("updater_versions_title"))); menu.Add(new patch_TextMenu.patch_SubHeader(Dialog.Clean("updater_versions_current").Replace("((version))", Everest.BuildString))); - ReloadItems(); + var currentBranch = new TextMenu.Option(Dialog.Clean("UPDATER_CURRENT_BRANCH")); + menu.Add(currentBranch); + + currentBranchName = new TextMenuExt.SubHeaderExt("") { + HeightExtra = 0f + }; + menu.Add(currentBranchName); + + Everest.Updater.Source currentSource = null; + foreach (Everest.Updater.Source source in Everest.Updater.Sources) { + currentBranch.Add(source.DisplayName.DialogClean(), source, source.DisplayName == CoreModule.Settings.CurrentBranch); + if (source.DisplayName == CoreModule.Settings.CurrentBranch) + currentSource = source; + } + currentBranch.Change(ReloadItems); + + ReloadItems(currentSource ?? currentBranch.Values[0].Item2); return menu; } - private void ReloadItems() { + private void ReloadItems(Everest.Updater.Source source) { + // Abuse using statements to avoid having to refactor again + using var scopeFinalizer = new ScopeFinalizer(() => { + // Do this afterwards as the menu has now properly updated its size. + for (int i = 0; i < items.Count; i++) + Add(new Coroutine(FadeIn(i, items[i]))); + + if (menu.Height > menu.ScrollableMinSize) { + menu.Position.Y = menu.ScrollTargetY; + } + }); + using var batchModeContext = new TextMenuExt.BatchModeContext((patch_TextMenu) menu); + foreach (TextMenu.Item item in items) menu.Remove(item); items.Clear(); - foreach (Everest.Updater.Source source in Everest.Updater.Sources) { - TextMenuExt.SubHeaderExt header = new TextMenuExt.SubHeaderExt(source.NameDialog.DialogClean()); - header.Alpha = 0f; - menu.Add(header); - items.Add(header); - - if (source.ErrorDialog != null) { - string text = source.ErrorDialog.DialogClean(); - TextMenuExt.SubHeaderExt error = new TextMenuExt.SubHeaderExt(text); - error.Alpha = 0f; - menu.Add(error); - items.Add(error); - continue; - } + currentBranchName.Title = source.DisplayName.DialogClean(); + + if (source.ErrorDialog != null) { + string text = source.ErrorDialog.DialogClean(); + TextMenuExt.SubHeaderExt error = new TextMenuExt.SubHeaderExt(text) { + Alpha = 0f, + AlwaysCenter = true, + }; + menu.Add(error); + items.Add(error); + return; + } - if (source.Entries == null) { - TextMenuExt.SubHeaderExt info = new TextMenuExt.SubHeaderExt(Dialog.Clean("updater_versions_requesting")); - info.Alpha = 0f; - menu.Add(info); - items.Add(info); - continue; - } + if (source.Entries == null) { + TextMenuExt.SubHeaderExt info = new TextMenuExt.SubHeaderExt(Dialog.Clean("updater_versions_requesting")) { + Alpha = 0f, + AlwaysCenter = true, + }; + menu.Add(info); + items.Add(info); + return; + } - string branch = null; - int count = 0; - foreach (Everest.Updater.Entry entry in source.Entries) { - if (entry.Branch != branch) { - branch = entry.Branch; - count = 0; - - if (!string.IsNullOrEmpty(entry.Branch)) { - TextMenuExt.SubHeaderExt headerBranch = new TextMenuExt.SubHeaderExt("branch: " + entry.Branch); - headerBranch.Alpha = 0f; - menu.Add(headerBranch); - items.Add(headerBranch); - } - } - if (count >= buildsPerBranch) - continue; - count++; - TextMenuExt.ButtonExt item = new TextMenuExt.ButtonExt(entry.Name); - item.Alpha = 0f; - menu.Add(item.Pressed(() => { - Everest.Updater.Update(OuiModOptions.Instance.Overworld.Goto(), entry); - })); - items.Add(item); + int count = 0; + foreach (Everest.Updater.Entry entry in source.Entries) { + if (count >= buildsPerBranch) continue; + count++; + TextMenuExt.ButtonExt item = new TextMenuExt.ButtonExt(entry.Name) { + Alpha = 0f, + AlwaysCenter = true, + }; + menu.Add(item.Pressed(() => { + Everest.Updater.Update(OuiModOptions.Instance.Overworld.Goto(), entry); + })); + items.Add(item); + if (!string.IsNullOrWhiteSpace(entry.Description)) { + string description = entry.Description; + // Recommended commit title max length + if (description.Length > 50) + description = description.Substring(0, 50)+"..."; + var info = new TextMenuExt.SubHeaderExt(description) { + Alpha = 0f, + HeightExtra = 0f, + AlwaysCenter = true, + }; + menu.Add(info); + items.Add(info); } - - } - - // Do this afterwards as the menu has now properly updated its size. - for (int i = 0; i < items.Count; i++) - Add(new Coroutine(FadeIn(i, items[i]))); - - if (menu.Height > menu.ScrollableMinSize) { - menu.Position.Y = menu.ScrollTargetY; } } From f77d9a0a0c03b7be229979ef17918a9527fdf831 Mon Sep 17 00:00:00 2001 From: coloursofnoise Date: Tue, 16 Aug 2022 17:37:27 -0700 Subject: [PATCH 09/74] Fix TitleScreen update notification --- Celeste.Mod.mm/Patches/OuiTitleScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Celeste.Mod.mm/Patches/OuiTitleScreen.cs b/Celeste.Mod.mm/Patches/OuiTitleScreen.cs index 5829eb299..1ef9c68fe 100644 --- a/Celeste.Mod.mm/Patches/OuiTitleScreen.cs +++ b/Celeste.Mod.mm/Patches/OuiTitleScreen.cs @@ -104,9 +104,9 @@ public override void Update() { } } - if (!updateChecked && Everest.Updater.HasUpdate && Everest.Updater.Newest != null && alpha >= 1f) { + if (!updateChecked && Everest.Updater.HasUpdate && Everest.Updater.Newest.Source.UpdatePriority is not Everest.Updater.UpdatePriority.None && alpha >= 1f) { updateChecked = true; - updateTex = Everest.Updater.Newest.Branch == "stable" ? GFX.Gui["areas/new"] : GFX.Gui["areas/new-yellow"]; + updateTex = Everest.Updater.Newest.Source.UpdatePriority is Everest.Updater.UpdatePriority.High ? GFX.Gui["areas/new"] : GFX.Gui["areas/new-yellow"]; Tween tween = Tween.Create(Tween.TweenMode.Oneshot, Ease.CubeInOut, 0.3f, true); tween.OnUpdate = t => { updateAlpha = t.Percent; From 79331dc1d8c2143deacfe82bba443144aa8f9a9a Mon Sep 17 00:00:00 2001 From: coloursofnoise Date: Tue, 16 Aug 2022 17:37:48 -0700 Subject: [PATCH 10/74] Add Dialog keys for new updater text --- Celeste.Mod.mm/Content/Dialog/English.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Celeste.Mod.mm/Content/Dialog/English.txt b/Celeste.Mod.mm/Content/Dialog/English.txt index 80815ad00..c5c0c8ec8 100755 --- a/Celeste.Mod.mm/Content/Dialog/English.txt +++ b/Celeste.Mod.mm/Content/Dialog/English.txt @@ -125,9 +125,19 @@ UPDATER_VERSIONS_ERR_DOWNLOAD= Failed downloading version list. UPDATER_VERSIONS_ERR_FORMAT= Unknown format. + + UPDATER_CURRENT_BRANCH= Current branch + + UPDATER_SRC_STABLE= STABLE + UPDATER_SRC_BETA= BETA + UPDATER_SRC_DEV= DEV + + UPDATER_SRC_RELEASE_GITHUB= Tagged releases (GitHub) + UPDATER_SRC_BUILDBOT_AZURE= Automatic builds (Azure) +# currently unused UPDATER_SRC_BUILDBOT= Automatic builds - + # Everest Updater EVERESTUPDATER_NOTSUPPORTED= Updating not supported on this platform - cancelling. EVERESTUPDATER_NOUPDATE= No update - cancelling. From 07a6c807af16289090f5e6cf949f97b3e8e533db Mon Sep 17 00:00:00 2001 From: coloursofnoise Date: Tue, 16 Aug 2022 17:41:32 -0700 Subject: [PATCH 11/74] Format changed files --- Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs | 4 ++-- Celeste.Mod.mm/Mod/UI/OuiVersionList.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs index bb7a30073..deaf26c5e 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs @@ -242,14 +242,14 @@ private static Func> AzureDataParser(string artifact int id = build["id"].ToObject(); string url = string.Format(artifactFormat, id); Entry entry = new Entry((id + offset).ToString(), url, id + offset, source); - try { entry.Description = build.SelectToken("triggerInfo['ci.message']").ToString().Split('\n')[0]; } catch {} + try { entry.Description = build.SelectToken("triggerInfo['ci.message']").ToString().Split('\n')[0]; } catch { } entries.Add(entry); } return entries; }; - private static Func> GitHubDataParser(int offset, bool prerelease=false) + private static Func> GitHubDataParser(int offset, bool prerelease = false) => (source, dataRaw) => { List entries = new List(); diff --git a/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs b/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs index 42c5220e9..8c74a3516 100644 --- a/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs @@ -113,7 +113,7 @@ private void ReloadItems(Everest.Updater.Source source) { string description = entry.Description; // Recommended commit title max length if (description.Length > 50) - description = description.Substring(0, 50)+"..."; + description = description.Substring(0, 50) + "..."; var info = new TextMenuExt.SubHeaderExt(description) { Alpha = 0f, HeightExtra = 0f, From 143a2e253ff5fe3024fd17ebe31b0a2a2a9d3f49 Mon Sep 17 00:00:00 2001 From: coloursofnoise Date: Tue, 16 Aug 2022 18:03:48 -0700 Subject: [PATCH 12/74] Add User-Agent header to all web requests --- Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs index deaf26c5e..49e15f86d 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs @@ -432,9 +432,11 @@ public static void DownloadFileWithProgress(string url, string destPath, Func Date: Tue, 16 Aug 2022 18:10:07 -0700 Subject: [PATCH 13/74] Use User-Agent property to set header --- Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs index 49e15f86d..1300dd447 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs @@ -432,7 +432,7 @@ public static void DownloadFileWithProgress(string url, string destPath, Func Date: Tue, 16 Aug 2022 22:13:39 -0700 Subject: [PATCH 14/74] Set Accept header to tell GitHub to download asset binary --- Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs index 1300dd447..13f395498 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs @@ -433,6 +433,7 @@ public static void DownloadFileWithProgress(string url, string destPath, Func Date: Wed, 17 Aug 2022 23:17:49 -0700 Subject: [PATCH 15/74] Make Everest.Updater class accessible to mods --- Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs index 13f395498..85f0364f1 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs @@ -18,7 +18,7 @@ namespace Celeste.Mod { public static partial class Everest { // TODO: General purpose updater for both Everest itself and any runtime mods. - internal static class Updater { + public static class Updater { public enum UpdatePriority { High, Low, None @@ -142,14 +142,14 @@ public void Clear() { UpdatePriority = UpdatePriority.High, Index = "https://api.github.com/repos/EverestAPI/Everest/releases", - ParseData = GitHubDataParser(offset: 700) + ParseData = GitHubReleasesParser(offset: 700) }, new Source { DisplayName = "updater_src_beta", NameDialog = "updater_src_release_github", Index = "https://api.github.com/repos/EverestAPI/Everest/releases", - ParseData = GitHubDataParser(offset: 700, prerelease: true) + ParseData = GitHubReleasesParser(offset: 700, prerelease: true) }, new Source { DisplayName = "updater_src_dev", @@ -162,7 +162,7 @@ public void Clear() { {"resultsFilter", "succeeded"}, {"api-version", "5.0"}, }).ToString(), - ParseData = AzureDataParser("https://dev.azure.com/EverestAPI/Everest/_apis/build/builds/{0}/artifacts?artifactName=main&api-version=5.0&%24format=zip", offset: 700) + ParseData = AzureBuildsParser("https://dev.azure.com/EverestAPI/Everest/_apis/build/builds/{0}/artifacts?artifactName=main&api-version=5.0&%24format=zip", offset: 700) }, }; @@ -232,7 +232,7 @@ private static Func CommonLineParser(string root) return new Entry(name, url, int.Parse(Regex.Match(split[1], @"\d+").Value), source); }; - private static Func> AzureDataParser(string artifactFormat, int offset) + public static Func> AzureBuildsParser(string artifactFormat, int offset) => (source, dataRaw) => { List entries = new List(); @@ -249,7 +249,7 @@ private static Func> AzureDataParser(string artifact return entries; }; - private static Func> GitHubDataParser(int offset, bool prerelease = false) + public static Func> GitHubReleasesParser(int offset, bool prerelease = false) => (source, dataRaw) => { List entries = new List(); From c2b04eea90651a509990d134527e4a9f32d24b0b Mon Sep 17 00:00:00 2001 From: coloursofnoise Date: Wed, 17 Aug 2022 23:28:49 -0700 Subject: [PATCH 16/74] Rename fields for clarity and fix display --- Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs | 22 +++++++++---------- Celeste.Mod.mm/Mod/UI/OuiVersionList.cs | 6 ++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs index 85f0364f1..d994e2b8d 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs @@ -40,9 +40,9 @@ public Entry(string name, string url, int version, Source source) { public class Source { - public string DisplayName; + public string Name; - public string NameDialog; + public string Description; public UpdatePriority UpdatePriority = UpdatePriority.Low; @@ -136,8 +136,8 @@ public void Clear() { public static List Sources = new List() { new Source { - DisplayName = "updater_src_stable", - NameDialog = "updater_src_release_github", + Name = "updater_src_stable", + Description = "updater_src_release_github", UpdatePriority = UpdatePriority.High, @@ -145,15 +145,15 @@ public void Clear() { ParseData = GitHubReleasesParser(offset: 700) }, new Source { - DisplayName = "updater_src_beta", - NameDialog = "updater_src_release_github", + Name = "updater_src_beta", + Description = "updater_src_release_github", Index = "https://api.github.com/repos/EverestAPI/Everest/releases", ParseData = GitHubReleasesParser(offset: 700, prerelease: true) }, new Source { - DisplayName = "updater_src_dev", - NameDialog = "updater_src_buildbot_azure", + Name = "updater_src_dev", + Description = "updater_src_buildbot_azure", Index = new URIHelper("https://dev.azure.com/EverestAPI/Everest/_apis/build/builds", new NameValueCollection() { {"definitions", "3"}, @@ -177,7 +177,7 @@ public static Task RequestAll() { return Task.Factory.ContinueWhenAll(tasks, finished => { List all = new List(); foreach (Source source in Sources) { - if (source.Entries == null || source.DisplayName != CoreModule.Settings.CurrentBranch) + if (source.Entries == null || source.Name != CoreModule.Settings.CurrentBranch) continue; all.AddRange(source.Entries); } @@ -294,7 +294,7 @@ public static void Update(OuiLoggedProgress progress, Entry version = null) { return; } - CoreModule.Settings.CurrentBranch = version.Source.DisplayName; + CoreModule.Settings.CurrentBranch = version.Source.Name; progress.Init(Dialog.Clean("updater_title"), new Task(() => _UpdateStart(progress, version)), 0); } private static void _UpdateStart(OuiLoggedProgress progress, Entry version) { @@ -304,7 +304,7 @@ private static void _UpdateStart(OuiLoggedProgress progress, Entry version) { 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.Source.DisplayName.DialogClean(), version.URL)); + progress.LogLine(string.Format(Dialog.Get("EVERESTUPDATER_UPDATING"), version.Name, version.Source.Name.DialogClean(), version.URL)); progress.LogLine(Dialog.Clean("EVERESTUPDATER_DOWNLOADING")); try { diff --git a/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs b/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs index 8c74a3516..f667468c1 100644 --- a/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs @@ -45,8 +45,8 @@ public TextMenu CreateMenu(bool inGame, EventInstance snapshot) { Everest.Updater.Source currentSource = null; foreach (Everest.Updater.Source source in Everest.Updater.Sources) { - currentBranch.Add(source.DisplayName.DialogClean(), source, source.DisplayName == CoreModule.Settings.CurrentBranch); - if (source.DisplayName == CoreModule.Settings.CurrentBranch) + currentBranch.Add(source.Name.DialogCleanOrNull() ?? source.Name, source, source.Name == CoreModule.Settings.CurrentBranch); + if (source.Name == CoreModule.Settings.CurrentBranch) currentSource = source; } currentBranch.Change(ReloadItems); @@ -73,7 +73,7 @@ private void ReloadItems(Everest.Updater.Source source) { menu.Remove(item); items.Clear(); - currentBranchName.Title = source.DisplayName.DialogClean(); + currentBranchName.Title = source.Description.DialogCleanOrNull() ?? source.Description; if (source.ErrorDialog != null) { string text = source.ErrorDialog.DialogClean(); From 3f5302a24193d09ac167f501034b9ccd5a647d78 Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Tue, 6 Sep 2022 04:38:28 +0200 Subject: [PATCH 17/74] Move parallax rendering improvement to separate method --- Celeste.Mod.mm/Patches/BackdropRenderer.cs | 19 ++++++++++++++++++- Celeste.Mod.mm/Patches/Parallax.cs | 8 +++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Celeste.Mod.mm/Patches/BackdropRenderer.cs b/Celeste.Mod.mm/Patches/BackdropRenderer.cs index 51b959337..fd6de3bf1 100644 --- a/Celeste.Mod.mm/Patches/BackdropRenderer.cs +++ b/Celeste.Mod.mm/Patches/BackdropRenderer.cs @@ -26,7 +26,7 @@ public void StartSpritebatchLooping(BlendState blendState) { namespace MonoMod { /// - /// Patches the method to begin a wrapping spritebatch for parallaxes. + /// Patches the method to render parallaxes using a wrapping SamplerState. /// [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchBackdropRendererRender))] class PatchBackdropRendererRenderAttribute : Attribute { } @@ -36,6 +36,7 @@ static partial class MonoModRules { public static void PatchBackdropRendererRender(ILContext context, CustomAttribute attrib) { MethodReference m_BackDropRenderer_StartSpritebatchLooping = context.Method.DeclaringType.FindMethod("StartSpritebatchLooping"); TypeReference t_Parallax = context.Module.GetType("Celeste.Parallax"); + MethodReference n_Parallax_ImprovedRender = t_Parallax.Resolve().FindMethod("ImprovedRender"); ILCursor cursor = new ILCursor(context); @@ -53,6 +54,22 @@ public static void PatchBackdropRendererRender(ILContext context, CustomAttribut cursor.MarkLabel(nextIf); cursor.Index--; cursor.Emit(OpCodes.Br, nextIf); + + // call ImprovedRender instead of Render for Parallax + cursor.GotoNext(instr => instr.MatchLdarg(1), instr => instr.MatchCallvirt("Celeste.Backdrop", "Render")); + cursor.Emit(OpCodes.Isinst, t_Parallax); + cursor.Emit(OpCodes.Dup); + ILLabel parallaxRender = cursor.DefineLabel(); + cursor.Emit(OpCodes.Brtrue_S, parallaxRender); + cursor.Emit(OpCodes.Pop); + cursor.Emit(OpCodes.Ldloc_2); + cursor.Index += 2; + ILLabel continueLoop = cursor.DefineLabel(); + cursor.Emit(OpCodes.Br, continueLoop); + cursor.MarkLabel(parallaxRender); + cursor.Emit(OpCodes.Ldarg_1); + cursor.Emit(OpCodes.Callvirt, n_Parallax_ImprovedRender); + cursor.MarkLabel(continueLoop); } } } \ No newline at end of file diff --git a/Celeste.Mod.mm/Patches/Parallax.cs b/Celeste.Mod.mm/Patches/Parallax.cs index 703469b10..fdd3ddfaf 100644 --- a/Celeste.Mod.mm/Patches/Parallax.cs +++ b/Celeste.Mod.mm/Patches/Parallax.cs @@ -3,7 +3,6 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Monocle; -using MonoMod; using System; namespace Celeste { @@ -15,8 +14,11 @@ public patch_Parallax(MTexture texture) : base(texture) { // no-op, ignored by MonoMod } - [MonoModReplace] - public override void Render(Scene scene) { + /// + /// An optimized version of the vanilla Render method. Only works if the SamplerState is set to PointWrap. + /// + /// The Level to render the Parallax to. + public void ImprovedRender(Scene scene) { Vector2 camera = ((scene as Level).Camera.Position + CameraOffset).Floor(); Vector2 position = (Position - camera * Scroll).Floor(); float alpha = fadeIn * Alpha * FadeAlphaMultiplier; From 8013873f0411879cfc4185a62eb91de2446b85c0 Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Tue, 6 Sep 2022 05:49:51 +0200 Subject: [PATCH 18/74] Only optimize modded Parallax rendering --- Celeste.Mod.mm/Patches/Monocle/MTexture.cs | 17 +++++++---------- Celeste.Mod.mm/Patches/Parallax.cs | 6 +++++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Celeste.Mod.mm/Patches/Monocle/MTexture.cs b/Celeste.Mod.mm/Patches/Monocle/MTexture.cs index e13d009cf..6d7d9b687 100644 --- a/Celeste.Mod.mm/Patches/Monocle/MTexture.cs +++ b/Celeste.Mod.mm/Patches/Monocle/MTexture.cs @@ -213,6 +213,8 @@ public void UndoOverride(ModAsset asset) { return new Rectangle(x, y, w, h); } + public bool IsPacked => Width != Texture.Width || Height != Texture.Height; + private static Texture2D CreateUnpackedTexture(Texture2D src, Rectangle rect) { Texture2D tex = new Texture2D(src.GraphicsDevice, rect.Width, rect.Height); @@ -311,6 +313,11 @@ public float ScaleFix { Monocle.Draw.SpriteBatch.Draw(Texture.Texture, position, GetRelativeRect(clip), color, rotation, (origin - DrawOffset) / scaleFix, scale * scaleFix, SpriteEffects.None, 0f); } + public void Draw(Vector2 position, Vector2 origin, Color color, float scale, float rotation, SpriteEffects flip, Rectangle absoluteClip) { + float scaleFix = ScaleFix; + Monocle.Draw.SpriteBatch.Draw(Texture.Texture, position, absoluteClip, color, rotation, (origin - DrawOffset) / scaleFix, scale * scaleFix, flip, 0f); + } + #endregion #region DrawCentered @@ -822,16 +829,6 @@ public float ScaleFix { #endregion - #region DrawWithWrappingSupport - - public void DrawWithWrappingSupport(Vector2 position, Vector2 origin, Color color, float scale, float rotation, SpriteEffects flip, Rectangle absoluteClip) { - float scaleFix = ScaleFix; - // TODO does this have to use reflection? - Monocle.Draw.SpriteBatch.Draw(typeof(SpriteBatch).GetField("samplerState", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(Monocle.Draw.SpriteBatch) != SamplerState.PointWrap ? Texture.Texture : Unpacked, position, absoluteClip, color, rotation, (origin - DrawOffset) / scaleFix, scale * scaleFix, flip, 0f); - } - - #endregion - #endregion } diff --git a/Celeste.Mod.mm/Patches/Parallax.cs b/Celeste.Mod.mm/Patches/Parallax.cs index fdd3ddfaf..7a206e4a1 100644 --- a/Celeste.Mod.mm/Patches/Parallax.cs +++ b/Celeste.Mod.mm/Patches/Parallax.cs @@ -19,6 +19,10 @@ public patch_Parallax(MTexture texture) : base(texture) { /// /// The Level to render the Parallax to. public void ImprovedRender(Scene scene) { + if (((patch_MTexture) Texture).IsPacked) { + Render(scene); + return; + } Vector2 camera = ((scene as Level).Camera.Position + CameraOffset).Floor(); Vector2 position = (Position - camera * Scroll).Floor(); float alpha = fadeIn * Alpha * FadeAlphaMultiplier; @@ -49,7 +53,7 @@ public void ImprovedRender(Scene scene) { flip |= SpriteEffects.FlipVertically; } Rectangle rect = new Rectangle(0, 0, LoopX ? (int) Math.Ceiling(Celeste.GameWidth - position.X) : Texture.Width, LoopY ? (int) Math.Ceiling(Celeste.GameHeight - position.Y) : Texture.Height); - ((patch_MTexture) Texture).DrawWithWrappingSupport(position, Vector2.Zero, color, 1f, 0f, flip, rect); + ((patch_MTexture) Texture).Draw(position, Vector2.Zero, color, 1f, 0f, flip, rect); } } } \ No newline at end of file From af1b5aa35a89f7ecda3ff441ed7292f80d5d5b3b Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Tue, 6 Sep 2022 05:51:58 +0200 Subject: [PATCH 19/74] Clean up unused code --- Celeste.Mod.mm/Patches/Monocle/MTexture.cs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/Celeste.Mod.mm/Patches/Monocle/MTexture.cs b/Celeste.Mod.mm/Patches/Monocle/MTexture.cs index 6d7d9b687..2c97ec4d2 100644 --- a/Celeste.Mod.mm/Patches/Monocle/MTexture.cs +++ b/Celeste.Mod.mm/Patches/Monocle/MTexture.cs @@ -7,7 +7,6 @@ using MonoMod; using System; using System.Collections.Generic; -using System.Reflection; namespace Monocle { class patch_MTexture : MTexture { @@ -45,8 +44,6 @@ public Atlas Atlas { private List _ModAssets; - private Texture2D unpacked; - // Patching constructors is ugly. public extern void orig_ctor(patch_MTexture parent, int x, int y, int width, int height); [MonoModConstructor] @@ -215,23 +212,6 @@ public void UndoOverride(ModAsset asset) { public bool IsPacked => Width != Texture.Width || Height != Texture.Height; - private static Texture2D CreateUnpackedTexture(Texture2D src, Rectangle rect) - { - Texture2D tex = new Texture2D(src.GraphicsDevice, rect.Width, rect.Height); - int count = rect.Width * rect.Height; - Color[] data = new Color[count]; - src.GetData(0, rect, data, 0, count); - tex.SetData(data); - return tex; - } - - private Texture2D Unpacked { - get { - unpacked ??= Width == Texture.Width && Height == Texture.Height ? Texture.Texture : CreateUnpackedTexture(Texture.Texture, ClipRect); - return unpacked; - } - } - #region Drawing Methods #region Draw-related fixes From cf0da67524f598cf50fdbdfe84b7396f31a39bb8 Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Wed, 7 Sep 2022 00:18:58 +0200 Subject: [PATCH 20/74] Fix typo --- Celeste.Mod.mm/Patches/BackdropRenderer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Celeste.Mod.mm/Patches/BackdropRenderer.cs b/Celeste.Mod.mm/Patches/BackdropRenderer.cs index fd6de3bf1..cd672e6ad 100644 --- a/Celeste.Mod.mm/Patches/BackdropRenderer.cs +++ b/Celeste.Mod.mm/Patches/BackdropRenderer.cs @@ -36,8 +36,8 @@ static partial class MonoModRules { public static void PatchBackdropRendererRender(ILContext context, CustomAttribute attrib) { MethodReference m_BackDropRenderer_StartSpritebatchLooping = context.Method.DeclaringType.FindMethod("StartSpritebatchLooping"); TypeReference t_Parallax = context.Module.GetType("Celeste.Parallax"); - MethodReference n_Parallax_ImprovedRender = t_Parallax.Resolve().FindMethod("ImprovedRender"); - + MethodReference m_Parallax_ImprovedRender = t_Parallax.Resolve().FindMethod("ImprovedRender"); + ILCursor cursor = new ILCursor(context); /* Change: StartSpritebatch(blendState); @@ -68,7 +68,7 @@ public static void PatchBackdropRendererRender(ILContext context, CustomAttribut cursor.Emit(OpCodes.Br, continueLoop); cursor.MarkLabel(parallaxRender); cursor.Emit(OpCodes.Ldarg_1); - cursor.Emit(OpCodes.Callvirt, n_Parallax_ImprovedRender); + cursor.Emit(OpCodes.Callvirt, m_Parallax_ImprovedRender); cursor.MarkLabel(continueLoop); } } From 551297271d45973250e7c0076c41dd5484df5359 Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Wed, 7 Sep 2022 00:20:30 +0200 Subject: [PATCH 21/74] Wrap long line Co-authored-by: microlith57 --- Celeste.Mod.mm/Patches/Parallax.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Celeste.Mod.mm/Patches/Parallax.cs b/Celeste.Mod.mm/Patches/Parallax.cs index 7a206e4a1..099d693bd 100644 --- a/Celeste.Mod.mm/Patches/Parallax.cs +++ b/Celeste.Mod.mm/Patches/Parallax.cs @@ -52,7 +52,9 @@ public void ImprovedRender(Scene scene) { if (FlipY) { flip |= SpriteEffects.FlipVertically; } - Rectangle rect = new Rectangle(0, 0, LoopX ? (int) Math.Ceiling(Celeste.GameWidth - position.X) : Texture.Width, LoopY ? (int) Math.Ceiling(Celeste.GameHeight - position.Y) : Texture.Height); + Rectangle rect = new Rectangle(0, 0, + LoopX ? (int) Math.Ceiling(Celeste.GameWidth - position.X) : Texture.Width, + LoopY ? (int) Math.Ceiling(Celeste.GameHeight - position.Y) : Texture.Height); ((patch_MTexture) Texture).Draw(position, Vector2.Zero, color, 1f, 0f, flip, rect); } } From b0f007252b8efdd2a3a2f118acaf603250a36d1b Mon Sep 17 00:00:00 2001 From: coloursofnoise Date: Fri, 9 Sep 2022 14:19:48 -0700 Subject: [PATCH 22/74] Use browser_download_url when downloading builds from GitHub --- Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs index d994e2b8d..106acd5c1 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs @@ -1,4 +1,4 @@ -using Celeste.Mod.Core; +using Celeste.Mod.Core; using Celeste.Mod.Helpers; using Celeste.Mod.UI; using Ionic.Zip; @@ -262,7 +262,7 @@ public static Func> GitHubReleasesParser(int offset, string url = null; foreach (JObject asset in release["assets"] as JArray) { if (asset["name"].ToString() == "main.zip") { - url = asset["url"].ToString(); + url = asset["browser_download_url"].ToString(); break; } } From 65df4f6d8d2fdbd51ff7495e46fe8e572c1034fa Mon Sep 17 00:00:00 2001 From: coloursofnoise Date: Fri, 9 Sep 2022 17:51:00 -0700 Subject: [PATCH 23/74] Use pre-formatted everest update list Azure and GitHub parsers are kept for use by mods --- Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs index 106acd5c1..6ad1c962d 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Updater.cs @@ -141,28 +141,22 @@ public void Clear() { UpdatePriority = UpdatePriority.High, - Index = "https://api.github.com/repos/EverestAPI/Everest/releases", - ParseData = GitHubReleasesParser(offset: 700) + Index = GetEverestUpdaterDatabaseURL(), + ParseData = UpdateListParser("stable") }, new Source { Name = "updater_src_beta", Description = "updater_src_release_github", - Index = "https://api.github.com/repos/EverestAPI/Everest/releases", - ParseData = GitHubReleasesParser(offset: 700, prerelease: true) + Index = GetEverestUpdaterDatabaseURL(), + ParseData = UpdateListParser("beta") }, new Source { Name = "updater_src_dev", Description = "updater_src_buildbot_azure", - Index = new URIHelper("https://dev.azure.com/EverestAPI/Everest/_apis/build/builds", new NameValueCollection() { - {"definitions", "3"}, - {"branchName", "refs/heads/dev"}, - {"statusFilter", "completed"}, - {"resultsFilter", "succeeded"}, - {"api-version", "5.0"}, - }).ToString(), - ParseData = AzureBuildsParser("https://dev.azure.com/EverestAPI/Everest/_apis/build/builds/{0}/artifacts?artifactName=main&api-version=5.0&%24format=zip", offset: 700) + Index = GetEverestUpdaterDatabaseURL(), + ParseData = UpdateListParser("dev") }, }; @@ -200,6 +194,17 @@ public static Task RequestAll() { }); } + private static string _everestUpdaterDatabaseURL; + private static string GetEverestUpdaterDatabaseURL() { + if (string.IsNullOrEmpty(_everestUpdaterDatabaseURL)) { + using (WebClient wc = new WebClient()) { + Logger.Log(LogLevel.Verbose, "updater", "Fetching everest updater database URL"); + _everestUpdaterDatabaseURL = wc.DownloadString("https://everestapi.github.io/everestupdater.txt").Trim(); + } + } + return _everestUpdaterDatabaseURL; + } + private static Func CommonLineParser(string root) => (source, line) => { string[] split = line.Split(' '); @@ -232,6 +237,21 @@ private static Func CommonLineParser(string root) return new Entry(name, url, int.Parse(Regex.Match(split[1], @"\d+").Value), source); }; + public static Func> UpdateListParser(string branch) + => (source, dataRaw) => { + List entries = new List(); + + JArray list = JArray.Parse(dataRaw); + foreach (JObject release in list) { + if (release["branch"].ToString() == branch) { + int build = release["version"].ToObject(); + string url = release["mainDownload"].ToString(); + entries.Add(new Entry(build.ToString(), url, build, source)); + } + } + return entries; + }; + public static Func> AzureBuildsParser(string artifactFormat, int offset) => (source, dataRaw) => { List entries = new List(); From 53750224620251077863570cb52da2cf7193d225 Mon Sep 17 00:00:00 2001 From: coloursofnoise Date: Fri, 9 Sep 2022 18:05:14 -0700 Subject: [PATCH 24/74] Increase max number of builds on update screen Each of the three branches have been moved to their own tab, so a hard cap is much less useful. --- Celeste.Mod.mm/Mod/UI/OuiVersionList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs b/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs index f667468c1..1d0400060 100644 --- a/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiVersionList.cs @@ -20,7 +20,7 @@ public class OuiVersionList : Oui, OuiModOptions.ISubmenu { private List items = new List(); - private int buildsPerBranch = 4; + private int buildsPerBranch = 12; public OuiVersionList() { } From 8fa1ca9b1768bc5ed21051ea92fb41468bfc2360 Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Sun, 18 Sep 2022 02:40:48 +0200 Subject: [PATCH 25/74] Refactor styleground rendering improvements --- Celeste.Mod.mm/Patches/BackdropRenderer.cs | 109 ++++++++++----------- Celeste.Mod.mm/Patches/Parallax.cs | 21 ++-- 2 files changed, 66 insertions(+), 64 deletions(-) diff --git a/Celeste.Mod.mm/Patches/BackdropRenderer.cs b/Celeste.Mod.mm/Patches/BackdropRenderer.cs index cd672e6ad..57c67b170 100644 --- a/Celeste.Mod.mm/Patches/BackdropRenderer.cs +++ b/Celeste.Mod.mm/Patches/BackdropRenderer.cs @@ -1,75 +1,72 @@ using Microsoft.Xna.Framework.Graphics; -using Mono.Cecil; -using Mono.Cecil.Cil; using Monocle; using MonoMod; -using MonoMod.Cil; -using MonoMod.Utils; -using System; namespace Celeste { class patch_BackdropRenderer : BackdropRenderer { private bool usingSpritebatch; + private bool usingLoopingSpritebatch; + /// + /// Start a new spritebatch for backdrop rendering that uses SamplerState.PointWrap, but is otherwise identical to the one started by StartSpritebatch. + /// + /// the blend state for the new spritebatch public void StartSpritebatchLooping(BlendState blendState) { if (!usingSpritebatch) { Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, blendState, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, null, Matrix); } usingSpritebatch = true; + usingLoopingSpritebatch = true; } - [MonoModIgnore] - [PatchBackdropRendererRender] - public override extern void Render(Scene scene); - } -} - -namespace MonoMod { - /// - /// Patches the method to render parallaxes using a wrapping SamplerState. - /// - [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchBackdropRendererRender))] - class PatchBackdropRendererRenderAttribute : Attribute { } - - static partial class MonoModRules { - - public static void PatchBackdropRendererRender(ILContext context, CustomAttribute attrib) { - MethodReference m_BackDropRenderer_StartSpritebatchLooping = context.Method.DeclaringType.FindMethod("StartSpritebatchLooping"); - TypeReference t_Parallax = context.Module.GetType("Celeste.Parallax"); - MethodReference m_Parallax_ImprovedRender = t_Parallax.Resolve().FindMethod("ImprovedRender"); - - ILCursor cursor = new ILCursor(context); - - /* Change: StartSpritebatch(blendState); - to: backdrop is Parallax ? StartSpritebatchLooping(blendState) : StartSpritebatch(blendState); */ - cursor.GotoNext(instr => instr.MatchCallvirt("Celeste.BackdropRenderer", "StartSpritebatch")); - ILLabel beforeStartSpritebatch = cursor.MarkLabel(); - cursor.MoveBeforeLabels(); - cursor.Emit(OpCodes.Ldloc_2); // load backdrop - cursor.Emit(OpCodes.Isinst, t_Parallax); - cursor.Emit(OpCodes.Brfalse_S, beforeStartSpritebatch); - cursor.Emit(OpCodes.Callvirt, m_BackDropRenderer_StartSpritebatchLooping); - ILLabel nextIf = cursor.DefineLabel(); - cursor.GotoNext(MoveType.After, instr => instr.MatchCallvirt("Celeste.BackdropRenderer", "StartSpritebatch")); - cursor.MarkLabel(nextIf); - cursor.Index--; - cursor.Emit(OpCodes.Br, nextIf); + [MonoModReplace] + public new void EndSpritebatch() { + if (usingSpritebatch) + { + Draw.SpriteBatch.End(); + } + usingSpritebatch = false; + usingLoopingSpritebatch = false; + } - // call ImprovedRender instead of Render for Parallax - cursor.GotoNext(instr => instr.MatchLdarg(1), instr => instr.MatchCallvirt("Celeste.Backdrop", "Render")); - cursor.Emit(OpCodes.Isinst, t_Parallax); - cursor.Emit(OpCodes.Dup); - ILLabel parallaxRender = cursor.DefineLabel(); - cursor.Emit(OpCodes.Brtrue_S, parallaxRender); - cursor.Emit(OpCodes.Pop); - cursor.Emit(OpCodes.Ldloc_2); - cursor.Index += 2; - ILLabel continueLoop = cursor.DefineLabel(); - cursor.Emit(OpCodes.Br, continueLoop); - cursor.MarkLabel(parallaxRender); - cursor.Emit(OpCodes.Ldarg_1); - cursor.Emit(OpCodes.Callvirt, m_Parallax_ImprovedRender); - cursor.MarkLabel(continueLoop); + [MonoModReplace] + public override void Render(Scene scene) { + BlendState blendState = BlendState.AlphaBlend; + foreach (Backdrop backdrop in Backdrops) + { + if (backdrop.Visible) + { + if (backdrop is Parallax parallax && (!usingLoopingSpritebatch || parallax.BlendState != blendState)) + { + EndSpritebatch(); + blendState = parallax.BlendState; + } + if (backdrop is not Parallax && backdrop.UseSpritebatch && usingLoopingSpritebatch) { // make sure non-Parallax backdrops are drawn with the normal spritebatch parameters + EndSpritebatch(); + } + if (backdrop.UseSpritebatch && !usingSpritebatch) + { + if (backdrop is Parallax) + { + StartSpritebatchLooping(blendState); + } + else + { + StartSpritebatch(blendState); + } + } + if (!backdrop.UseSpritebatch && usingSpritebatch) + { + EndSpritebatch(); + } + backdrop.Render(scene); + } + } + if (Fade > 0f) + { + Draw.Rect(-10f, -10f, 340f, 200f, FadeColor * Fade); + } + EndSpritebatch(); } } } \ No newline at end of file diff --git a/Celeste.Mod.mm/Patches/Parallax.cs b/Celeste.Mod.mm/Patches/Parallax.cs index 099d693bd..65576eed2 100644 --- a/Celeste.Mod.mm/Patches/Parallax.cs +++ b/Celeste.Mod.mm/Patches/Parallax.cs @@ -1,9 +1,11 @@ #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value +#pragma warning disable CS0626 // Method, operator, or accessor is marked external and has no attributes on it using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Monocle; using System; +using System.Reflection; namespace Celeste { class patch_Parallax : Parallax { @@ -14,13 +16,15 @@ public patch_Parallax(MTexture texture) : base(texture) { // no-op, ignored by MonoMod } - /// - /// An optimized version of the vanilla Render method. Only works if the SamplerState is set to PointWrap. - /// - /// The Level to render the Parallax to. - public void ImprovedRender(Scene scene) { - if (((patch_MTexture) Texture).IsPacked) { - Render(scene); + public extern void orig_Render(Scene scene); + + // not pretty, but because of SpriteSortMode.Deferred, we can't just check Draw.SpriteBatch.GraphicsDevice.SamplerStates[0], so we need reflection since we can't patch XNA + private static readonly FieldInfo spriteBatchSamplerState = typeof(SpriteBatch).GetField("samplerState", BindingFlags.Instance | BindingFlags.NonPublic); + public override void Render(Scene scene) { + if (((patch_MTexture) Texture).IsPacked // atlas-packed textures do not support wrapping spritebatches and are therefore drawn normally + || spriteBatchSamplerState.GetValue(Draw.SpriteBatch) != SamplerState.PointWrap) { // if Parallax.Render is called from outside BackdropRenderer.Render, it might use a different SamplerState + // in either case, fall back to vanilla rendering + orig_Render(scene); return; } Vector2 camera = ((scene as Level).Camera.Position + CameraOffset).Floor(); @@ -39,6 +43,7 @@ public void ImprovedRender(Scene scene) { if (color.A <= 1) { return; } + // use modulo instead of vanilla's loops, which might be very inefficient for a small looping styleground far offscreen if (LoopX) { position.X = (position.X % Texture.Width - Texture.Width) % Texture.Width; } @@ -55,7 +60,7 @@ public void ImprovedRender(Scene scene) { Rectangle rect = new Rectangle(0, 0, LoopX ? (int) Math.Ceiling(Celeste.GameWidth - position.X) : Texture.Width, LoopY ? (int) Math.Ceiling(Celeste.GameHeight - position.Y) : Texture.Height); - ((patch_MTexture) Texture).Draw(position, Vector2.Zero, color, 1f, 0f, flip, rect); + ((patch_MTexture) Texture).Draw(position, Vector2.Zero, color, 1f, 0f, flip, rect); // take advantage of the PointWrap sampler state to draw in a single draw call } } } \ No newline at end of file From 963b832a661ac3ca7a100fa9567677d6b748417e Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Mon, 26 Sep 2022 02:09:31 +0200 Subject: [PATCH 26/74] Improvements to CustomTextVignette --- .../Mod/Entities/CustomTextVignette.cs | 39 ++++++++++++------- Celeste.Mod.mm/Mod/Meta/MapMeta.cs | 3 ++ Celeste.Mod.mm/Patches/LevelEnter.cs | 11 ++---- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Entities/CustomTextVignette.cs b/Celeste.Mod.mm/Mod/Entities/CustomTextVignette.cs index f03617b78..2c1f6c168 100644 --- a/Celeste.Mod.mm/Mod/Entities/CustomTextVignette.cs +++ b/Celeste.Mod.mm/Mod/Entities/CustomTextVignette.cs @@ -1,4 +1,5 @@ -using FMOD.Studio; +using Celeste.Mod.Meta; +using FMOD.Studio; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Monocle; @@ -7,7 +8,6 @@ namespace Celeste.Mod.Entities { public class CustomTextVignette : Scene { - private const float SFXDuration = 18.683f; public bool CanPause => menu == null; @@ -20,9 +20,10 @@ public class CustomTextVignette : Scene { private bool started; private bool exiting; - private float timer; private float fade; private float pauseFade; + private float initialDelay; + private float finalDelay; private FancyText.Text text; private int textStart; @@ -32,28 +33,35 @@ public class CustomTextVignette : Scene { private HudRenderer renderer; private HiresSnow snow; - public CustomTextVignette(Session session, string text, HiresSnow snow = null) { + public CustomTextVignette(Session session, MapMetaTextVignette meta, HiresSnow snow = null) { this.session = session; areaMusic = session.Audio.Music.Event; session.Audio.Music.Event = null; session.Audio.Apply(); - sfx = Audio.Play(SFX.music_prologue_intro_vignette); + sfx = Audio.Play(meta.Audio); if (snow == null) { fade = 1f; snow = new HiresSnow(); } + snow.Direction = meta.SnowDirection; Add(renderer = new HudRenderer()); Add(this.snow = snow); RendererList.UpdateLists(); - this.text = FancyText.Parse(Dialog.Get(text), 960, 8, 0f); + initialDelay = meta.InitialDelay; + finalDelay = meta.FinalDelay; + + text = FancyText.Parse(Dialog.Get(meta.Dialog), 960, 8, 0f); textCoroutine = new Coroutine(TextSequence()); } + public CustomTextVignette(Session session, string text, HiresSnow snow = null) // maintain interface for backwards compatibility + : this(session, new MapMetaTextVignette {Dialog = text}, snow) { } + private IEnumerator TextSequence() { - yield return 3f; + yield return initialDelay; while (textStart < text.Count) { textAlpha = 1f; @@ -78,6 +86,12 @@ private IEnumerator TextSequence() { textStart = text.GetNextPageStart(textStart); yield return 0.5f; } + if (finalDelay > 0) { + yield return finalDelay; + } + if (!started) { + StartGame(); + } textStart = int.MaxValue; } @@ -88,12 +102,7 @@ public override void Update() { if (textCoroutine != null && textCoroutine.Active) { textCoroutine.Update(); } - - timer += Engine.DeltaTime; - if (timer >= SFXDuration && !started) { - StartGame(); - } - if (timer < (SFXDuration - 2f) && menu == null && (Input.Pause.Pressed || Input.ESC.Pressed)) { + if (menu == null && (Input.Pause.Pressed || Input.ESC.Pressed)) { Input.Pause.ConsumeBuffer(); Input.ESC.ConsumeBuffer(); OpenMenu(); @@ -175,11 +184,11 @@ public override void Render() { Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.LinearClamp, null, RasterizerState.CullNone, null, Engine.ScreenMatrix); if (fade > 0f) { - Draw.Rect(-1f, -1f, 1922f, 1082f, Color.Black * fade); + Draw.Rect(-1f, -1f, Celeste.TargetWidth + 2f, Celeste.TargetHeight + 2f, Color.Black * fade); } if (textStart < text.Nodes.Count && textAlpha > 0f) { - text.Draw(new Vector2(1920f, 1080f) * 0.5f, new Vector2(0.5f, 0.5f), Vector2.One, textAlpha * (1f - pauseFade), textStart); + text.DrawJustifyPerLine(new Vector2(Celeste.TargetWidth, Celeste.TargetHeight) * 0.5f, new Vector2(0.5f, 0.5f), Vector2.One, textAlpha * (1f - pauseFade), textStart); } Draw.SpriteBatch.End(); diff --git a/Celeste.Mod.mm/Mod/Meta/MapMeta.cs b/Celeste.Mod.mm/Mod/Meta/MapMeta.cs index aeeec44aa..fd8a14f41 100644 --- a/Celeste.Mod.mm/Mod/Meta/MapMeta.cs +++ b/Celeste.Mod.mm/Mod/Meta/MapMeta.cs @@ -510,6 +510,9 @@ public class MapMetaCompleteScreenTitle { public class MapMetaTextVignette { public string Dialog { get; set; } + public string Audio { get; set; } = SFX.music_prologue_intro_vignette; // for backwards compatibility reasons, default to prologue audio if not specified + public float InitialDelay { get; set; } = 3; + public float FinalDelay { get; set; } [YamlIgnore] public Vector2 SnowDirection => SnowDirectionArray.ToVector2() ?? Vector2.UnitY; //Snowing downwards by default [YamlMember(Alias = "SnowDirection")] public float[] SnowDirectionArray { get; set; } } diff --git a/Celeste.Mod.mm/Patches/LevelEnter.cs b/Celeste.Mod.mm/Patches/LevelEnter.cs index fcbc62663..b41a4fd18 100644 --- a/Celeste.Mod.mm/Patches/LevelEnter.cs +++ b/Celeste.Mod.mm/Patches/LevelEnter.cs @@ -64,14 +64,11 @@ public static bool PlayCustomVignette(Session session, bool fromSaveData) { Engine.Scene = new CustomScreenVignette(session, meta: screen); return true; } else if (playVignette && (text = area.GetMeta()?.LoadingVignetteText) != null && text.Dialog != null) { - HiresSnow snow = null; - if (Engine.Scene is Overworld) - snow = (Engine.Scene as Overworld).Snow; - - if (snow != null && text.SnowDirection != null) - snow.Direction = text.SnowDirection; + if (Engine.Scene is not Overworld {Snow: HiresSnow snow}) { + snow = null; + } - Engine.Scene = new CustomTextVignette(session, text.Dialog, snow); + Engine.Scene = new CustomTextVignette(session, text, snow); return true; } From 6c55ef2adc451197d07e5c08c00d4c0df2728298 Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Mon, 26 Sep 2022 02:20:57 +0200 Subject: [PATCH 27/74] Fix whitespace --- Celeste.Mod.mm/Mod/Entities/CustomTextVignette.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Entities/CustomTextVignette.cs b/Celeste.Mod.mm/Mod/Entities/CustomTextVignette.cs index 2c1f6c168..3d5843975 100644 --- a/Celeste.Mod.mm/Mod/Entities/CustomTextVignette.cs +++ b/Celeste.Mod.mm/Mod/Entities/CustomTextVignette.cs @@ -8,7 +8,6 @@ namespace Celeste.Mod.Entities { public class CustomTextVignette : Scene { - public bool CanPause => menu == null; private Session session; @@ -33,7 +32,7 @@ public class CustomTextVignette : Scene { private HudRenderer renderer; private HiresSnow snow; - public CustomTextVignette(Session session, MapMetaTextVignette meta, HiresSnow snow = null) { + public CustomTextVignette(Session session, MapMetaTextVignette meta, HiresSnow snow = null) { this.session = session; areaMusic = session.Audio.Music.Event; session.Audio.Music.Event = null; From d9049181244e537ef161afc59600a8f6a520b958 Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Mon, 26 Sep 2022 05:24:26 +0200 Subject: [PATCH 28/74] Make default vignette snow direction match vanilla --- Celeste.Mod.mm/Mod/Meta/MapMeta.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Celeste.Mod.mm/Mod/Meta/MapMeta.cs b/Celeste.Mod.mm/Mod/Meta/MapMeta.cs index fd8a14f41..62c737fc3 100644 --- a/Celeste.Mod.mm/Mod/Meta/MapMeta.cs +++ b/Celeste.Mod.mm/Mod/Meta/MapMeta.cs @@ -513,7 +513,7 @@ public class MapMetaTextVignette { public string Audio { get; set; } = SFX.music_prologue_intro_vignette; // for backwards compatibility reasons, default to prologue audio if not specified public float InitialDelay { get; set; } = 3; public float FinalDelay { get; set; } - [YamlIgnore] public Vector2 SnowDirection => SnowDirectionArray.ToVector2() ?? Vector2.UnitY; //Snowing downwards by default + [YamlIgnore] public Vector2 SnowDirection => SnowDirectionArray.ToVector2() ?? -Vector2.UnitX; //Snowing to the left by default [YamlMember(Alias = "SnowDirection")] public float[] SnowDirectionArray { get; set; } } From 5e64bc005be2a613c881e351ad7ab5b63748d90c Mon Sep 17 00:00:00 2001 From: Vexatos Date: Tue, 18 Oct 2022 12:24:30 +0200 Subject: [PATCH 29/74] Start music early if CassetteSong metadata exists and LeadBeats is 0 --- .../Patches/CassetteBlockManager.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Celeste.Mod.mm/Patches/CassetteBlockManager.cs b/Celeste.Mod.mm/Patches/CassetteBlockManager.cs index b7b02edb1..6906345f0 100644 --- a/Celeste.Mod.mm/Patches/CassetteBlockManager.cs +++ b/Celeste.Mod.mm/Patches/CassetteBlockManager.cs @@ -56,6 +56,30 @@ public override void Awake(Scene scene) { } } + [MonoModLinkTo("Monocle.Entity", "Update")] + [MonoModIgnore] + public extern void base_Update(); + [MonoModReplace] + public override void Update() { + base_Update(); + if (isLevelMusic) { + sfx = Audio.CurrentMusicEventInstance; + } + + if (sfx == null && !isLevelMusic) { + string cassetteSong = AreaData.Areas[SceneAs().Session.Area.ID].CassetteSong; + sfx = Audio.CreateInstance(cassetteSong); + Audio.Play("event:/game/general/cassette_block_switch_2"); + + if (leadBeats == 0) { + beatIndex = 0; + sfx?.start(); + } + } else { + AdvanceMusic(Engine.DeltaTime * tempoMult); + } + } + [MonoModReplace] public new void AdvanceMusic(float time) { beatTimer += time; From 4d83195e981657d9c5da9f5f860b778e5a67dcee Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Wed, 26 Oct 2022 20:38:13 +0200 Subject: [PATCH 30/74] Allow reskinning chapter panel tab and related textures --- Celeste.Mod.mm/Patches/OuiChapterPanel.cs | 134 +++++++++++++++++++++- 1 file changed, 129 insertions(+), 5 deletions(-) diff --git a/Celeste.Mod.mm/Patches/OuiChapterPanel.cs b/Celeste.Mod.mm/Patches/OuiChapterPanel.cs index baaa4aad1..72c09b885 100644 --- a/Celeste.Mod.mm/Patches/OuiChapterPanel.cs +++ b/Celeste.Mod.mm/Patches/OuiChapterPanel.cs @@ -1,4 +1,5 @@ #pragma warning disable CS0626 // Method, operator, or accessor is marked external and has no attributes on it +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value using Celeste.Mod.UI; using Microsoft.Xna.Framework; @@ -17,7 +18,8 @@ namespace Celeste { class patch_OuiChapterPanel : OuiChapterPanel { - private bool instantClose = false; + private bool instantClose; + private List