From 1ccc3be9718dbb7a5943a9aba59661657cbfab12 Mon Sep 17 00:00:00 2001 From: SilverDorian46 <86711559+SilverDorian46@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:31:44 +0400 Subject: [PATCH] Make SpriteBank.orig_ctor throw a descriptive error if an element has `copy` but no `path` --- Celeste.Mod.mm/Patches/Monocle/SpriteBank.cs | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/Celeste.Mod.mm/Patches/Monocle/SpriteBank.cs b/Celeste.Mod.mm/Patches/Monocle/SpriteBank.cs index 684feed9b..357686044 100644 --- a/Celeste.Mod.mm/Patches/Monocle/SpriteBank.cs +++ b/Celeste.Mod.mm/Patches/Monocle/SpriteBank.cs @@ -1,7 +1,11 @@ #pragma warning disable CS0626 // Method, operator, or accessor is marked external and has no attributes on it using Celeste.Mod; +using Mono.Cecil; using MonoMod; +using MonoMod.Cil; +using MonoMod.InlineRT; +using MonoMod.Utils; using System; using System.Collections.Generic; using System.IO; @@ -22,6 +26,7 @@ public patch_SpriteBank(Atlas atlas, XmlDocument xml) } // Patching constructors is ugly. + [PatchSpriteBankCtor] public extern void orig_ctor(Atlas atlas, XmlDocument xml); [MonoModConstructor] public void ctor(Atlas atlas, XmlDocument xml) { @@ -207,3 +212,75 @@ public static string GetXMLPath(this SpriteBank self) } } + +namespace MonoMod { + /// + /// Patch SpriteBank's orig_ctor to throw a more descriptive error in the case of a sprite having `copy` but no `path` + /// + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchSpriteBankCtor))] + class PatchSpriteBankCtorAttribute : Attribute { } + + static partial class MonoModRules { + public static void PatchSpriteBankCtor(ILContext il, CustomAttribute attrib) { + TypeDefinition t_Calc = MonoModRule.Modder.Module.GetType("Monocle.Calc"); + MethodReference m_Calc_HasAttr = t_Calc.FindMethod("HasAttr"); + + TypeDefinition t_XmlNode = MonoModRule.Modder.FindType("System.Xml.XmlNode").Resolve(); + MethodReference m_XmlNode_get_Name = MonoModRule.Modder.Module.ImportReference( + t_XmlNode.FindMethod("get_Name") + ); + + TypeDefinition t_String = MonoModRule.Modder.FindType("System.String").Resolve(); + MethodReference m_String_Concat_string_string_string = MonoModRule.Modder.Module.ImportReference( + t_String.FindMethod("System.String Concat(System.String,System.String,System.String)") + ); + + TypeDefinition t_Exception = MonoModRule.Modder.FindType("System.Exception").Resolve(); + MethodReference m_Exception_ctor_string = MonoModRule.Modder.Module.ImportReference( + t_Exception.FindMethod("System.Void .ctor(System.String)") + ); + + var cursor = new ILCursor(il); + + /* + foreach (object child in XML["Sprites"].ChildNodes) { + if (child is XmlElement) { + var xmlElement = child as XmlElement; + [...] + + if (xmlElement.HasAttr("copy")) { + // Insert... + <-- if (!xmlElement.HasAttr("path")) + <-- throw new Exception("Sprite '" + xmlElement.Name + "': 'path' is missing!"); + + spriteData.Add(dictionary[xmlElement.Attr("copy")], xmlElement.Attr("path")); + } + + [...] + } + } + */ + + cursor.GotoNext(instr => instr.MatchLdstr("copy")); + cursor.GotoNext(MoveType.After, instr => instr.MatchBrfalse(out _)); + cursor.MoveAfterLabels(); + + ILLabel hasPathLabel = cursor.DefineLabel(); + + cursor.EmitLdloc3(); // local 3 is the current XmlElement + cursor.EmitLdstr("path"); + cursor.EmitCall(m_Calc_HasAttr); + cursor.EmitBrtrue(hasPathLabel); + + cursor.EmitLdstr("Sprite '"); + cursor.EmitLdloc3(); + cursor.EmitCallvirt(m_XmlNode_get_Name); + cursor.EmitLdstr("': 'path' is missing!"); + cursor.EmitCall(m_String_Concat_string_string_string); + cursor.EmitNewobj(m_Exception_ctor_string); + cursor.EmitThrow(); + + cursor.MarkLabel(hasPathLabel); + } + } +}