diff --git a/UndertaleModCli/Program.UMTLibInherited.cs b/UndertaleModCli/Program.UMTLibInherited.cs index 4bfa19006..507d204e1 100644 --- a/UndertaleModCli/Program.UMTLibInherited.cs +++ b/UndertaleModCli/Program.UMTLibInherited.cs @@ -1024,6 +1024,7 @@ public void ReassignGUIDs(string guid, uint objectIndex) public uint ReduceCollisionValue(List possibleValues) { + this.ScriptMessage("haha dumbass"); if (possibleValues.Count == 1) { if (possibleValues[0] != uint.MaxValue) diff --git a/UndertaleModCli/Program.cs b/UndertaleModCli/Program.cs index 59bbb8421..f8261801d 100644 --- a/UndertaleModCli/Program.cs +++ b/UndertaleModCli/Program.cs @@ -341,15 +341,8 @@ private static int Dump(DumpOptions options) return EXIT_FAILURE; } - if (program.Data.IsYYC()) - { - Console.WriteLine("The game was made with YYC (YoYo Compiler), which means that the code was compiled into the executable. " + - "There is thus no code to dump. Exiting."); - return EXIT_SUCCESS; - } - // If user provided code to dump, dump code - if ((options.Code?.Length > 0) && (program.Data.Code?.Count > 0)) + if ((options.Code?.Length > 0) && (program.Data.Code.Count > 0)) { // If user wanted to dump everything, do that, otherwise only dump what user provided string[] codeArray; @@ -579,17 +572,8 @@ private void CliQuickInfo() Console.WriteLine($"{Data.Paths.Count} Paths, {Data.Scripts.Count} Scripts, {Data.Shaders.Count} Shaders"); Console.WriteLine($"{Data.Fonts.Count} Fonts, {Data.Timelines.Count} Timelines, {Data.GameObjects.Count} Game Objects"); Console.WriteLine($"{Data.Rooms.Count} Rooms, {Data.Extensions.Count} Extensions, {Data.TexturePageItems.Count} Texture Page Items"); - if (!Data.IsYYC()) - { - Console.WriteLine($"{Data.Code.Count} Code Entries, {Data.Variables.Count} Variables, {Data.Functions.Count} Functions"); - Console.WriteLine($"{Data.CodeLocals.Count} Code locals, {Data.Strings.Count} Strings, {Data.EmbeddedTextures.Count} Embedded Textures"); - } - else - { - Console.WriteLine("Unknown amount of Code entries and Code locals"); - } - Console.WriteLine($"{Data.Strings.Count} Strings"); - Console.WriteLine($"{Data.EmbeddedTextures.Count} Embedded Textures"); + Console.WriteLine($"{Data.Code.Count} Code Entries, {Data.Variables.Count} Variables, {Data.Functions.Count} Functions"); + Console.WriteLine($"{Data.CodeLocals.Count} Code locals, {Data.Strings.Count} Strings, {Data.EmbeddedTextures.Count} Embedded Textures"); Console.WriteLine($"{Data.EmbeddedAudio.Count} Embedded Audio"); if (IsInteractive) Pause(); diff --git a/UndertaleModLib/Compiler/AssemblyWriter.cs b/UndertaleModLib/Compiler/AssemblyWriter.cs index e1e0f9c65..caf613e92 100644 --- a/UndertaleModLib/Compiler/AssemblyWriter.cs +++ b/UndertaleModLib/Compiler/AssemblyWriter.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Xml.Linq; using UndertaleModLib.Decompiler; using UndertaleModLib.Models; using static UndertaleModLib.Models.UndertaleInstruction; @@ -27,7 +28,7 @@ public class CodeWriter public List varPatches = new List(); public List funcPatches = new List(); public List stringPatches = new List(); - + public List currentArgsNames = new List(); public CodeWriter(CompileContext context) { compileContext = context; @@ -190,7 +191,7 @@ bool hasLocal(string name) patch.Target.Destination = new Reference(def, patch.VarType); else patch.Target.Value = new Reference(def, patch.VarType); - + if (patch.VarType == VariableType.Normal) patch.Target.TypeInst = InstanceType.Local; else if (CompileContext.GMS2_3) @@ -227,7 +228,7 @@ bool hasLocal(string name) compileContext.BuiltInList.GlobalArray.ContainsKey(patch.Name) || compileContext.BuiltInList.GlobalNotArray.ContainsKey(patch.Name) || compileContext.BuiltInList.Instance.ContainsKey(patch.Name) || - compileContext.BuiltInList.InstanceLimitedEvent.ContainsKey(patch.Name), + compileContext.BuiltInList.InstanceLimitedEvent.ContainsKey(patch.Name), compileContext.Data.Strings, compileContext.Data); if (patch.Target.Kind == Opcode.Pop) patch.Target.Destination = new Reference(def, patch.VarType); @@ -279,11 +280,10 @@ bool hasLocal(string name) NameStringID = childNameIndex, Autogenerated = true }; - compileContext.Data.Functions.Add(childFunction); compileContext.Data.KnownSubFunctions.Add(patch.Name, childFunction); - + continue; } @@ -343,7 +343,7 @@ bool hasLocal(string name) { patch.Target.Value = new UndertaleResourceById( compileContext.Data.Strings[ind], ind); - } + } else { UndertaleString newString = new UndertaleString(patch.Content); @@ -655,7 +655,7 @@ private static void AssembleStatement(CodeWriter cw, Parser.Statement s, int rem conditionPatch.Finish(cw); AssembleStatement(cw, s.Children[2]); // else statement elsePatch.Finish(cw); - } + } else { conditionPatch.Finish(cw); @@ -725,7 +725,7 @@ private static void AssembleStatement(CodeWriter cw, Parser.Statement s, int rem AssemblyWriterError(cw, "Malformed repeat loop.", s.Token); break; } - + // This loop keeps its counter on the stack AssembleExpression(cw, s.Children[0]); // number of times to repeat @@ -875,7 +875,7 @@ private static void AssembleStatement(CodeWriter cw, Parser.Statement s, int rem if (shouldAdd) alreadyUsed.Add(c); } - + // Write each case statement's code! for (int i = 0; i < cases.Count; i++) { @@ -1033,7 +1033,7 @@ private static void AssembleStatement(CodeWriter cw, Parser.Statement s, int rem if (oc.Kind == OtherContext.ContextKind.Switch) { cw.Emit(Opcode.Popz, oc.TypeToPop); - } + } else { // With @@ -1054,7 +1054,7 @@ private static void AssembleStatement(CodeWriter cw, Parser.Statement s, int rem }); } cw.Emit(Opcode.Ret, DataType.Variable); - } + } else { // Returns nothing, basically the same as exit @@ -1085,7 +1085,7 @@ private static void AssembleOperationAssign(CodeWriter cw, Parser.Statement s, O // Right AssembleExpression(cw, s.Children[2]); var type = cw.typeStack.Pop(); - if ((needsToBeIntOrLong && type != DataType.Int32 && type != DataType.Int64) + if ((needsToBeIntOrLong && type != DataType.Int32 && type != DataType.Int64) || (!needsToBeIntOrLong && type == DataType.Boolean)) { cw.Emit(Opcode.Conv, type, DataType.Int32); @@ -1094,13 +1094,13 @@ private static void AssembleOperationAssign(CodeWriter cw, Parser.Statement s, O // Actual operation cw.Emit(op, type, DataType.Variable); - + // Store back, using duplicate reference if necessary AssembleStoreVariable(cw, s.Children[0], DataType.Variable, !isSingle, true); } private static void AssemblePostOrPre(CodeWriter cw, Parser.Statement s, bool isPost, bool isExpression) - { + { // Variable to operate on, duplicated second-to-last variable if necessary bool isSingle; bool isArray; @@ -1249,14 +1249,6 @@ private static void AssembleExpression(CodeWriter cw, Parser.Statement e, Parser cw.Emit(Opcode.Push, DataType.Int64).Value = value.valueInt64; cw.typeStack.Push(DataType.Int64); break; - case Parser.ExpressionConstant.Kind.Reference: - { - var instr = cw.Emit(Opcode.Break, DataType.Int32); - instr.Value = (short)-11; // pushref - instr.IntArgument = (int)value.valueNumber; - cw.typeStack.Push(DataType.Variable); - break; - } default: cw.typeStack.Push(DataType.Variable); AssemblyWriterError(cw, "Invalid constant type.", e.Token); @@ -1278,7 +1270,7 @@ private static void AssembleExpression(CodeWriter cw, Parser.Statement e, Parser AssemblyWriterError(cw, "Malformed function assignment.", e.Token); break; } - + Patch startPatch = Patch.StartHere(cw); Patch endPatch = Patch.Start(); endPatch.Add(cw.Emit(Opcode.B)); @@ -1289,7 +1281,7 @@ private static void AssembleExpression(CodeWriter cw, Parser.Statement e, Parser var func = cw.compileContext.Data.Functions.FirstOrDefault(f => f.Name.Content == "gml_Script_" + funcDefName.Text); if (func != null) cw.compileContext.Data.KnownSubFunctions.TryAdd(funcDefName.Text, func); - + if (cw.compileContext.Data.KnownSubFunctions.ContainsKey(funcDefName.Text)) { string subFunctionName = cw.compileContext.Data.KnownSubFunctions[funcDefName.Text].Name.Content; @@ -1308,9 +1300,18 @@ private static void AssembleExpression(CodeWriter cw, Parser.Statement e, Parser isNewFunc = true }); } - cw.loopContexts.Push(new LoopContext(endPatch, startPatch)); + + List argsList = new(); + foreach (var arg in e.Children[0].Children) + { + argsList.Add(arg.Text); + } + + var oldArgsNames = cw.currentArgsNames; + cw.currentArgsNames = argsList; AssembleStatement(cw, e.Children[1]); // body + cw.currentArgsNames = oldArgsNames; AssembleExit(cw); cw.loopContexts.Pop(); endPatch.Finish(cw); @@ -1360,7 +1361,7 @@ private static void AssembleExpression(CodeWriter cw, Parser.Statement e, Parser if (isAnd) { branchPatch.Add(cw.Emit(Opcode.Bf)); - } + } else { branchPatch.Add(cw.Emit(Opcode.Bt)); @@ -1380,7 +1381,7 @@ private static void AssembleExpression(CodeWriter cw, Parser.Statement e, Parser if (isAnd) { cw.Emit(Opcode.Push, DataType.Int16).Value = (short)0; - } + } else { cw.Emit(Opcode.Push, DataType.Int16).Value = (short)1; @@ -1389,7 +1390,7 @@ private static void AssembleExpression(CodeWriter cw, Parser.Statement e, Parser return; } - + for (int i = 1; i < e.Children.Count; i++) { // Push the next value to the stack @@ -1786,14 +1787,22 @@ private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out switch (id) { case -1: - if (cw.compileContext.BuiltInList.GlobalArray.ContainsKey(name) || cw.compileContext.BuiltInList.GlobalNotArray.ContainsKey(name)) + if (cw.compileContext.BuiltInList.GlobalArray.ContainsKey(name) + || cw.compileContext.BuiltInList.GlobalNotArray.ContainsKey(name) + || cw.currentArgsNames.Contains(name)) { + if (name.In(cw.currentArgsNames.ToArray())) + { + int index = cw.currentArgsNames.IndexOf(name); + name = "argument" + index; + } + if (CompileContext.GMS2_3 && - name.In( - "argument0", "argument1", "argument2", "argument3", - "argument4", "argument5", "argument6", "argument7", - "argument8", "argument9", "argument10", "argument11", - "argument12", "argument13", "argument14", "argument15")) + (name.In( + "argument0", "argument1", "argument2", "argument3", + "argument4", "argument5", "argument6", "argument7", + "argument8", "argument9", "argument10", "argument11", + "argument12", "argument13", "argument14", "argument15"))) { // 2.3 argument (excuse the condition... the ID seems to be lost, so this is the easiest way to check) cw.varPatches.Add(new VariablePatch() @@ -1919,7 +1928,7 @@ private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out } } } - } + } else if (e.Kind == Parser.Statement.StatementKind.ExprSingleVariable) { // Assume local or self if necessary. Global doesn't apply here @@ -2084,8 +2093,18 @@ private static void AssembleStoreVariable(CodeWriter cw, Parser.Statement s, Dat int id = s.Children[0].ID; if (id >= 100000) id -= 100000; - if (CompileContext.GMS2_3 && (cw.compileContext.BuiltInList.GlobalArray.ContainsKey(s.Children[0].Text) || cw.compileContext.BuiltInList.GlobalNotArray.ContainsKey(s.Children[0].Text))) + if (CompileContext.GMS2_3 + && (cw.compileContext.BuiltInList.GlobalArray.ContainsKey(s.Children[0].Text) + || cw.compileContext.BuiltInList.GlobalNotArray.ContainsKey(s.Children[0].Text) + || cw.currentArgsNames.Contains(s.Children[0].Text)) + ) { + if (s.Children[0].Text.In(cw.currentArgsNames.ToArray())) + { + int index = cw.currentArgsNames.IndexOf(s.Children[0].Text); + s.Children[0].Text = "argument" + index; + } + if (s.Children[0].Text.In( "argument0", "argument1", "argument2", "argument3", "argument4", "argument5", "argument6", "argument7", @@ -2182,7 +2201,7 @@ private static void AssembleStoreVariable(CodeWriter cw, Parser.Statement s, Dat if (cw.compileContext.LocalVars.ContainsKey(variableName)) { fix2.ID = -7; // local - } + } else if (cw.compileContext.GlobalVars.ContainsKey(variableName)) { fix2.ID = -5; // global @@ -2222,4 +2241,4 @@ private static void AssemblyWriterError(CodeWriter cw, string msg, Lexer.Token c } } } -} +} \ No newline at end of file diff --git a/UndertaleModLib/Compiler/Compiler.cs b/UndertaleModLib/Compiler/Compiler.cs index 3fa2ae6b8..e93c1d5e9 100644 --- a/UndertaleModLib/Compiler/Compiler.cs +++ b/UndertaleModLib/Compiler/Compiler.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using UndertaleModLib.Models; using static UndertaleModLib.Compiler.Compiler.AssemblyWriter; -using AssetRefType = UndertaleModLib.Decompiler.Decompiler.ExpressionAssetRef.RefType; namespace UndertaleModLib.Compiler { @@ -18,10 +17,10 @@ public class CompileContext public bool ensureFunctionsDefined = true; public bool ensureVariablesDefined = true; public static bool GMS2_3; - public bool TypedAssetRefs => Data.IsVersionAtLeast(2023, 8); public int LastCompiledArgumentCount = 0; public Dictionary LocalVars = new Dictionary(); public Dictionary GlobalVars = new Dictionary(); + //public Dictionary> LocalArgs = new(); public Dictionary> Enums = new Dictionary>(); public UndertaleCode OriginalCode; public IList OriginalReferencedLocalVars; @@ -82,21 +81,18 @@ public void Setup(bool redoAssets = false) private void MakeAssetDictionary() { assetIds.Clear(); - AddAssetsFromList(Data?.GameObjects, AssetRefType.Object); - AddAssetsFromList(Data?.Sprites, AssetRefType.Sprite); - AddAssetsFromList(Data?.Sounds, AssetRefType.Sound); - AddAssetsFromList(Data?.Backgrounds, AssetRefType.Background); - AddAssetsFromList(Data?.Paths, AssetRefType.Path); - AddAssetsFromList(Data?.Fonts, AssetRefType.Font); - AddAssetsFromList(Data?.Timelines, AssetRefType.Timeline); + AddAssetsFromList(Data?.GameObjects); + AddAssetsFromList(Data?.Sprites); + AddAssetsFromList(Data?.Sounds); + AddAssetsFromList(Data?.Backgrounds); + AddAssetsFromList(Data?.Paths); + AddAssetsFromList(Data?.Fonts); + AddAssetsFromList(Data?.Timelines); if (!GMS2_3) - AddAssetsFromList(Data?.Scripts, AssetRefType.Object /* not actually used */); - AddAssetsFromList(Data?.Shaders, AssetRefType.Shader); - AddAssetsFromList(Data?.Rooms, AssetRefType.Room); - AddAssetsFromList(Data?.AudioGroups, AssetRefType.Sound /* apparently? */); - AddAssetsFromList(Data?.AnimationCurves, AssetRefType.AnimCurve); - AddAssetsFromList(Data?.Sequences, AssetRefType.Sequence); - AddAssetsFromList(Data?.ParticleSystems, AssetRefType.ParticleSystem); + AddAssetsFromList(Data?.Scripts); + AddAssetsFromList(Data?.Shaders); + AddAssetsFromList(Data?.Rooms); + AddAssetsFromList(Data?.AudioGroups); scripts.Clear(); if (Data?.Scripts != null) @@ -121,30 +117,15 @@ private void MakeAssetDictionary() } } - private void AddAssetsFromList(IList list, AssetRefType type) where T : UndertaleNamedResource + private void AddAssetsFromList(IList list) where T : UndertaleNamedResource { if (list == null) return; - if (TypedAssetRefs) + for (int i = 0; i < list.Count; i++) { - for (int i = 0; i < list.Count; i++) - { - string name = list[i].Name?.Content; - if (name != null) - { - // Typed asset refs pack their type into the ID - assetIds[name] = (i & 0xffffff) | (((int)type & 0x7f) << 24); - } - } - } - else - { - for (int i = 0; i < list.Count; i++) - { - string name = list[i].Name?.Content; - if (name != null) - assetIds[name] = i; - } + string name = list[i].Name?.Content; + if (name != null) + assetIds[name] = i; } } } @@ -216,4 +197,4 @@ public static CompileContext CompileGMLText(string input, CompileContext context return context; } } -} +} \ No newline at end of file diff --git a/UndertaleModLib/Compiler/Parser.cs b/UndertaleModLib/Compiler/Parser.cs index 9638cc1de..a0449707c 100644 --- a/UndertaleModLib/Compiler/Parser.cs +++ b/UndertaleModLib/Compiler/Parser.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -31,8 +32,7 @@ public enum Kind Number, String, Constant, - Int64, - Reference + Int64 } public ExpressionConstant(double val) @@ -403,7 +403,8 @@ private static void ReportCodeError(string msg, Lexer.Token context, bool synchr if (context.Location != null) { msg += string.Format(" around line {0}, column {1}", context.Location.Line, context.Location.Column); - } else if (context.Kind == TokenKind.EOF) + } + else if (context.Kind == TokenKind.EOF) { msg += " around EOF (end of file)"; } @@ -439,6 +440,7 @@ public static Statement ParseTokens(CompileContext context, List to context.LocalVars["arguments"] = "arguments"; context.GlobalVars.Clear(); context.Enums.Clear(); + //context.LocalArgs.Clear(); hasError = false; // Ensuring an EOF exists @@ -460,7 +462,7 @@ public static Statement ParseTokens(CompileContext context, List to else if (tokens[i].Kind == TokenKind.Identifier) { // Convert identifiers into their proper references, at least sort of. - if ((i != 0 && tokens[i - 1].Kind == TokenKind.Dot) || + if ((i != 0 && tokens[i - 1].Kind == TokenKind.Dot) || !ResolveIdentifier(context, tokens[i].Content, out ExpressionConstant constant)) { int ID = GetVariableID(context, tokens[i].Content, out _); @@ -485,7 +487,8 @@ public static Statement ParseTokens(CompileContext context, List to try { val = Convert.ToInt64(t.Content.Substring(t.Content[0] == '$' ? 1 : 2), 16); - } catch (Exception) + } + catch (Exception) { ReportCodeError("Invalid hex literal.", t, false); constant = new ExpressionConstant(0); @@ -672,12 +675,16 @@ private static Statement ParseFunction(CompileContext context) } EnsureTokenKind(TokenKind.OpenParen); - + var i = 0; + //Dictionary argsDict = new(); while (remainingStageOne.Count > 0 && !hasError && !IsNextToken(TokenKind.EOF, TokenKind.CloseParen)) { - Statement expr = ParseExpression(context); - if (expr != null) - args.Children.Add(expr); + var token = EnsureTokenKind(TokenKind.ProcVariable); + if (token == null) + return null; + //argsDict.Add(token.Text, i); + args.Children.Add(new Statement(Statement.StatementKind.Token, token.Text)); + i++; if (!IsNextTokenDiscard(TokenKind.Comma)) { if (!IsNextToken(TokenKind.CloseParen)) @@ -691,7 +698,9 @@ private static Statement ParseFunction(CompileContext context) if (EnsureTokenKind(TokenKind.CloseParen) == null) return null; - result.Children.Add(ParseStatement(context)); + result.Children.Add(ParseStatement(context)); // most likely parses the body. + //context.LocalArgs.Add(result, argsDict); + if (expressionMode) return result; else // Whatever you call non-anonymous definitions @@ -1044,7 +1053,7 @@ private static Statement ParseFunctionCall(CompileContext context, bool expressi if (EnsureTokenKind(TokenKind.CloseParen) == null) return null; // Check for proper argument count, at least for builtins - if (context.BuiltInList.Functions.TryGetValue(s.Text, out FunctionInfo fi) && + if (context.BuiltInList.Functions.TryGetValue(s.Text, out FunctionInfo fi) && fi.ArgumentCount != -1 && result.Children.Count != fi.ArgumentCount) ReportCodeError(string.Format("Function {0} expects {1} arguments, got {2}.", s.Text, fi.ArgumentCount, result.Children.Count) @@ -1107,7 +1116,7 @@ private static Statement ParseOrOp(CompileContext context) { result.Children.Add(ParseAndOp(context)); } - + return result; } else @@ -1339,7 +1348,7 @@ private static Statement ParsePostAndRef(CompileContext context) // Parse chain variable reference Statement result = new Statement(Statement.StatementKind.ExprVariableRef, remainingStageOne.Peek().Token); bool combine = false; - if (left.Kind != Statement.StatementKind.ExprConstant || left.Constant.kind == ExpressionConstant.Kind.Reference /* TODO: will this ever change? */) + if (left.Kind != Statement.StatementKind.ExprConstant) result.Children.Add(left); else combine = true; @@ -1385,7 +1394,7 @@ private static Statement ParseSingleVar(CompileContext context) if (context.BuiltInList.Functions.ContainsKey(s.Text) || context.scripts.Contains(s.Text)) { //ReportCodeError(string.Format("Variable name {0} cannot be used; a function or script already has the name.", s.Text), false); - return new Statement(Statement.StatementKind.ExprFuncName, s.Token) { ID = s.ID }; + return new Statement(Statement.StatementKind.ExprFuncName, s.Token) { ID = s.ID }; } Statement result = new Statement(Statement.StatementKind.ExprSingleVariable, s.Token); @@ -1575,7 +1584,8 @@ public static Statement Optimize(CompileContext context, Statement s) result.Children[i] = Optimize(context, result.Children[i]); } } - } else + } + else result = new Statement(s); Statement child0 = result.Children[0]; @@ -1619,7 +1629,8 @@ public static Statement Optimize(CompileContext context, Statement s) accessorFunc.Children.Insert(0, left); accessorFunc.Children.Add(Optimize(context, result.Children[2])); return accessorFunc; - } else + } + else { // Not the final set function Statement accessorFunc = new Statement(Statement.StatementKind.ExprFunctionCall, ai.RFunc); @@ -1646,7 +1657,8 @@ public static Statement Optimize(CompileContext context, Statement s) { result.Children[i] = Optimize(context, result.Children[i]); } - } else + } + else { for (int i = 0; i < result.Children.Count; i++) { @@ -1935,7 +1947,7 @@ public static Statement Optimize(CompileContext context, Statement s) ReportCodeError("Case argument must be constant.", result.Token, false); } break; - // todo: parse enum references + // todo: parse enum references } return result; } @@ -1950,7 +1962,8 @@ private static AccessorInfo GetAccessorInfoFromStatement(CompileContext context, ai = context.BuiltInList.Accessors1D[kind]; else ReportCodeError("Accessor has incorrect number of arguments", s.Children[0].Token, false); - } else + } + else { if (context.BuiltInList.Accessors2D.ContainsKey(kind)) ai = context.BuiltInList.Accessors2D[kind]; @@ -2708,8 +2721,6 @@ private static bool ResolveIdentifier(CompileContext context, string identifier, } return false; } - if (context.TypedAssetRefs) - constant.kind = ExpressionConstant.Kind.Reference; constant.valueNumber = (double)index; return true; } @@ -2745,4 +2756,4 @@ private static int GetVariableID(CompileContext context, string name, out bool i } } } -} +} \ No newline at end of file diff --git a/UndertaleModLib/Decompiler/Assembler.cs b/UndertaleModLib/Decompiler/Assembler.cs index 5179f5af6..e45662e47 100644 --- a/UndertaleModLib/Decompiler/Assembler.cs +++ b/UndertaleModLib/Decompiler/Assembler.cs @@ -23,8 +23,7 @@ public static class Assembler { -7, "setstatic" }, { -8, "savearef" }, { -9, "restorearef" }, - { -10, "chknullish" }, - { -11, "pushref" } + { -10, "chknullish" } }; public static Dictionary NameToBreakID = new Dictionary() { @@ -37,8 +36,7 @@ public static class Assembler { "setstatic", -7 }, { "savearef", -8 }, { "restorearef", -9 }, - { "chknullish", -10 }, - { "pushref", -11 } + { "chknullish", -10 } }; // TODO: Improve the error messages @@ -219,14 +217,7 @@ public static UndertaleInstruction AssembleOne(string source, IList !GlobalContext.Data.IsVersionAtLeast(2023, 8); public DecompileContext(GlobalDecompileContext globalContext, UndertaleCode code, bool computeObject = true) { @@ -75,32 +74,21 @@ public DecompileContext(GlobalDecompileContext globalContext, UndertaleCode code if (code.ParentEntry != null) throw new InvalidOperationException("This code block represents a function nested inside " + code.ParentEntry.Name + " - decompile that instead"); - if (computeObject && globalContext.Data is not null) + if (computeObject && globalContext.Data != null) { // TODO: This is expensive, move it somewhere else as a dictionary // and have it update when events/objects are modified. - - // Currently using for loops on purpose, as foreach has memory issues due to IEnumerable - for (int i = 0; i < globalContext.Data.GameObjects.Count; i++) - { - UndertaleGameObject obj = globalContext.Data.GameObjects[i]; - for (int j = 0; j < obj.Events.Count; j++) - { - var eventList = obj.Events[j]; - for (int k = 0; k < eventList.Count; k++) - { - UndertaleGameObject.Event subEvent = eventList[k]; - for (int l = 0; l < subEvent.Actions.Count; l++) - { - UndertaleGameObject.EventAction ev = subEvent.Actions[l]; - if (ev.CodeId != code) continue; - Object = obj; - return; - } - } - } - } + foreach (var obj in globalContext.Data.GameObjects) + foreach (var event_list in obj.Events) + foreach (var subevent in event_list) + foreach (var ev in subevent.Actions) + if (ev.CodeId == code) + { + Object = obj; + goto LoopEnd; + } } + LoopEnd: return; } #region Struct management @@ -502,7 +490,7 @@ public override string ToString(DecompileContext context) break;*/ } - if ((context.AssetResolutionEnabled || AssetType == AssetIDType.Script) && context.GlobalContext.Data != null && AssetType != AssetIDType.Other) + if (context.GlobalContext.Data != null && AssetType != AssetIDType.Other) { IList assetList = null; switch (AssetType) @@ -587,128 +575,6 @@ internal override AssetIDType DoTypePropagation(DecompileContext context, AssetI } } - // Represents a reference to an asset in the resource tree, used in 2023.8+ only - public class ExpressionAssetRef : Expression - { - // NOTE: Also see generalized "ResourceType" enum. This has slightly differing values, though - public enum RefType - { - Object = 0, - Sprite = 1, - Sound = 2, - Room = 3, - Background = 4, - Path = 5, - // missing 6 - Font = 7, - Timeline = 8, - // missing 9 - Shader = 10, - Sequence = 11, - AnimCurve = 12, - ParticleSystem = 13 - } - - public int AssetIndex; - public RefType AssetRefType; - - public ExpressionAssetRef(int encodedResourceIndex) - { - Type = UndertaleInstruction.DataType.Variable; - - // Break down index - first 24 bits are the ID, the rest is the ref type - AssetIndex = encodedResourceIndex & 0xffffff; - AssetRefType = (RefType)(encodedResourceIndex >> 24); - } - - public ExpressionAssetRef(int resourceIndex, RefType resourceType) - { - Type = UndertaleInstruction.DataType.Variable; - AssetIndex = resourceIndex; - AssetRefType = resourceType; - } - - internal override bool IsDuplicationSafe() - { - return true; - } - - public override Statement CleanStatement(DecompileContext context, BlockHLStatement block) - { - return this; - } - public override string ToString(DecompileContext context) - { - if (context.GlobalContext.Data != null) - { - IList assetList = null; - switch (AssetRefType) - { - case RefType.Sprite: - assetList = (IList)context.GlobalContext.Data.Sprites; - break; - case RefType.Background: - assetList = (IList)context.GlobalContext.Data.Backgrounds; - break; - case RefType.Sound: - assetList = (IList)context.GlobalContext.Data.Sounds; - break; - case RefType.Font: - assetList = (IList)context.GlobalContext.Data.Fonts; - break; - case RefType.Path: - assetList = (IList)context.GlobalContext.Data.Paths; - break; - case RefType.Timeline: - assetList = (IList)context.GlobalContext.Data.Timelines; - break; - case RefType.Room: - assetList = (IList)context.GlobalContext.Data.Rooms; - break; - case RefType.Object: - assetList = (IList)context.GlobalContext.Data.GameObjects; - break; - case RefType.Shader: - assetList = (IList)context.GlobalContext.Data.Shaders; - break; - case RefType.AnimCurve: - assetList = (IList)context.GlobalContext.Data.AnimationCurves; - break; - case RefType.Sequence: - assetList = (IList)context.GlobalContext.Data.Sequences; - break; - case RefType.ParticleSystem: - assetList = (IList)context.GlobalContext.Data.ParticleSystems; - break; - } - - if (assetList != null && AssetIndex >= 0 && AssetIndex < assetList.Count) - return ((UndertaleNamedResource)assetList[AssetIndex]).Name.Content; - } - return $"/* ERROR: missing {AssetRefType} asset, using ID instead */ {AssetIndex}"; - } - internal override AssetIDType DoTypePropagation(DecompileContext context, AssetIDType suggestedType) - { - // Convert type to corresponding AssetIDType equivalent - return AssetRefType switch - { - RefType.Object => AssetIDType.GameObject, - RefType.Sprite => AssetIDType.Sprite, - RefType.Sound => AssetIDType.Sound, - RefType.Room => AssetIDType.Room, - RefType.Background => AssetIDType.Background, - RefType.Path => AssetIDType.Path, - RefType.Font => AssetIDType.Font, - RefType.Timeline => AssetIDType.Timeline, - RefType.Shader => AssetIDType.Shader, - RefType.Sequence => AssetIDType.Sequence, - RefType.AnimCurve => AssetIDType.AnimCurve, - RefType.ParticleSystem => AssetIDType.ParticleSystem, - _ => throw new NotImplementedException($"Missing ref type {AssetRefType}") - }; - } - } - // Represents an expression converted to one of another data type - makes no difference on high-level code. public class ExpressionCast : Expression { @@ -1730,6 +1596,8 @@ cast.Argument is ExpressionConstant constant && return String.Format("{0}({1})", OverridenName != string.Empty ? OverridenName : Function.Name.Content, argumentString.ToString()); } + + } public override Statement CleanStatement(DecompileContext context, BlockHLStatement block) @@ -2718,9 +2586,6 @@ assign.Value is FunctionDefinition funcDef && // Note that this operator peeks from the stack, it does not pop directly. break; - case -11: // GM 2023.8+, pushref - stack.Push(new ExpressionAssetRef(instr.IntArgument)); - break; } } @@ -3475,7 +3340,7 @@ public static Dictionary> ComputeReverseDominators(Dictionary Block b = blockList[i]; - Block[] e; + IEnumerable e; if (b.conditionalExit) { reverseUse2[0] = b.nextBlockTrue; @@ -4054,7 +3919,7 @@ public static void BuildSubFunctionCache(UndertaleData data) { throw new TimeoutException("The building cache process hung.\n" + "The function code entries that didn't manage to decompile:\n" + - String.Join('\n', processingCodeList.Keys) + "\n\n" + + String.Join('\n', processingCodeList.Keys) + "\n\n" + "You should save the game data (if it's necessary) and re-open the app.\n"); } } @@ -4077,13 +3942,8 @@ private static Block DetermineSwitchEnd(Block start, Block end, Block meetPoint) return meetPoint; Queue blocks = new Queue(); - // Preventing the same block and its children from being queued repeatedly - // becomes increasingly important on large switches. The HashSet should give - // good performance while preventing this type of duplication. - HashSet usedBlocks = new HashSet(); blocks.Enqueue(start); - usedBlocks.Add(start); while (blocks.Count > 0) { Block test = blocks.Dequeue(); @@ -4092,16 +3952,10 @@ private static Block DetermineSwitchEnd(Block start, Block end, Block meetPoint) return end; if (test == meetPoint) return meetPoint; - if (!usedBlocks.Contains(test.nextBlockTrue)) - { - blocks.Enqueue(test.nextBlockTrue); - usedBlocks.Add(test.nextBlockTrue); - } - if (!usedBlocks.Contains(test.nextBlockFalse)) - { + + blocks.Enqueue(test.nextBlockTrue); + if (test.nextBlockTrue != test.nextBlockFalse) blocks.Enqueue(test.nextBlockFalse); - usedBlocks.Add(test.nextBlockFalse); - } } diff --git a/UndertaleModLib/Models/UndertaleAnimationCurve.cs b/UndertaleModLib/Models/UndertaleAnimationCurve.cs index 2d46db1cb..35ee81f81 100644 --- a/UndertaleModLib/Models/UndertaleAnimationCurve.cs +++ b/UndertaleModLib/Models/UndertaleAnimationCurve.cs @@ -3,23 +3,14 @@ namespace UndertaleModLib.Models; /// -/// An animation curve entry in a data file. These were introduced in GameMaker 2.3.0 +/// An animation curve entry in a data file. /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleAnimationCurve : UndertaleNamedResource, IDisposable { - /// - /// TODO: unknown - /// public enum GraphTypeEnum : uint { - /// - /// Unknown - /// Unknown0 = 0, - /// - /// Unknown - /// Unknown1 = 1 } @@ -32,10 +23,8 @@ public enum GraphTypeEnum : uint /// The graph type of this animation curve. /// public GraphTypeEnum GraphType { get; set; } - - /// - /// The channels this animation curve has. - /// + + public UndertaleSimpleList Channels { get; set; } /// @@ -110,56 +99,30 @@ public void Dispose() { foreach (Channel channel in Channels) channel?.Dispose(); - } + } Name = null; Channels = null; } - - /// - /// A channel in an animation curve. - /// + [PropertyChanged.AddINotifyPropertyChangedInterface] - public class Channel : UndertaleNamedResource, IDisposable + public class Channel : UndertaleObject, IDisposable { - /// - /// The curve type determines how points flow to each other in a channel. - /// - public enum CurveType : uint + public enum FunctionType : uint { - /// - /// Creates a linear progression between points. - /// Linear = 0, - /// - /// Creates a smooth progression between points using catmull-rom interpolation. - /// Smooth = 1 - // TODO: What about bezier? } - /// public UndertaleString Name { get; set; } - - /// - /// The curve type this channel uses. - /// - public CurveType Curve { get; set; } - - /// - /// TODO: document this - /// + public FunctionType Function { get; set; } public uint Iterations { get; set; } - - /// - /// The points - /// public UndertaleSimpleList Points { get; set; } /// public void Serialize(UndertaleWriter writer) { writer.WriteUndertaleString(Name); - writer.Write((uint)Curve); + writer.Write((uint)Function); writer.Write(Iterations); Points.Serialize(writer); } @@ -168,7 +131,7 @@ public void Serialize(UndertaleWriter writer) public void Unserialize(UndertaleReader reader) { Name = reader.ReadUndertaleString(); - Curve = (CurveType)reader.ReadUInt32(); + Function = (FunctionType)reader.ReadUInt32(); Iterations = reader.ReadUInt32(); Points = reader.ReadUndertaleObject>(); } @@ -197,39 +160,14 @@ public void Dispose() Points = null; } - /// - /// A point which can exist on a . - /// public class Point : UndertaleObject { - /// - /// The X coordinate of this point. GameMaker abbreviates this to "h". - /// public float X; - - /// - /// The Y coordinate of this point. GameMaker abbreviates this to "v". - /// public float Value; - /// - /// The Y position for the first bezier handle. Only used if the Channel is set to Bezier. - /// - public float BezierX0; - - /// - /// The Y position for the first bezier handle. Only used if the Channel is set to Bezier. - /// + public float BezierX0; // Bezier only public float BezierY0; - - /// - /// The X position for the second bezier handle. Only used if the Channel is set to Bezier. - /// public float BezierX1; - - /// - /// The Y position for the second bezier handle. Only used if the Channel is set to Bezier. - /// public float BezierY1; /// diff --git a/UndertaleModLib/Models/UndertaleCode.cs b/UndertaleModLib/Models/UndertaleCode.cs index dbedb4abb..273c3b630 100644 --- a/UndertaleModLib/Models/UndertaleCode.cs +++ b/UndertaleModLib/Models/UndertaleCode.cs @@ -209,9 +209,7 @@ public enum ComparisonType : byte public object Value { get; set; } public Reference Destination { get; set; } public Reference Function { get; set; } - private int _IntegerArgument; - public int JumpOffset { get => _IntegerArgument; set => _IntegerArgument = value; } - public int IntArgument { get => _IntegerArgument; set => _IntegerArgument = value; } + public int JumpOffset { get; set; } public bool JumpOffsetPopenvExitMagic { get; set; } public ushort ArgumentsCount { get; set; } public byte Extra { get; set; } @@ -562,7 +560,6 @@ public void Serialize(UndertaleWriter writer) writer.Write((short)Value); writer.Write((byte)Type1); writer.Write((byte)Kind); - if (Type1 == DataType.Int32) writer.Write(IntArgument); } break; @@ -748,12 +745,6 @@ public void Unserialize(UndertaleReader reader) Value = reader.ReadInt16(); Type1 = (DataType)reader.ReadByte(); if (reader.ReadByte() != (byte)Kind) throw new Exception("really shouldn't happen"); - if (Type1 == DataType.Int32) - { - IntArgument = reader.ReadInt32(); - if (!reader.undertaleData.IsVersionAtLeast(2023, 8)) - reader.undertaleData.SetGMS2Version(2023, 8); - } } break; @@ -780,6 +771,7 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) case InstructionType.DoubleTypeInstruction: case InstructionType.ComparisonInstruction: case InstructionType.GotoInstruction: + case InstructionType.BreakInstruction: reader.Position += 4; break; @@ -825,17 +817,6 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) reader.Position += 8; return 1; // "Function" - case InstructionType.BreakInstruction: - { - reader.Position += 2; - DataType Type1 = (DataType)reader.ReadByte(); - if (Type1 == DataType.Int32) - reader.Position += 5; - else - reader.Position += 1; - break; - } - default: throw new IOException("Unknown opcode " + Kind.ToString().ToUpper(CultureInfo.InvariantCulture)); } @@ -876,7 +857,7 @@ public string ToString(UndertaleCode code, List blocks = null) if (Kind == Opcode.Dup || Kind == Opcode.CallV) { sb.Append(' '); - sb.Append(Extra); + sb.Append(Extra.ToString()); if (Kind == Opcode.Dup) { if ((byte)ComparisonKind != 0) @@ -924,7 +905,7 @@ public string ToString(UndertaleCode code, List blocks = null) { // Special scenario - the swap instruction // TODO: Figure out the proper syntax, see #129 - sb.Append(SwapExtra); + sb.Append(SwapExtra.ToString()); sb.Append(" ;;; this is a weird swap instruction, see #129"); } else @@ -954,7 +935,7 @@ public string ToString(UndertaleCode code, List blocks = null) sb.Append(' '); sb.Append(Function); sb.Append("(argc="); - sb.Append(ArgumentsCount); + sb.Append(ArgumentsCount.ToString()); sb.Append(')'); break; @@ -962,14 +943,9 @@ public string ToString(UndertaleCode code, List blocks = null) sb.Append("." + Type1.ToOpcodeParam()); if (unknownBreak) { - sb.Append(' '); + sb.Append(" "); sb.Append(Value); } - if (Type1 == DataType.Int32) - { - sb.Append(' '); - sb.Append(IntArgument); - } break; } return sb.ToString(); @@ -984,8 +960,6 @@ public uint CalculateInstructionSize() return 3; else if (Type1 != DataType.Int16) return 2; - if (Kind == Opcode.Break && Type1 == DataType.Int32) - return 2; return 1; } } diff --git a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs index 0bc4b1e92..bb893c27e 100644 --- a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs +++ b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs @@ -1,12 +1,14 @@ using ICSharpCode.SharpZipLib.BZip2; using System; using System.Buffers.Binary; +using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Text; using UndertaleModLib.Util; namespace UndertaleModLib.Models; @@ -119,7 +121,7 @@ public void Serialize(UndertaleWriter writer) writer.Write(IndexInGroup); } if (TextureExternal) - writer.Write(0); // Ensure null pointer is written with external texture + writer.Write((int)0); // Ensure null pointer is written with external texture else writer.WriteUndertaleObjectPointer(_textureData); } @@ -244,7 +246,7 @@ public TexData LoadExternalTexture() // Try to find file on disk string path = Path.Combine(_2022_9_GameDirectory, TextureInfo.Directory.Content, - TextureInfo.Name.Content + "_" + IndexInGroup + TextureInfo.Extension.Content); + TextureInfo.Name.Content + "_" + IndexInGroup.ToString() + TextureInfo.Extension.Content); if (!File.Exists(path)) return _placeholderTexture; @@ -351,10 +353,6 @@ public int Height /// public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Invoked whenever the effective value of any dependency property has been updated. - /// protected void OnPropertyChanged([CallerMemberName] string name = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); diff --git a/UndertaleModLib/Models/UndertaleExtension.cs b/UndertaleModLib/Models/UndertaleExtension.cs index aa85bb50d..c944d4ed8 100644 --- a/UndertaleModLib/Models/UndertaleExtension.cs +++ b/UndertaleModLib/Models/UndertaleExtension.cs @@ -108,20 +108,12 @@ public class UndertaleExtensionFunction : UndertaleObject, IDisposable /// An identification number of the function. /// public uint ID { get; set; } - - /// - /// TODO: is this kind the same as extension kind? - /// public uint Kind { get; set; } /// /// The return type of the function. /// public UndertaleExtensionVarType RetType { get; set; } - - /// - /// TODO: The extension of the filename this function belongs to? - /// public UndertaleString ExtName { get; set; } /// @@ -176,35 +168,13 @@ public void Dispose() } } -/// -/// A file that's used in an . -/// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleExtensionFile : UndertaleObject, IDisposable { - /// - /// The filename of this extension file. - /// public UndertaleString Filename { get; set; } - - /// - /// The script name that gets called when the game ends. - /// public UndertaleString CleanupScript { get; set; } - - /// - /// The script name that gets called when the game starts. - /// public UndertaleString InitScript { get; set; } - - /// - /// The type of extension this belongs to. - /// public UndertaleExtensionKind Kind { get; set; } - - /// - /// The functions this file has defined. - /// public UndertalePointerList Functions { get; set; } = new UndertalePointerList(); /// @@ -261,7 +231,7 @@ public void Dispose() { foreach (UndertaleExtensionFunction func in Functions) func?.Dispose(); - } + } Filename = null; CleanupScript = null; InitScript = null; @@ -269,45 +239,22 @@ public void Dispose() } } -/// -/// An option that's used in an . -/// + [PropertyChanged.AddINotifyPropertyChangedInterface] -public class UndertaleExtensionOption : UndertaleNamedResource, IStaticChildObjectsSize, IDisposable +public class UndertaleExtensionOption : UndertaleObject, IStaticChildObjectsSize, IDisposable { /// public static readonly uint ChildObjectsSize = 12; - /// - /// The type of what the option value is. - /// public enum OptionKind : uint { - /// - /// The option value is a boolean- - /// Boolean = 0, - /// - /// The option value is a number. - /// Number = 1, - /// - /// The option value is a string. - /// String = 2 } - /// public UndertaleString Name { get; set; } - - /// - /// The value of this option. - /// public UndertaleString Value { get; set; } - - /// - /// The type of this option. - /// public OptionKind Kind { get; set; } = OptionKind.String; /// @@ -359,25 +306,10 @@ public class UndertaleExtension : UndertaleNamedResource, IDisposable /// The name of the extension. /// public UndertaleString Name { get; set; } - - /// - /// TODO: unknown? - /// public UndertaleString ClassName { get; set; } - - /// - /// The version of the extension. - /// public UndertaleString Version { get; set; } - /// - /// The files that this extension contains. - /// public UndertalePointerList Files { get; set; } = new UndertalePointerList(); - - /// - /// The options that this extension contains. - /// public UndertalePointerList Options { get; set; } = new UndertalePointerList(); /// diff --git a/UndertaleModLib/Models/UndertaleFeatureFlags.cs b/UndertaleModLib/Models/UndertaleFeatureFlags.cs index 4e7e6d0ae..533eb87aa 100644 --- a/UndertaleModLib/Models/UndertaleFeatureFlags.cs +++ b/UndertaleModLib/Models/UndertaleFeatureFlags.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; namespace UndertaleModLib.Models; @@ -8,9 +10,6 @@ namespace UndertaleModLib.Models; [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleFeatureFlags : UndertaleObject, IDisposable { - /// - /// The list of feature flags. - /// public UndertaleSimpleListString List { get; set; } diff --git a/UndertaleModLib/Models/UndertaleFont.cs b/UndertaleModLib/Models/UndertaleFont.cs index 73be58a73..6a2b3fdf1 100644 --- a/UndertaleModLib/Models/UndertaleFont.cs +++ b/UndertaleModLib/Models/UndertaleFont.cs @@ -92,11 +92,6 @@ public class UndertaleFont : UndertaleNamedResource, IDisposable /// 0 if SDF is disabled for this font. public uint SDFSpread { get; set; } - /// - /// Was introduced in GM 2023.6. - /// - public uint LineHeight { get; set; } - /// /// The glyphs that this font uses. /// @@ -297,8 +292,6 @@ public void Serialize(UndertaleWriter writer) writer.Write(Ascender); if (writer.undertaleData.IsVersionAtLeast(2023, 2)) writer.Write(SDFSpread); - if (writer.undertaleData.IsVersionAtLeast(2023, 6)) - writer.Write(LineHeight); writer.WriteUndertaleObject(Glyphs); } @@ -333,8 +326,6 @@ public void Unserialize(UndertaleReader reader) Ascender = reader.ReadUInt32(); if (reader.undertaleData.IsVersionAtLeast(2023, 2)) SDFSpread = reader.ReadUInt32(); - if (reader.undertaleData.IsVersionAtLeast(2023, 6)) - LineHeight = reader.ReadUInt32(); Glyphs = reader.ReadUndertaleObject>(); } @@ -348,8 +339,6 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) skipSize += 4; // Ascender if (reader.undertaleData.IsVersionAtLeast(2023, 2)) skipSize += 4; // SDFSpread - if (reader.undertaleData.IsVersionAtLeast(2023, 6)) - skipSize += 4; // LineHeight reader.Position += skipSize; diff --git a/UndertaleModLib/Models/UndertaleFunction.cs b/UndertaleModLib/Models/UndertaleFunction.cs index ecbf4d4ab..50cb49dc7 100644 --- a/UndertaleModLib/Models/UndertaleFunction.cs +++ b/UndertaleModLib/Models/UndertaleFunction.cs @@ -52,7 +52,7 @@ public void Serialize(UndertaleWriter writer) writer.Write(addr); } else - writer.Write(-1); + writer.Write((int)-1); } /// diff --git a/UndertaleModLib/Models/UndertaleGameObject.cs b/UndertaleModLib/Models/UndertaleGameObject.cs index 34940aee4..65ee498fa 100644 --- a/UndertaleModLib/Models/UndertaleGameObject.cs +++ b/UndertaleModLib/Models/UndertaleGameObject.cs @@ -150,10 +150,6 @@ public class UndertaleGameObject : UndertaleNamedResource, INotifyPropertyChange /// public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Invoked whenever the effective value of any dependency property has been updated. - /// protected void OnPropertyChanged([CallerMemberName] string name = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); @@ -281,7 +277,7 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) public UndertaleCode EventHandlerFor(EventType type, uint subtype, IList strg, IList codelist, IList localslist) { - Event subtypeObj = Events[(int)type].FirstOrDefault(x => x.EventSubtype == subtype); + Event subtypeObj = Events[(int)type].Where((x) => x.EventSubtype == subtype).FirstOrDefault(); if (subtypeObj == null) Events[(int)type].Add(subtypeObj = new Event() { EventSubtype = subtype }); EventAction action = subtypeObj.Actions.FirstOrDefault(); @@ -293,7 +289,7 @@ public UndertaleCode EventHandlerFor(EventType type, uint subtype, IList public event PropertyChangedEventHandler PropertyChanged; /// diff --git a/UndertaleModLib/Models/UndertaleGeneralInfo.cs b/UndertaleModLib/Models/UndertaleGeneralInfo.cs index 107e568fb..f20302958 100644 --- a/UndertaleModLib/Models/UndertaleGeneralInfo.cs +++ b/UndertaleModLib/Models/UndertaleGeneralInfo.cs @@ -591,15 +591,15 @@ public override string ToString() else sb.Append(" (GM "); sb.Append(Major); - sb.Append('.'); + sb.Append("."); sb.Append(Minor); if (Release != 0) { - sb.Append('.'); + sb.Append("."); sb.Append(Release); if (Build != 0) { - sb.Append('.'); + sb.Append("."); sb.Append(Build); } } @@ -608,7 +608,7 @@ public override string ToString() sb.Append(", bytecode "); sb.Append(BytecodeVersion); } - sb.Append(')'); + sb.Append(")"); return sb.ToString(); } } @@ -711,58 +711,44 @@ public enum OptionsFlags : ulong public OptionsFlags Info { get; set; } = OptionsFlags.InterpolatePixels | OptionsFlags.UseNewAudio | OptionsFlags.ShowCursor | OptionsFlags.ScreenKey | OptionsFlags.QuitKey | OptionsFlags.SaveKey | OptionsFlags.ScreenShotKey | OptionsFlags.CloseSec | OptionsFlags.ScaleProgress | OptionsFlags.DisplayErrors | OptionsFlags.VariableErrors | OptionsFlags.CreationEventOrder; /// - /// The window scale. // TODO: is this a legacy gm thing, or still used today? + /// The window scale. /// public int Scale { get; set; } = -1; /// - /// The window color. TODO: unused? Legacy GM remnant? Is this the "Color outside the room region" thing? + /// The window color. TODO: unused? Legacy GM remnant? /// public uint WindowColor { get; set; } = 0; /// - /// The Color depth the game uses. Used only in Game Maker 8 and earlier. + /// The Color depth. TODO: unused? Legacy GM remnant? /// public uint ColorDepth { get; set; } = 0; /// - /// The game's resolution. Used only in Game Maker 8 and earlier. + /// The game's resolution. TODO: unused? Legacy GM remnant? /// public uint Resolution { get; set; } = 0; /// - /// The game's refresh rate. Used only in Game Maker 8 and earlier. + /// The game's refresh rate. TODO: unused? Legacy GM remnant? /// public uint Frequency { get; set; } = 0; /// - /// Whether the game uses V-Sync. Used only in Game Maker 8 and earlier. + /// Whether the game uses V-Sync. TODO: unused? Legacy GM remnant? /// public uint VertexSync { get; set; } = 0; /// - /// The priority of the game process. The higher the number, the more priority will be given to the game. Used only in Game Maker 8 and earlier. + /// TODO: unused? Legacy GM remnant? /// public uint Priority { get; set; } = 0; - - /// - /// The background of the loading bar when loading GameMaker 8 games. - /// + + // Apparently these exist, but I can't find any examples of it. They're also only used in "old format". public UndertaleSprite.TextureEntry BackImage { get; set; } = new UndertaleSprite.TextureEntry(); - - /// - /// The image of the loading bar when loading GameMaker 8 games. - /// public UndertaleSprite.TextureEntry FrontImage { get; set; } = new UndertaleSprite.TextureEntry(); - - /// - /// The image that gets shown when loading GameMaker 8 games. - /// public UndertaleSprite.TextureEntry LoadImage { get; set; } = new UndertaleSprite.TextureEntry(); - - /// - /// The transparency value of . 255 indicates fully opaque, 0 means fully transparent. - /// public uint LoadAlpha { get; set; } = 255; /// @@ -879,7 +865,7 @@ public void Serialize(UndertaleWriter writer) /// public void Unserialize(UndertaleReader reader) { - NewFormat = reader.ReadInt32() == Int32.MinValue; + NewFormat = reader.ReadInt32() == int.MinValue; reader.Position -= 4; if (NewFormat) { @@ -945,7 +931,7 @@ public void Unserialize(UndertaleReader reader) public static uint UnserializeChildObjectCount(UndertaleReader reader) { uint count = 0; - bool newFormat = reader.ReadInt32() == Int32.MinValue; + bool newFormat = reader.ReadInt32() == int.MinValue; reader.Position -= 4; reader.Position += newFormat ? 60u : 140u; @@ -966,7 +952,7 @@ public void Dispose() { foreach (Constant constant in Constants) constant?.Dispose(); - } + } BackImage = new(); FrontImage = new(); LoadImage = new(); diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index 1a6af6958..a1cacfd8d 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -71,12 +71,12 @@ public enum RoomEntryFlags : uint /// /// Whether this room is persistant. /// - public bool Persistent { get; set; } + public bool Persistent { get; set; } = false; /// /// The background color of this room. /// - public uint BackgroundColor { get; set; } + public uint BackgroundColor { get; set; } = 0; /// /// Whether the display buffer should be cleared with Window Color. @@ -172,7 +172,7 @@ public enum RoomEntryFlags : uint /// Calls for in order to update the room background color.
/// Only used for GameMaker: Studio 2 rooms. ///
- public void UpdateBGColorLayer() => OnPropertyChanged(nameof(BGColorLayer)); + public void UpdateBGColorLayer() => OnPropertyChanged("BGColorLayer"); /// /// Checks whether is ordered by . @@ -222,6 +222,7 @@ public void RearrangeLayers(Tuple layerProperties = null) for (int i = 0; i < orderedLayers.Length; i++) { + if (Layers[i] != orderedLayers[i]) Layers[i] = orderedLayers[i]; } } @@ -237,19 +238,16 @@ public Layer BGColorLayer { get { - return (_layers?.Where(l => l.LayerType is LayerType.Background - && l.BackgroundData.Sprite is null - && l.BackgroundData.Color != 0)) - .MinBy(l => l.LayerDepth); + return _layers?.Where(l => l.LayerType is LayerType.Background + && l.BackgroundData.Sprite is null + && l.BackgroundData.Color != 0) + .OrderBy(l => l.LayerDepth) + .FirstOrDefault(); } } /// public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Invoked whenever the effective value of any dependency property has been updated. - /// protected void OnPropertyChanged([CallerMemberName] string name = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); @@ -429,7 +427,7 @@ public void Unserialize(UndertaleReader reader) && layer.InstancesData.InstanceIds[0] > GameObjects[^1].InstanceID) { // Make sure it's not a false positive - uint firstLayerInstID = layer.InstancesData.InstanceIds.MinBy(x => x); + uint firstLayerInstID = layer.InstancesData.InstanceIds.OrderBy(x => x).First(); uint lastInstID = GameObjects.OrderBy(x => x.InstanceID).Last().InstanceID; if (firstLayerInstID > lastInstID) { @@ -1211,7 +1209,7 @@ public UndertaleBackground BackgroundDefinition { _backgroundDefinition.Resource = value; OnPropertyChanged(); - OnPropertyChanged(nameof(ObjectDefinition)); + OnPropertyChanged("ObjectDefinition"); } } @@ -1225,7 +1223,7 @@ public UndertaleSprite SpriteDefinition { _spriteDefinition.Resource = value; OnPropertyChanged(); - OnPropertyChanged(nameof(ObjectDefinition)); + OnPropertyChanged("ObjectDefinition"); } } @@ -1466,7 +1464,7 @@ public void UpdateParentRoom() if (TilesData != null) TilesData.ParentLayer = this; } - public void UpdateZIndex() => OnPropertyChanged(nameof(LayerDepth)); + public void UpdateZIndex() => OnPropertyChanged("LayerDepth"); /// public void Serialize(UndertaleWriter writer) @@ -1651,7 +1649,7 @@ public uint TilesX { Array.Resize(ref _tileData[y], (int)value); } - OnPropertyChanged(nameof(TileData)); + OnPropertyChanged("TileData"); } } } @@ -1668,7 +1666,7 @@ public uint TilesY if (_tileData[y] == null) _tileData[y] = new uint[TilesX]; } - OnPropertyChanged(nameof(TileData)); + OnPropertyChanged("TileData"); } } } @@ -2197,7 +2195,7 @@ public void Unserialize(UndertaleReader reader) public static UndertaleString GenerateRandomName(UndertaleData data) { // The same format as in "GameMaker Studio: 2". - return data.Strings.MakeString("graphic_" + ((uint)Random.Shared.Next(-Int32.MaxValue, Int32.MaxValue)).ToString("X8")); + return data.Strings.MakeString("graphic_" + ((uint)Random.Shared.Next(-int.MaxValue, int.MaxValue)).ToString("X8")); } /// @@ -2357,7 +2355,7 @@ public void Unserialize(UndertaleReader reader) public static UndertaleString GenerateRandomName(UndertaleData data) { - return data.Strings.MakeString("particle_" + ((uint)Random.Shared.Next(-Int32.MaxValue, Int32.MaxValue)).ToString("X8")); + return data.Strings.MakeString("particle_" + ((uint)Random.Shared.Next(-int.MaxValue, int.MaxValue)).ToString("X8")); } /// @@ -2387,6 +2385,6 @@ public static class UndertaleRoomExtensions { public static T ByInstanceID(this IList list, uint instance) where T : UndertaleRoom.IRoomObject { - return list.FirstOrDefault(x => x.InstanceID == instance); + return list.Where((x) => x.InstanceID == instance).FirstOrDefault(); } } \ No newline at end of file diff --git a/UndertaleModLib/Models/UndertaleSequence.cs b/UndertaleModLib/Models/UndertaleSequence.cs index 34c41f766..40612b895 100644 --- a/UndertaleModLib/Models/UndertaleSequence.cs +++ b/UndertaleModLib/Models/UndertaleSequence.cs @@ -249,7 +249,7 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) public class Track : UndertaleObject { - public enum TrackBuiltinName + public enum TrackBuiltinName : int { Gain = 5, Pitch = 6, @@ -273,7 +273,7 @@ public enum TrackBuiltinName } [Flags] - public enum TrackTraits + public enum TrackTraits : int { None, ChildrenIgnoreOrigin @@ -657,7 +657,7 @@ public class Data : ResourceData - public new static uint UnserializeChildObjectCount(UndertaleReader reader) + public static new uint UnserializeChildObjectCount(UndertaleReader reader) { uint count = ResourceData>.UnserializeChildObjectCount(reader); @@ -810,7 +810,7 @@ public override void Unserialize(UndertaleReader reader) } /// - public new static uint UnserializeChildObjectCount(UndertaleReader reader) + public static new uint UnserializeChildObjectCount(UndertaleReader reader) { reader.Position += 4; // "Value" @@ -847,7 +847,7 @@ public override void Unserialize(UndertaleReader reader) } /// - public new static uint UnserializeChildObjectCount(UndertaleReader reader) + public static new uint UnserializeChildObjectCount(UndertaleReader reader) { while (reader.AbsPosition % 4 != 0) reader.Position++; @@ -877,7 +877,7 @@ public override void Unserialize(UndertaleReader reader) } /// - public new static uint UnserializeChildObjectCount(UndertaleReader reader) + public static new uint UnserializeChildObjectCount(UndertaleReader reader) { reader.Position += 4; // "Value" @@ -914,7 +914,7 @@ public override void Unserialize(UndertaleReader reader) } /// - public new static uint UnserializeChildObjectCount(UndertaleReader reader) + public static new uint UnserializeChildObjectCount(UndertaleReader reader) { while (reader.AbsPosition % 4 != 0) reader.Position++; diff --git a/UndertaleModLib/Models/UndertaleShader.cs b/UndertaleModLib/Models/UndertaleShader.cs index 8c7ca0700..425c2c9d5 100644 --- a/UndertaleModLib/Models/UndertaleShader.cs +++ b/UndertaleModLib/Models/UndertaleShader.cs @@ -490,8 +490,16 @@ public class UndertaleRawShaderData : IDisposable internal uint _PointerLocation; internal uint _Length; // note: this is not always an accurate value, use Data.Length if necessary public byte[] Data; - public bool IsNull = true; - + public bool IsNull; + + public UndertaleRawShaderData() + { + _Position = 0; + _PointerLocation = 0; + _Length = 0; + IsNull = true; + } + public void Serialize(UndertaleWriter writer, bool writeLength = true) { _PointerLocation = writer.Position; diff --git a/UndertaleModLib/Models/UndertaleSound.cs b/UndertaleModLib/Models/UndertaleSound.cs index e45940b63..d5cd2bf58 100644 --- a/UndertaleModLib/Models/UndertaleSound.cs +++ b/UndertaleModLib/Models/UndertaleSound.cs @@ -75,7 +75,7 @@ public enum AudioEntryFlags : uint /// /// The pitch change of the audio entry. /// - public float Pitch { get; set; } + public float Pitch { get; set; } = 0; private UndertaleResourceById _audioGroup = new(); private UndertaleResourceById _audioFile = new(); @@ -102,10 +102,6 @@ public enum AudioEntryFlags : uint /// public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Invoked whenever the effective value of any dependency property has been updated. - /// protected void OnPropertyChanged([CallerMemberName] string name = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); diff --git a/UndertaleModLib/Models/UndertaleSprite.cs b/UndertaleModLib/Models/UndertaleSprite.cs index bcad338d2..1ceddbe8f 100644 --- a/UndertaleModLib/Models/UndertaleSprite.cs +++ b/UndertaleModLib/Models/UndertaleSprite.cs @@ -81,7 +81,7 @@ public void Dispose() /// /// Sprite entry in the data file. /// -public class UndertaleSprite : UndertaleNamedResource, PrePaddedObject, INotifyPropertyChanged, IDisposable +public class UndertaleSprite : UndertaleNamedResource, PrePaddedObject, INotifyPropertyChanged { /// /// The name of the sprite. @@ -155,7 +155,7 @@ public class UndertaleSprite : UndertaleNamedResource, PrePaddedObject, INotifyP public int OriginY { get; set; } /// - /// A wrapper that also sets accordingly. + /// A wrapper that also sets accordingly. /// /// /// This attribute is used only in UndertaleModTool and doesn't exist in GameMaker. @@ -173,7 +173,7 @@ public int OriginXWrapper } /// - /// A wrapper that also sets accordingly. + /// A wrapper that also sets accordingly. /// /// /// This attribute is used only in UndertaleModTool and doesn't exist in GameMaker. @@ -379,11 +379,11 @@ public void Serialize(UndertaleWriter writer) if (SVersion >= 2) { sequencePatchPos = writer.Position; - writer.Write(0); + writer.Write((int)0); if (SVersion >= 3) { nineSlicePatchPos = writer.Position; - writer.Write(0); + writer.Write((int)0); } } } @@ -458,7 +458,7 @@ public void Serialize(UndertaleWriter writer) writer.Position = sequencePatchPos; writer.Write(returnTo); writer.Position = returnTo; - writer.Write(1); + writer.Write((int)1); writer.WriteUndertaleObject(V2Sequence); } if (nineSlicePatchPos != 0 && V3NineSlice != null) @@ -1079,7 +1079,7 @@ public void Unserialize(UndertaleReader reader) } } -public enum UndertaleYYSWFItemType +public enum UndertaleYYSWFItemType : int { ItemInvalid, ItemShape, @@ -1174,20 +1174,11 @@ public class UndertaleYYSWFGradientFillData : UndertaleObject public UndertaleYYSWFGradientFillType GradientFillType { get; set; } public UndertaleYYSWFMatrix33 TransformationMatrix { get; set; } public UndertaleSimpleList Records { get; set; } - /// - /// Unknown purpose. Probably to accomodate for new texture formats. - /// - /// - /// Presumably present in GM 2022.1+. - /// - public int? TPEIndex { get; set; } /// public void Serialize(UndertaleWriter writer) { writer.Write((int)GradientFillType); - if (TPEIndex is not null) - writer.Write(TPEIndex.Value); writer.WriteUndertaleObject(TransformationMatrix); writer.WriteUndertaleObject(Records); } @@ -1196,10 +1187,6 @@ public void Serialize(UndertaleWriter writer) public void Unserialize(UndertaleReader reader) { GradientFillType = (UndertaleYYSWFGradientFillType)reader.ReadInt32(); - if (reader.undertaleData.IsVersionAtLeast(2022, 1)) - { - TPEIndex = reader.ReadInt32(); - } TransformationMatrix = reader.ReadUndertaleObject(); Records = new UndertaleSimpleList(); int count = reader.ReadInt32(); @@ -1664,13 +1651,6 @@ public class UndertaleYYSWFBitmapData : UndertaleObject public UndertaleYYSWFBitmapType Type { get; set; } public int Width { get; set; } public int Height { get; set; } - /// - /// Unknown purpose. Probably to accomodate for new texture formats. - /// - /// - /// Presumably present in GM 2022.1+. - /// - public int? TPEIndex { get; set; } public byte[] ImageData { get; set; } public byte[] AlphaData { get; set; } public byte[] ColorPaletteData { get; set; } @@ -1683,25 +1663,18 @@ public void Serialize(UndertaleWriter writer) writer.Write(Width); writer.Write(Height); - if (TPEIndex is null) - { - writer.Write(ImageData is null ? 0 : ImageData.Length); - writer.Write(AlphaData is null ? 0 : AlphaData.Length); - writer.Write(ColorPaletteData is null ? 0 : ColorPaletteData.Length); + writer.Write(ImageData is null ? 0 : ImageData.Length); + writer.Write(AlphaData is null ? 0 : AlphaData.Length); + writer.Write(ColorPaletteData is null ? 0 : ColorPaletteData.Length); - if (ImageData != null) - writer.Write(ImageData); - if (AlphaData != null) - writer.Write(AlphaData); - if (ColorPaletteData != null) - writer.Write(ColorPaletteData); + if (ImageData != null) + writer.Write(ImageData); + if (AlphaData != null) + writer.Write(AlphaData); + if (ColorPaletteData != null) + writer.Write(ColorPaletteData); - writer.Align(4); - } - else - { - writer.Write(TPEIndex.Value); - } + writer.Align(4); } /// @@ -1711,25 +1684,18 @@ public void Unserialize(UndertaleReader reader) Width = reader.ReadInt32(); Height = reader.ReadInt32(); - if (reader.undertaleData.IsVersionAtLeast(2022, 1)) - { - TPEIndex = reader.ReadInt32(); - } - else - { - int iL = reader.ReadInt32(); - int aL = reader.ReadInt32(); - int cL = reader.ReadInt32(); + int iL = reader.ReadInt32(); + int aL = reader.ReadInt32(); + int cL = reader.ReadInt32(); - if (iL > 0) - ImageData = reader.ReadBytes(iL); - if (aL > 0) - AlphaData = reader.ReadBytes(aL); - if (cL > 0) - ColorPaletteData = reader.ReadBytes(cL); + if (iL > 0) + ImageData = reader.ReadBytes(iL); + if (aL > 0) + AlphaData = reader.ReadBytes(aL); + if (cL > 0) + ColorPaletteData = reader.ReadBytes(cL); - reader.Align(4); - } + reader.Align(4); } } @@ -1913,7 +1879,7 @@ public void Serialize(UndertaleWriter writer) public void Unserialize(UndertaleReader reader) { reader.Align(4); - int jpeglen = reader.ReadInt32() & (~Int32.MinValue); // the length is ORed with int.MinValue. + int jpeglen = reader.ReadInt32() & (~int.MinValue); // the length is ORed with int.MinValue. Version = reader.ReadInt32(); Util.DebugUtil.Assert(Version == 8 || Version == 7, "Invalid YYSWF version data! Expected 7 or 8, got " + Version); diff --git a/UndertaleModLib/Models/UndertaleTags.cs b/UndertaleModLib/Models/UndertaleTags.cs index f8ccffe9f..78f11d1a1 100644 --- a/UndertaleModLib/Models/UndertaleTags.cs +++ b/UndertaleModLib/Models/UndertaleTags.cs @@ -29,7 +29,6 @@ public static int GetAssetTagID(UndertaleData data, UndertaleNamedResource resou UndertaleShader => ResourceType.Shader, UndertaleSequence => ResourceType.Sequence, UndertaleAnimationCurve => ResourceType.AnimCurve, - UndertaleParticleSystem => ResourceType.ParticleSystem, _ => throw new ArgumentException("Invalid resource type!") }; IList list = data[resource.GetType()]; diff --git a/UndertaleModLib/Scripting/IScriptInterface.cs b/UndertaleModLib/Scripting/IScriptInterface.cs index e896e4373..32c761638 100644 --- a/UndertaleModLib/Scripting/IScriptInterface.cs +++ b/UndertaleModLib/Scripting/IScriptInterface.cs @@ -1,645 +1,648 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Pipes; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using UndertaleModLib.Decompiler; using UndertaleModLib.Models; -namespace UndertaleModLib.Scripting; - -/// -/// The exception that is thrown when trivial errors happen during runtime of UndertaleModTool scripts.
-/// This exception does not contain a stacktrace and should more be handled like an error message that stops execution of the currently running script. -///
-/// if (Data is null) throw new ScriptException("Please load data.win first!); -public class ScriptException : Exception +namespace UndertaleModLib.Scripting { /// - /// Initializes a new instance of the IOException class with its message string set to the empty string (""). - /// - public ScriptException() - { - } - - /// - /// Initializes a new instance of the IOException class with its message string set to . - /// - /// A that describes the error. The content of is intended to be understood by humans. - public ScriptException(string msg) : base(msg) - { - } -} - -/// -/// Defines a generalized set of attributes and methods that a value type or class implements -/// to be able to interact with UndertaleModTool-Scripts. -/// -public interface IScriptInterface -{ - /// - /// The data file. - /// - UndertaleData Data { get; } - - /// - /// The file path where resides. - /// - string FilePath { get; } - - /// - /// The path of the current executed script. - /// - string ScriptPath { get; } - - /// - /// The object that's currently highlighted in the GUI. - /// - object Highlighted { get; } - - /// - /// The object that's currently selected in the GUI. - /// - object Selected { get; } - - /// - /// Indicates whether saving is currently enabled. - /// - bool CanSave { get; } - - /// - /// Indicates whether the last script executed successfully or not. - /// - bool ScriptExecutionSuccess { get; } - - /// - /// Error message of the last executed script. Will be "" () if no error occured. - /// - string ScriptErrorMessage { get; } - - /// - /// Path of the main executable that's currently running. - /// - /// For example C://Users/me/UMT/UMT.exe or /bin/UMTCLI. - string ExePath { get; } - - /// - /// A string, detailing the type of the last encountered error. - /// - string ScriptErrorType { get; } - - /// - /// Indicates whether the user has enabled the setting to use decompiled code cache. - /// - bool GMLCacheEnabled { get; } - - /// - /// Indicating whether the Program is currently closed. - /// //TODO: Only GUI + ExportAllRoomsToPng.csx uses this, but nothing should ever need to access this value. - /// "somehow Dispatcher.Invoke() in a loop creates executable code queue that doesn't clear on app closing." - /// - bool IsAppClosed { get; } - - /// - /// Ensures that a valid data file () is loaded. An exception should be thrown if it isn't. - /// - void EnsureDataLoaded() - { - if (Data is null) - throw new ScriptException("No data file is currently loaded!"); - } - - /// - /// Creates a new Data file asynchronously. - /// - /// if task was successful, if not. - Task MakeNewDataFile(); - - /// Obsolete. Use . - [Obsolete("Use MakeNewDataFile instead!")] - sealed Task Make_New_File() - { - return MakeNewDataFile(); - } - - //TODO: i have absolutely no idea what any of these do. - void ReplaceTempWithMain(bool imAnExpertBtw = false); - void ReplaceMainWithTemp(bool imAnExpertBtw = false); - void ReplaceTempWithCorrections(bool imAnExpertBtw = false); - void ReplaceCorrectionsWithTemp(bool imAnExpertBtw = false); - void UpdateCorrections(bool imAnExpertBtw = false); - - /// - /// Used in Scripts in order to show a message to the user. - /// - /// The message to show. - void ScriptMessage(string message); - - //TODO: currently should get repurposed/renamed? - /// - /// Sets the message of the variable holding text from the console. Currently only used in GUI. - /// - /// The message to set it to. - void SetUMTConsoleText(string message); - - /// - /// Used in Scripts in order to ask a yes/no question to the user which they can answer. + /// The exception that is thrown when trivial errors happen during runtime of UndertaleModTool scripts.
+ /// This exception does not contain a stacktrace and should more be handled like an error message that stops execution of the currently running script. ///
- /// The message to ask. - /// if user affirmed the question, if not. - bool ScriptQuestion(string message); - - /// - /// Used in Scripts in order to show an error to the user. - /// - /// The error message to show. - /// A short-descriptive title. - /// Whether to call with . - //TODO: setConsoleText should get a *clearer* name - void ScriptError(string error, string title = "Error", bool SetConsoleText = true); - - /// - /// Used in Scripts in order to open a URL in the users' browser. - /// - /// The URL to open. - void ScriptOpenURL(string url); - - /// - /// Run a C# UndertaleModLib compatible script file. - /// - /// File path to the script file to execute. - /// A that indicates whether the execution of the script was successful. - bool RunUMTScript(string path); - - /// - /// Lint whether a file is C# UndertaleModLib compatible. - /// - /// File path to the script file to lint. - /// A that indicates whether the linting was successful. - bool LintUMTScript(string path); - - /// - /// Initializes a Script Dialog with default values - /// - void InitializeScriptDialog(); - - //TODO: some profile mod stuff, not quite sure on what its supposed to do. - void ReapplyProfileCode(); - void NukeProfileGML(string codeName); - - /// - ///Get the decompiled text from a code entry (like gml_Script_moveTo). - /// - /// The name of the code entry from which to get the decompiled code from. - /// The GlobalDecompileContext - /// Decompiled text as a . - /// This will return a string, even if the decompilation failed! Usually commented out and featuring - /// DECOMPILER FAILED! . - string GetDecompiledText(string codeName, GlobalDecompileContext context = null); - - /// - /// Get the decompiled text from an object. - /// - /// The object from which to get the decompiled code from. - /// The GlobalDecompileContext - /// Decompiled text as a . - /// This will return a string, even if the decompilation failed! Usually commented out and featuring - /// DECOMPILER FAILED! . - string GetDecompiledText(UndertaleCode code, GlobalDecompileContext context = null); - - /// - /// Get the disassembly from a code entry (like gml_Script_moveTo). - /// - /// The name of the code entry from which to get the disassembly from. - /// Disassembly as . - /// This will return a string, even if the disassembly failed! Usually commented out and featuring - /// DISASSEMBLY FAILED! . - string GetDisassemblyText(string codeName); - - /// - /// Get the disassembly from an object. - /// - /// The object from which to get the disassembly from. - /// Disassembly as . - /// This will return a string, even if the disassembly failed! Usually commented out and featuring - /// DISASSEMBLY FAILED! . - string GetDisassemblyText(UndertaleCode code); - - /// - /// Check whether two files are identical. - /// - /// File path to first file. - /// File path to second file. - /// A that indicates whether the files are identical or not. - bool AreFilesIdentical(string file1, string file2) + /// if (Data is null) throw new ScriptException("Please load data.win first!); + public class ScriptException : Exception { - using FileStream fs1 = new FileStream(file1, FileMode.Open, FileAccess.Read, FileShare.Read); - using FileStream fs2 = new FileStream(file2, FileMode.Open, FileAccess.Read, FileShare.Read); - - if (fs1.Length != fs2.Length) return false; // different size, files can't be the same - - while (true) + /// + /// Initializes a new instance of the IOException class with its message string set to the empty string (""). + /// + public ScriptException() { - int b1 = fs1.ReadByte(); - int b2 = fs2.ReadByte(); - if (b1 != b2) return false; // different contents, files are not the same - if (b1 == -1) break; // here both bytes are the same. Thus we only need to check if one is at end-of-file. } - // identical - return true; - } - - /// - /// Allows the user to input text with the option to cancel it. - /// - /// A short descriptive title. - /// A label describing what the user should input. - /// The default value of the input. - /// The text of the cancel button. - /// The text of the submit button. - /// Whether to allow the input to have multiple lines. - /// Whether the window is allowed to be closed. - /// Should this be set to , then there also won't be a close button. - /// The text that the user inputted. - string ScriptInputDialog(string title, string label, string defaultInput, string cancelText, string submitText, bool isMultiline, bool preventClose); - - /// - /// Allows the user to input text in a simple dialog. - /// - /// A short descriptive title. - /// A label describing what the user should input. - /// The default value of the input. - /// Whether to allow the input to have multiple lines. - /// Whether to block the parent window and only continue after the dialog is cleared. - /// The text that the user inputted. - string SimpleTextInput(string title, string label, string defaultValue, bool allowMultiline, bool showDialog = true); - - /// - /// Shows simple output to the user. - /// - /// A short descriptive title. - /// A label describing the output. - /// The message to convey to the user. - /// Whether to allow the message to be multiline or not. - /// Should this be false but have multiple lines, then only the first line will be shown. - void SimpleTextOutput(string title, string label, string message, bool allowMultiline); - - /// - /// Shows search output with clickable text to the user. - /// - /// A short descriptive title. - /// The query that was searched for. - /// How many results have been found. - /// An of type , - /// with TKey being the name of the code entry an TValue being a list of matching code lines with their line number prepended. - /// Whether to open the "Decompiled" view or the "Disassembly" view when clicking on an entry name. - /// A list of code entries that encountered an error while searching. - /// A task that represents the search output. - Task ClickableSearchOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool showInDecompiledView, IOrderedEnumerable failedList = null); - - /// - /// Obsolete. - /// - [Obsolete("Use ClickableSearchOutput instead!")] - sealed Task ClickableTextOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool showInDecompiledView, IOrderedEnumerable failedList = null) - { - return ClickableSearchOutput(title, query, resultsCount, resultsDict, showInDecompiledView, failedList); - } - - /// - /// Shows search output with clickable text to the user. - /// - /// A short descriptive title. - /// The query that was searched for. - /// How many results have been found. - /// A with TKey being the name of the code entry and - /// TValue being a list of matching code lines with their line number prepended. - /// Whether to open the "Decompiled" view or the "Disassembly" view when clicking on an entry name. - /// A list of code entries that encountered an error while searching. - /// A task that represents the search output. - Task ClickableSearchOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool showInDecompiledView, IEnumerable failedList = null); - - /// - /// Obsolete. - /// - [Obsolete("Use ClickableSearchOutput instead!")] - sealed Task ClickableTextOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool showInDecompiledView, IEnumerable failedList = null) - { - return ClickableSearchOutput(title, query, resultsCount, resultsDict, showInDecompiledView, failedList); - } - - /// - /// Sets . - /// - /// The state to set it to. - void SetFinishedMessage(bool isFinishedMessageEnabled); - - /// - /// Updates the progress bar. Not to be called directly in scripts! Use instead! - /// - /// - /// - /// - /// - void UpdateProgressBar(string message, string status, double progressValue, double maxValue); - - /// - /// Sets the progress bar dialog to a certain value. - /// - /// What the progress bar is describing. - /// What the current status is. For example Decompiling.... - /// The value to set the progress bar to. - /// The max value of the progress bar. - void SetProgressBar(string message, string status, double progressValue, double maxValue); - - /// - /// Show the progress bar. - /// - void SetProgressBar(); - - /// - /// Updates the value of the current running progress bar dialog. - /// - /// The new value to set the progress bar to. - void UpdateProgressValue(double progressValue); - - /// - /// Updates the status of the current running progress bar dialog. - /// - /// The new status. For example Decompiling.... - void UpdateProgressStatus(string status); - - //TODO: considering this forces everything that implements this to have their own progressValue, - //why not make that a necessary attribute? - /// - /// Adds a certain amount to the variable holding a progress value. - /// - /// The amount to add. - void AddProgress(int amount); - - /// - /// Increments the variable holding a progress value by one. - /// - void IncrementProgress(); - - /// - /// Obsolete. - /// - [Obsolete("Use IncrementProgress instead!")] - sealed void IncProgress() - { - IncrementProgress(); - } - - /// - /// Adds a certain amount to the variable holding a progress value in. - /// Used for parallel operations, as it is thread-safe. - /// - /// The amount to add. - void AddProgressParallel(int amount); - - /// - /// Obsolete. - /// - [Obsolete("Use AddProgressParallel instead!")] - sealed void AddProgressP(int amount) - { - AddProgressParallel(amount); + /// + /// Initializes a new instance of the IOException class with its message string set to . + /// + /// A that describes the error. The content of is intended to be understood by humans. + public ScriptException(string msg) : base(msg) + { + } } /// - /// Increments the variable holding a progress value by one. - /// Used for parallel operations, as it is thread-safe. + /// Defines a generalized set of attributes and methods that a value type or class implements + /// to be able to interact with UndertaleModTool-Scripts. /// - void IncrementProgressParallel(); - - /// - /// Obsolete. - /// - [Obsolete("Use IncrementProgressParallel instead!")] - sealed void IncProgressP() + public interface IScriptInterface { - IncrementProgressParallel(); - } - - /// - /// Gets the value of the variable holding a progress value. - /// - /// The value as . - int GetProgress(); - - /// - /// Sets the value of the variable holding a progress variable to another value. - /// - /// The new value for the progress variable. - void SetProgress(int value); - - /// - /// Hides the progress bar. - /// - void HideProgressBar(); - - /// - /// Enables the UI. - /// - void EnableUI(); - - /// - /// Allows scripts to modify asset lists from the non-UI thread. - /// If this isn't called before attempting to modify them, a will be thrown. - /// - /// A comma separated list of asset list names. This is case sensitive. - /// Whether to enable or disable the synchronization. - //TODO: Having resourceType as a comma separated list just screams for error. Make it use some array of predefined assets it can use. - void SyncBinding(string resourceType, bool enable); - - /// - /// Stops the synchronization of all previously enabled assets. - /// - void DisableAllSyncBindings(); + /// + /// The data file. + /// + UndertaleData Data { get; } + + /// + /// The file path where resides. + /// + string FilePath { get; } + + /// + /// The path of the current executed script. + /// + string ScriptPath { get; } + + /// + /// The object that's currently highlighted in the GUI. + /// + object Highlighted { get; } + + /// + /// The object that's currently selected in the GUI. + /// + object Selected { get; } + + /// + /// Indicates whether saving is currently enabled. + /// + bool CanSave { get; } + + /// + /// Indicates whether the last script executed successfully or not. + /// + bool ScriptExecutionSuccess { get; } + + /// + /// Error message of the last executed script. Will be "" () if no error occured. + /// + string ScriptErrorMessage { get; } + + /// + /// Path of the main executable that's currently running. + /// + /// For example C://Users/me/UMT/UMT.exe or /bin/UMTCLI. + string ExePath { get; } + + /// + /// A string, detailing the type of the last encountered error. + /// + string ScriptErrorType { get; } + + /// + /// Indicates whether the user has enabled the setting to use decompiled code cache. + /// + bool GMLCacheEnabled { get; } + + /// + /// Indicating whether the Program is currently closed. + /// //TODO: Only GUI + ExportAllRoomsToPng.csx uses this, but nothing should ever need to access this value. + /// "somehow Dispatcher.Invoke() in a loop creates executable code queue that doesn't clear on app closing." + /// + bool IsAppClosed { get; } + + /// + /// Ensures that a valid data file () is loaded. An exception should be thrown if it isn't. + /// + void EnsureDataLoaded() + { + if (Data is null) + throw new ScriptException("No data file is currently loaded!"); + } - /// - /// Obsolete - /// - /// - [Obsolete("Use DisableAllSyncBindings() instead!")] - sealed void SyncBinding(bool enable = false) - { - DisableAllSyncBindings(); - } + /// + /// Creates a new Data file asynchronously. + /// + /// if task was successful, if not. + Task MakeNewDataFile(); - /// - /// Starts the task that updates a progress bar in parallel. - /// - void StartProgressBarUpdater(); + /// Obsolete. Use . + [Obsolete("Use MakeNewDataFile instead!")] + sealed Task Make_New_File() + { + return MakeNewDataFile(); + } - /// - /// Obsolete. - /// - [Obsolete("Use StartProgressBarUpdater instead!")] - sealed void StartUpdater() - { - StartProgressBarUpdater(); - } + //TODO: i have absolutely no idea what any of these do. + void ReplaceTempWithMain(bool imAnExpertBtw = false); + void ReplaceMainWithTemp(bool imAnExpertBtw = false); + void ReplaceTempWithCorrections(bool imAnExpertBtw = false); + void ReplaceCorrectionsWithTemp(bool imAnExpertBtw = false); + void UpdateCorrections(bool imAnExpertBtw = false); + + /// + /// Used in Scripts in order to show a message to the user. + /// + /// The message to show. + void ScriptMessage(string message); + + //TODO: currently should get repurposed/renamed? + /// + /// Sets the message of the variable holding text from the console. Currently only used in GUI. + /// + /// The message to set it to. + void SetUMTConsoleText(string message); + + /// + /// Used in Scripts in order to ask a yes/no question to the user which they can answer. + /// + /// The message to ask. + /// if user affirmed the question, if not. + bool ScriptQuestion(string message); + + /// + /// Used in Scripts in order to show an error to the user. + /// + /// The error message to show. + /// A short-descriptive title. + /// Whether to call with . + //TODO: setConsoleText should get a *clearer* name + void ScriptError(string error, string title = "Error", bool SetConsoleText = true); + + /// + /// Used in Scripts in order to open a URL in the users' browser. + /// + /// The URL to open. + void ScriptOpenURL(string url); + + /// + /// Run a C# UndertaleModLib compatible script file. + /// + /// File path to the script file to execute. + /// A that indicates whether the execution of the script was successful. + bool RunUMTScript(string path); + + /// + /// Lint whether a file is C# UndertaleModLib compatible. + /// + /// File path to the script file to lint. + /// A that indicates whether the linting was successful. + bool LintUMTScript(string path); + + /// + /// Initializes a Script Dialog with default values + /// + void InitializeScriptDialog(); + + //TODO: some profile mod stuff, not quite sure on what its supposed to do. + void ReapplyProfileCode(); + void NukeProfileGML(string codeName); + + /// + ///Get the decompiled text from a code entry (like gml_Script_moveTo). + /// + /// The name of the code entry from which to get the decompiled code from. + /// The GlobalDecompileContext + /// Decompiled text as a . + /// This will return a string, even if the decompilation failed! Usually commented out and featuring + /// DECOMPILER FAILED! . + string GetDecompiledText(string codeName, GlobalDecompileContext context = null); + + /// + /// Get the decompiled text from an object. + /// + /// The object from which to get the decompiled code from. + /// The GlobalDecompileContext + /// Decompiled text as a . + /// This will return a string, even if the decompilation failed! Usually commented out and featuring + /// DECOMPILER FAILED! . + string GetDecompiledText(UndertaleCode code, GlobalDecompileContext context = null); + + /// + /// Get the disassembly from a code entry (like gml_Script_moveTo). + /// + /// The name of the code entry from which to get the disassembly from. + /// Disassembly as . + /// This will return a string, even if the disassembly failed! Usually commented out and featuring + /// DISASSEMBLY FAILED! . + string GetDisassemblyText(string codeName); + + /// + /// Get the disassembly from an object. + /// + /// The object from which to get the disassembly from. + /// Disassembly as . + /// This will return a string, even if the disassembly failed! Usually commented out and featuring + /// DISASSEMBLY FAILED! . + string GetDisassemblyText(UndertaleCode code); + + /// + /// Check whether two files are identical. + /// + /// File path to first file. + /// File path to second file. + /// A that indicates whether the files are identical or not. + bool AreFilesIdentical(string file1, string file2) + { + using FileStream fs1 = new FileStream(file1, FileMode.Open, FileAccess.Read, FileShare.Read); + using FileStream fs2 = new FileStream(file2, FileMode.Open, FileAccess.Read, FileShare.Read); - /// - /// Stops the task that updates a progress bar in parallel. - /// - /// A task that represents the stopped progress updater. - Task StopProgressBarUpdater(); + if (fs1.Length != fs2.Length) return false; // different size, files can't be the same - /// - /// Obsolete. - /// - [Obsolete("Use StopProgressBarUpdater instead!")] - sealed Task StopUpdater () - { - return StopProgressBarUpdater(); - } + while (true) + { + int b1 = fs1.ReadByte(); + int b2 = fs2.ReadByte(); + if (b1 != b2) return false; // different contents, files are not the same + if (b1 == -1) break; // here both bytes are the same. Thus we only need to check if one is at end-of-file. + } - /// - /// Generates a decompiled code cache to accelerate operations that need to access code often. - /// - /// The GlobalDecompileContext. - /// The dialog that should be shown. If then a new dialog will be automatically created and shown. - /// Whether to clear from . - /// Whether the decompiled GML cache was generated or not. if it was successful, - /// if it wasn't or is disabled. - Task GenerateGMLCache(ThreadLocal decompileContext = null, object dialog = null, bool clearGMLEditedBefore = false); + // identical + return true; + } - /// - /// Changes the currently selected in the GUI. - /// - /// The new object that should now be selected. - /// Whether the object should be open in a new tab. - void ChangeSelection(object newSelection, bool inNewTab = false); + /// + /// Allows the user to input text with the option to cancel it. + /// + /// A short descriptive title. + /// A label describing what the user should input. + /// The default value of the input. + /// The text of the cancel button. + /// The text of the submit button. + /// Whether to allow the input to have multiple lines. + /// Whether the window is allowed to be closed. + /// Should this be set to , then there also won't be a close button. + /// The text that the user inputted. + string ScriptInputDialog(string title, string label, string defaultInput, string cancelText, string submitText, bool isMultiline, bool preventClose); + + /// + /// Allows the user to input text in a simple dialog. + /// + /// A short descriptive title. + /// A label describing what the user should input. + /// The default value of the input. + /// Whether to allow the input to have multiple lines. + /// Whether to block the parent window and only continue after the dialog is cleared. + /// The text that the user inputted. + string SimpleTextInput(string title, string label, string defaultValue, bool allowMultiline, bool showDialog = true); + + /// + /// Shows simple output to the user. + /// + /// A short descriptive title. + /// A label describing the output. + /// The message to convey to the user. + /// Whether to allow the message to be multiline or not. + /// Should this be false but have multiple lines, then only the first line will be shown. + void SimpleTextOutput(string title, string label, string message, bool allowMultiline); + + /// + /// Shows search output with clickable text to the user. + /// + /// A short descriptive title. + /// The query that was searched for. + /// How many results have been found. + /// An of type , + /// with TKey being the name of the code entry an TValue being a list of matching code lines with their line number prepended. + /// Whether to open the "Decompiled" view or the "Disassembly" view when clicking on an entry name. + /// A list of code entries that encountered an error while searching. + /// A task that represents the search output. + Task ClickableSearchOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool showInDecompiledView, IOrderedEnumerable failedList = null); + + /// + /// Obsolete. + /// + [Obsolete("Use ClickableSearchOutput instead!")] + sealed Task ClickableTextOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool showInDecompiledView, IOrderedEnumerable failedList = null) + { + return ClickableSearchOutput(title, query, resultsCount, resultsDict, showInDecompiledView, failedList); + } - /// - /// Used to prompt the user for a directory. - /// - /// The directory selected by the user. - string PromptChooseDirectory(); + /// + /// Shows search output with clickable text to the user. + /// + /// A short descriptive title. + /// The query that was searched for. + /// How many results have been found. + /// A with TKey being the name of the code entry and + /// TValue being a list of matching code lines with their line number prepended. + /// Whether to open the "Decompiled" view or the "Disassembly" view when clicking on an entry name. + /// A list of code entries that encountered an error while searching. + /// A task that represents the search output. + Task ClickableSearchOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool showInDecompiledView, IEnumerable failedList = null); + + /// + /// Obsolete. + /// + [Obsolete("Use ClickableSearchOutput instead!")] + sealed Task ClickableTextOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool showInDecompiledView, IEnumerable failedList = null) + { + return ClickableSearchOutput(title, query, resultsCount, resultsDict, showInDecompiledView, failedList); + } - /// - /// Obsolete - /// - [Obsolete("Use this parameters, as it is not used.")] - sealed string PromptChooseDirectory(string prompt) - { - return PromptChooseDirectory(); - } + /// + /// Sets . + /// + /// The state to set it to. + void SetFinishedMessage(bool isFinishedMessageEnabled); + + /// + /// Updates the progress bar. Not to be called directly in scripts! Use instead! + /// + /// + /// + /// + /// + void UpdateProgressBar(string message, string status, double progressValue, double maxValue); + + /// + /// Sets the progress bar dialog to a certain value. + /// + /// What the progress bar is describing. + /// What the current status is. For example Decompiling.... + /// The value to set the progress bar to. + /// The max value of the progress bar. + void SetProgressBar(string message, string status, double progressValue, double maxValue); + + /// + /// Show the progress bar. + /// + void SetProgressBar(); + + /// + /// Updates the value of the current running progress bar dialog. + /// + /// The new value to set the progress bar to. + void UpdateProgressValue(double progressValue); + + /// + /// Updates the status of the current running progress bar dialog. + /// + /// The new status. For example Decompiling.... + void UpdateProgressStatus(string status); + + //TODO: considering this forces everything that implements this to have their own progressValue, + //why not make that a necessary attribute? + /// + /// Adds a certain amount to the variable holding a progress value. + /// + /// The amount to add. + void AddProgress(int amount); + + /// + /// Increments the variable holding a progress value by one. + /// + void IncrementProgress(); + + /// + /// Obsolete. + /// + [Obsolete("Use IncrementProgress instead!")] + sealed void IncProgress() + { + IncrementProgress(); + } - /// - /// Used to prompt the user for a file. - /// - /// The default extension that should be selected. - /// The filters used for the file select. - /// The file selected by the user. - string PromptLoadFile(string defaultExt, string filter); + /// + /// Adds a certain amount to the variable holding a progress value in. + /// Used for parallel operations, as it is thread-safe. + /// + /// The amount to add. + void AddProgressParallel(int amount); + + /// + /// Obsolete. + /// + [Obsolete("Use AddProgressParallel instead!")] + sealed void AddProgressP(int amount) + { + AddProgressParallel(amount); + } - //TODO: so much stuff.... - /// - /// Replaces/Imports all GML in a specific code entry with a specified string. - /// - /// The name of the code entry that shall get replaced.
- /// If the entry does not exist, it will be created! - /// The new GML code that shall replace the current GML code of . - /// Whether the code entry should get linked. - /// In other words, have special handling for scripts, globals and object code. - /// If this is an empty string - /// will be used for replacing the code entry in the case that anything fails. - /// If this is then an error will be shown instead and the code entry will not get replaced. - void ImportGMLString(string codeName, string gmlCode, bool doParse = true, bool checkDecompiler = false); + /// + /// Increments the variable holding a progress value by one. + /// Used for parallel operations, as it is thread-safe. + /// + void IncrementProgressParallel(); + + /// + /// Obsolete. + /// + [Obsolete("Use IncrementProgressParallel instead!")] + sealed void IncProgressP() + { + IncrementProgressParallel(); + } - /// - /// Replaces/Imports all GM-Bytecode ASM in a specific code entry with a specified string. - /// - /// The name of the code entry that shall get replaced.
- /// If the entry does not exist, it will be created! - /// The new ASM code that shall replace the current GML code of . - /// Whether the code entry should get linked. - /// In other words, have special handling for scripts, globals and object code. - /// Whether or not to nuke the profile entry for profile mode. - /// If this is an empty string - /// will be used for replacing the code entry in the case that anything fails. - /// If this is then an error will be shown instead and the code entry will not get replaced. - void ImportASMString(string codeName, string gmlCode, bool doParse = true, bool nukeProfile = true, bool checkDecompiler = false); + /// + /// Gets the value of the variable holding a progress value. + /// + /// The value as . + int GetProgress(); + + /// + /// Sets the value of the variable holding a progress variable to another value. + /// + /// The new value for the progress variable. + void SetProgress(int value); + + /// + /// Hides the progress bar. + /// + void HideProgressBar(); + + /// + /// Enables the UI. + /// + void EnableUI(); + + /// + /// Allows scripts to modify asset lists from the non-UI thread. + /// If this isn't called before attempting to modify them, a will be thrown. + /// + /// A comma separated list of asset list names. This is case sensitive. + /// Whether to enable or disable the synchronization. + //TODO: Having resourceType as a comma separated list just screams for error. Make it use some array of predefined assets it can use. + void SyncBinding(string resourceType, bool enable); + + /// + /// Stops the synchronization of all previously enabled assets. + /// + void DisableAllSyncBindings(); + + /// + /// Obsolete + /// + /// + [Obsolete("Use DisableAllSyncBindings() instead!")] + sealed void SyncBinding(bool enable = false) + { + DisableAllSyncBindings(); + } - /// - /// Replaces/Imports all GML in a specific code entry with a specified file. - /// - /// The path to the GML file. This file needs to - /// A) contain valid GML and B) the filename needs to be the name of the code entry that shall get replaced
- /// If the entry does not exist, it will be created! - /// - /// Whether the code entry should get linked. - /// In other words, have special handling for scripts, globals and object code. - /// If this is an empty string - /// will be used for replacing the code entry in the case that anything fails. - /// Whether a will be thrown on any errors. - void ImportGMLFile(string fileName, bool doParse = true, bool checkDecompiler = false, bool throwOnError = false); + /// + /// Starts the task that updates a progress bar in parallel. + /// + void StartProgressBarUpdater(); - /// - /// Replaces/Imports all GM-Bytecode ASM in a specific code entry with a specified file. - /// - /// The path to the ASM file. This file needs to - /// A) contain valid GM-Bytecode ASM and B) the filename needs to be the name of the code entry that shall get replaced
- /// If the entry does not exist, it will be created! - /// Whether the code entry should get linked. - /// In other words, have special handling for scripts, globals and object code. - /// Whether or not to nuke the profile entry for profile mode. - /// If this is an empty string - /// will be used for replacing the code entry in the case that anything fails. - /// Whether a will be thrown on any errors. - void ImportASMFile(string fileName, bool doParse = true, bool nukeProfile = true, bool checkDecompiler = false, bool throwOnError = false); + /// + /// Obsolete. + /// + [Obsolete("Use StartProgressBarUpdater instead!")] + sealed void StartUpdater() + { + StartProgressBarUpdater(); + } - /// - /// Find a keyword in a GML code entry and replaces it with a replacement string. - /// - /// The name of the code entry that shall get replaced. - /// The search term. - /// The replacement term. - /// Whether the keyword search should be case-sensitive. - /// Whether should be treated as RegEx. - /// The global decompile context. - void ReplaceTextInGML(string codeName, string keyword, string replacement, bool caseSensitive = false, bool isRegex = false, GlobalDecompileContext context = null); + /// + /// Stops the task that updates a progress bar in parallel. + /// + /// A task that represents the stopped progress updater. + Task StopProgressBarUpdater(); + + /// + /// Obsolete. + /// + [Obsolete("Use StopProgressBarUpdater instead!")] + sealed Task StopUpdater () + { + return StopProgressBarUpdater(); + } - /// - /// Find a keyword in a GML code entry and replaces it with a replacement string. - /// - /// The code entry that shall get replaced. - /// The search term. - /// The replacement term. - /// Whether the keyword search should be case-sensitive. - /// Whether should be treated as RegEx. - /// The global decompile context. - void ReplaceTextInGML(UndertaleCode code, string keyword, string replacement, bool caseSensitive = false, bool isRegex = false, GlobalDecompileContext context = null); + /// + /// Generates a decompiled code cache to accelerate operations that need to access code often. + /// + /// The GlobalDecompileContext. + /// The dialog that should be shown. If then a new dialog will be automatically created and shown. + /// Whether to clear from . + /// Whether the decompiled GML cache was generated or not. if it was successful, + /// if it wasn't or is disabled. + Task GenerateGMLCache(ThreadLocal decompileContext = null, object dialog = null, bool clearGMLEditedBefore = false); + + /// + /// Changes the currently selected in the GUI. + /// + /// The new object that should now be selected. + /// Whether the object should be open in a new tab. + void ChangeSelection(object newSelection, bool inNewTab = false); + + /// + /// Used to prompt the user for a directory. + /// + /// The directory selected by the user. + string PromptChooseDirectory(); + + /// + /// Obsolete + /// + [Obsolete("Use this parameters, as it is not used.")] + sealed string PromptChooseDirectory(string prompt) + { + return PromptChooseDirectory(); + } - /// - /// Method returning a dummy boolean value. - /// - /// Returns a dummy boolean value - bool DummyBool() - { - return true; - } + /// + /// Used to prompt the user for a file. + /// + /// The default extension that should be selected. + /// The filters used for the file select. + /// The file selected by the user. + string PromptLoadFile(string defaultExt, string filter); + + //TODO: so much stuff.... + /// + /// Replaces/Imports all GML in a specific code entry with a specified string. + /// + /// The name of the code entry that shall get replaced.
+ /// If the entry does not exist, it will be created! + /// The new GML code that shall replace the current GML code of . + /// Whether the code entry should get linked. + /// In other words, have special handling for scripts, globals and object code. + /// If this is an empty string + /// will be used for replacing the code entry in the case that anything fails. + /// If this is then an error will be shown instead and the code entry will not get replaced. + void ImportGMLString(string codeName, string gmlCode, bool doParse = true, bool checkDecompiler = false); + + /// + /// Replaces/Imports all GM-Bytecode ASM in a specific code entry with a specified string. + /// + /// The name of the code entry that shall get replaced.
+ /// If the entry does not exist, it will be created! + /// The new ASM code that shall replace the current GML code of . + /// Whether the code entry should get linked. + /// In other words, have special handling for scripts, globals and object code. + /// Whether or not to nuke the profile entry for profile mode. + /// If this is an empty string + /// will be used for replacing the code entry in the case that anything fails. + /// If this is then an error will be shown instead and the code entry will not get replaced. + void ImportASMString(string codeName, string gmlCode, bool doParse = true, bool nukeProfile = true, bool checkDecompiler = false); + + /// + /// Replaces/Imports all GML in a specific code entry with a specified file. + /// + /// The path to the GML file. This file needs to + /// A) contain valid GML and B) the filename needs to be the name of the code entry that shall get replaced
+ /// If the entry does not exist, it will be created! + /// + /// Whether the code entry should get linked. + /// In other words, have special handling for scripts, globals and object code. + /// If this is an empty string + /// will be used for replacing the code entry in the case that anything fails. + /// Whether a will be thrown on any errors. + void ImportGMLFile(string fileName, bool doParse = true, bool checkDecompiler = false, bool throwOnError = false); + + /// + /// Replaces/Imports all GM-Bytecode ASM in a specific code entry with a specified file. + /// + /// The path to the ASM file. This file needs to + /// A) contain valid GM-Bytecode ASM and B) the filename needs to be the name of the code entry that shall get replaced
+ /// If the entry does not exist, it will be created! + /// Whether the code entry should get linked. + /// In other words, have special handling for scripts, globals and object code. + /// Whether or not to nuke the profile entry for profile mode. + /// If this is an empty string + /// will be used for replacing the code entry in the case that anything fails. + /// Whether a will be thrown on any errors. + void ImportASMFile(string fileName, bool doParse = true, bool nukeProfile = true, bool checkDecompiler = false, bool throwOnError = false); + + /// + /// Find a keyword in a GML code entry and replaces it with a replacement string. + /// + /// The name of the code entry that shall get replaced. + /// The search term. + /// The replacement term. + /// Whether the keyword search should be case-sensitive. + /// Whether should be treated as RegEx. + /// The global decompile context. + void ReplaceTextInGML(string codeName, string keyword, string replacement, bool caseSensitive = false, bool isRegex = false, GlobalDecompileContext context = null); + + /// + /// Find a keyword in a GML code entry and replaces it with a replacement string. + /// + /// The code entry that shall get replaced. + /// The search term. + /// The replacement term. + /// Whether the keyword search should be case-sensitive. + /// Whether should be treated as RegEx. + /// The global decompile context. + void ReplaceTextInGML(UndertaleCode code, string keyword, string replacement, bool caseSensitive = false, bool isRegex = false, GlobalDecompileContext context = null); + + /// + /// Method returning a dummy boolean value. + /// + /// Returns a dummy boolean value + bool DummyBool() + { + return true; + } - /// - /// Method doing nothing. - /// - void DummyVoid() - { + /// + /// Method doing nothing. + /// + void DummyVoid() + { - } + } - /// - /// Method returning a dummy string value. - /// - /// Returns a dummy string value. - string DummyString() - { - return ""; + /// + /// Method returning a dummy string value. + /// + /// Returns a dummy string value. + string DummyString() + { + return ""; + } } -} \ No newline at end of file +} diff --git a/UndertaleModLib/UndertaleBaseTypes.cs b/UndertaleModLib/UndertaleBaseTypes.cs index b4341c7c9..44d5604a9 100644 --- a/UndertaleModLib/UndertaleBaseTypes.cs +++ b/UndertaleModLib/UndertaleBaseTypes.cs @@ -87,10 +87,7 @@ public enum ResourceType // GMS2.3+ Sequence = 11, - AnimCurve = 12, - - // GM 2023+ - ParticleSystem = 13 + AnimCurve = 12 } public interface UndertaleResource : UndertaleObject diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index 7f54c965f..4cb010604 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -451,7 +451,6 @@ public class UndertaleChunkFONT : UndertaleListChunk public byte[] Padding; private static bool checkedFor2022_2; - private static bool checkedFor2023_6; private void CheckForGM2022_2(UndertaleReader reader) { /* This code performs four checks to identify GM2022.2. @@ -510,57 +509,6 @@ private void CheckForGM2022_2(UndertaleReader reader) checkedFor2022_2 = true; } - private void CheckForGM2023_6(UndertaleReader reader) - { - // This is basically the same as the 2022.2 check, but adapted for the LineHeight value instead of Ascender. - - // We already know whether the version is more or less than 2023.2 due to PSEM. Checking a shorter range narrows possibility of error. - if (!reader.undertaleData.IsVersionAtLeast(2023, 2) || reader.undertaleData.IsVersionAtLeast(2023, 6)) - { - checkedFor2023_6 = true; - return; - } - - long positionToReturn = reader.Position; - bool GMS2023_6 = false; - - if (reader.ReadUInt32() > 0) // Font count - { - uint firstFontPointer = reader.ReadUInt32(); - reader.AbsPosition = firstFontPointer + 56; // Two more values: SDFSpread and LineHeight. 48 + 4 + 4 = 56. - uint glyphsLength = reader.ReadUInt32(); - GMS2023_6 = true; - if ((glyphsLength * 4) > this.Length) - { - GMS2023_6 = false; - } - else if (glyphsLength != 0) - { - List glyphPointers = new List((int)glyphsLength); - for (uint i = 0; i < glyphsLength; i++) - glyphPointers.Add(reader.ReadUInt32()); - foreach (uint pointer in glyphPointers) - { - if (reader.AbsPosition != pointer) - { - GMS2023_6 = false; - break; - } - - reader.Position += 14; - ushort kerningLength = reader.ReadUInt16(); - reader.Position += (uint)4 * kerningLength; // combining read/write would apparently break - } - } - - } - if (GMS2023_6) - reader.undertaleData.SetGMS2Version(2023, 6); - reader.Position = positionToReturn; - - checkedFor2023_6 = true; - } - internal override void SerializeChunk(UndertaleWriter writer) { base.SerializeChunk(writer); @@ -580,9 +528,6 @@ internal override void UnserializeChunk(UndertaleReader reader) if (!checkedFor2022_2) CheckForGM2022_2(reader); - if (!checkedFor2023_6) - CheckForGM2023_6(reader); - base.UnserializeChunk(reader); Padding = reader.ReadBytes(512); @@ -591,10 +536,8 @@ internal override void UnserializeChunk(UndertaleReader reader) internal override uint UnserializeObjectCount(UndertaleReader reader) { checkedFor2022_2 = false; - checkedFor2023_6 = false; CheckForGM2022_2(reader); - CheckForGM2023_6(reader); return base.UnserializeObjectCount(reader); } diff --git a/UndertaleModLib/UndertaleData.cs b/UndertaleModLib/UndertaleData.cs index b1013f55e..f81f8008f 100644 --- a/UndertaleModLib/UndertaleData.cs +++ b/UndertaleModLib/UndertaleData.cs @@ -341,7 +341,7 @@ public IList this[Type resourceType] //TODO: Why are the functions that deal with the cache in a completely different place than the cache parameters? These have *no* place of being here. /// - /// A of cached decompiled code, + /// A of cached decompiled code, /// with the code name as the Key and the decompiled code text as the value. /// public ConcurrentDictionary GMLCache { get; set; } diff --git a/UndertaleModLib/UndertaleIO.cs b/UndertaleModLib/UndertaleIO.cs index 7ced9747b..cabbd6667 100644 --- a/UndertaleModLib/UndertaleIO.cs +++ b/UndertaleModLib/UndertaleIO.cs @@ -638,7 +638,7 @@ public void ToHere() if (length != expectedLength) { int diff = (int)expectedLength - (int)length; - reader.SubmitMessage("WARNING: File specified length " + expectedLength + ", but read only " + length + " (" + diff + " padding?)"); + Console.WriteLine("WARNING: File specified length " + expectedLength + ", but read only " + length + " (" + diff + " padding?)"); if (diff > 0) reader.Position += (uint)diff; else diff --git a/UndertaleModLib/Util/DebugUtil.cs b/UndertaleModLib/Util/DebugUtil.cs index 0d254e602..615b07913 100644 --- a/UndertaleModLib/Util/DebugUtil.cs +++ b/UndertaleModLib/Util/DebugUtil.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -9,13 +8,6 @@ namespace UndertaleModLib.Util { public class DebugUtil { - - /// - /// Asserts that a specified condition is true. - /// - /// The condition to assert for. - /// The message to use if the assertion fails. - /// Gets thrown if the assertion fails. public static void Assert(bool expr, string msg = "Unknown error.") { if (expr) diff --git a/UndertaleModLib/Util/DictionaryExtensions.cs b/UndertaleModLib/Util/DictionaryExtensions.cs new file mode 100644 index 000000000..5eb520ff4 --- /dev/null +++ b/UndertaleModLib/Util/DictionaryExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UndertaleModLib.Util +{ + public static class DictionaryExtensions + { + public static TValue GetValueOrDefault(this Dictionary dictionary, TKey key, TValue defaultValue = default(TValue)) + { + if (dictionary == null) { throw new ArgumentNullException(nameof(dictionary)); } + if (key == null) { throw new ArgumentNullException(nameof(key)); } + + TValue value; + return dictionary.TryGetValue(key, out value) ? value : defaultValue; + } + } +} diff --git a/UndertaleModLib/Util/QoiConverter.cs b/UndertaleModLib/Util/QoiConverter.cs index 1391c24fc..ea05a5ac3 100644 --- a/UndertaleModLib/Util/QoiConverter.cs +++ b/UndertaleModLib/Util/QoiConverter.cs @@ -63,15 +63,20 @@ public static Bitmap GetImageFromStream(Stream s) } /// - /// Creates a from a of s. + /// Creates a from a . /// - /// The of s to create the PNG image from. + /// The to create the PNG image from. /// The QOI image as a PNG. /// If there is an invalid QOIF magic header or there was an error with stride width. public static Bitmap GetImageFromSpan(ReadOnlySpan bytes) => GetImageFromSpan(bytes, out _); - /// - /// The total amount of data read from the . + /// + /// Creates a from a . + /// + /// The to create the PNG image from. + /// The total amount of data read from the . + /// The QOI image as a PNG. + /// If there is an invalid QOIF magic header or there was an error with stride width. public unsafe static Bitmap GetImageFromSpan(ReadOnlySpan bytes, out int length) { ReadOnlySpan header = bytes[..12]; @@ -190,13 +195,13 @@ public unsafe static Bitmap GetImageFromSpan(ReadOnlySpan bytes, out int l public static byte[] GetArrayFromImage(Bitmap bmp, int padding = 4) => GetSpanFromImage(bmp, padding).ToArray(); /// - /// Creates a QOI image as a from a . + /// Creates a QOI image as a from a . /// /// The to create the QOI image from. /// The amount of bytes of padding that should be used. /// A QOI Image as a byte array. /// If there was an error with stride width. - public static unsafe Span GetSpanFromImage(Bitmap bmp, int padding = 4) + public unsafe static Span GetSpanFromImage(Bitmap bmp, int padding = 4) { if (!isBufferEmpty) Array.Clear(sharedBuffer); diff --git a/UndertaleModTests/GameLoadingTests.cs b/UndertaleModTests/GameLoadingTests.cs index 9680a1688..4092c514e 100644 --- a/UndertaleModTests/GameLoadingTests.cs +++ b/UndertaleModTests/GameLoadingTests.cs @@ -96,7 +96,7 @@ public void DisassembleAndReassembleAllScripts() Assert.AreEqual(code.Instructions[i].ArgumentsCount, reasm[i].ArgumentsCount, errMsg); Assert.AreEqual(code.Instructions[i].JumpOffsetPopenvExitMagic, reasm[i].JumpOffsetPopenvExitMagic, errMsg); if (!code.Instructions[i].JumpOffsetPopenvExitMagic) - Assert.AreEqual(code.Instructions[i].JumpOffset, reasm[i].JumpOffset, errMsg); // note: also handles IntArgument implicitly + Assert.AreEqual(code.Instructions[i].JumpOffset, reasm[i].JumpOffset, errMsg); Assert.AreSame(code.Instructions[i].Destination?.Target, reasm[i].Destination?.Target, errMsg); Assert.AreEqual(code.Instructions[i].Destination?.Type, reasm[i].Destination?.Type, errMsg); Assert.AreSame(code.Instructions[i].Function?.Target, reasm[i].Function?.Target, errMsg); diff --git a/UndertaleModTool/Converters/IsVersionAtLeastConverter.cs b/UndertaleModTool/Converters/IsVersionAtLeastConverter.cs index f957c4dfa..e0c546240 100644 --- a/UndertaleModTool/Converters/IsVersionAtLeastConverter.cs +++ b/UndertaleModTool/Converters/IsVersionAtLeastConverter.cs @@ -33,7 +33,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn if (ver.Groups[3].Value != "") release = uint.Parse(ver.Groups[3].Value); if (ver.Groups[4].Value != "") - build = uint.Parse(ver.Groups[4].Value); + release = uint.Parse(ver.Groups[4].Value); if (mainWindow.Data.IsVersionAtLeast(major, minor, release, build)) return Visibility.Visible; diff --git a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs index 687f4163d..d52b913f7 100644 --- a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs @@ -383,7 +383,7 @@ private static void RestoreCaretPosition(TextEditor textEditor, int linePos, int textEditor.ScrollToEnd(); } } - + private static void FillObjectDicts() { var data = mainWindow.Data; @@ -690,7 +690,7 @@ private async Task DecompileCode(UndertaleCode code, bool first, LoaderDialog ex { if (match.Success) { - if (gettext.TryGetValue(match.Groups[1].Value, out string text)) + if (gettext.TryGetValue(match.Groups[1].Value, out string text) && !decompiled.Contains($" // {text}")) decompiledLines[i] += $" // {text}"; } } @@ -707,7 +707,7 @@ private async Task DecompileCode(UndertaleCode code, bool first, LoaderDialog ex { if (match.Success) { - if (gettextJSON.TryGetValue(match.Groups[^1].Value, out string text)) + if (gettextJSON.TryGetValue(match.Groups[^1].Value, out string text) && !decompiled.Contains($" // {text}")) decompiledLines[i] += $" // {text}"; } } @@ -1025,7 +1025,7 @@ public override void StartGeneration(ITextRunConstructionContext context) { int line = docLine.LineNumber; var highlighter = highlighterInst; - + HighlightedLine highlighted; try { @@ -1071,10 +1071,10 @@ public override VisualLineElement ConstructElement(int offset) return null; var doc = CurrentContext.Document; - string numText = doc.GetText(offset, numLength); + string numText = doc.GetText(offset, numLength); var line = new ClickVisualLineText(numText, CurrentContext.VisualLine, numLength); - + line.Clicked += (text, inNewTab) => { if (int.TryParse(text, out int id)) @@ -1105,12 +1105,6 @@ public override VisualLineElement ConstructElement(int offset) possibleObjects.Add(data.Shaders[id]); if (id < data.Timelines.Count) possibleObjects.Add(data.Timelines[id]); - if (id < (data.AnimationCurves?.Count ?? 0)) - possibleObjects.Add(data.AnimationCurves[id]); - if (id < (data.Sequences?.Count ?? 0)) - possibleObjects.Add(data.Sequences[id]); - if (id < (data.ParticleSystems?.Count ?? 0)) - possibleObjects.Add(data.ParticleSystems[id]); } ContextMenuDark contextMenu = new(); @@ -1128,7 +1122,7 @@ public override VisualLineElement ConstructElement(int offset) { mainWindow.Focus(); mainWindow.ChangeSelection(obj, true); - + } else if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) mainWindow.ChangeSelection(obj); @@ -1189,11 +1183,11 @@ public class NameGenerator : VisualLineElementGenerator private readonly TextEditor textEditorInst; private readonly UndertaleCodeEditor codeEditorInst; - private static readonly SolidColorBrush FunctionBrush = new(Color.FromRgb(0xFF, 0xB8, 0x71)); - private static readonly SolidColorBrush GlobalBrush = new(Color.FromRgb(0xF9, 0x7B, 0xF9)); - private static readonly SolidColorBrush ConstantBrush = new(Color.FromRgb(0xFF, 0x80, 0x80)); - private static readonly SolidColorBrush InstanceBrush = new(Color.FromRgb(0x58, 0xE3, 0x5A)); - private static readonly SolidColorBrush LocalBrush = new(Color.FromRgb(0xFF, 0xF8, 0x99)); + private SolidColorBrush FunctionBrush = new(Color.FromRgb(CodeColorsWindow.FunctionColor_0, CodeColorsWindow.FunctionColor_1, CodeColorsWindow.FunctionColor_2)); + private SolidColorBrush GlobalBrush = new(Color.FromRgb(CodeColorsWindow.GlobalColor_0, CodeColorsWindow.GlobalColor_1, CodeColorsWindow.GlobalColor_2)); + private SolidColorBrush ConstantBrush = new(Color.FromRgb(CodeColorsWindow.ConstantColor_0, CodeColorsWindow.ConstantColor_1, CodeColorsWindow.ConstantColor_2)); + private SolidColorBrush InstanceBrush = new(Color.FromRgb(CodeColorsWindow.InstanceColor_0, CodeColorsWindow.InstanceColor_1, CodeColorsWindow.InstanceColor_2)); + private SolidColorBrush LocalBrush = new(Color.FromRgb(CodeColorsWindow.LocalColor_0, CodeColorsWindow.LocalColor_1, CodeColorsWindow.LocalColor_2)); // new(Color.FromRgb(0x58, 0xF8, 0x99)); -> this color is pretty cool private static ContextMenuDark contextMenu; @@ -1221,7 +1215,6 @@ public NameGenerator(UndertaleCodeEditor codeEditorInst, TextArea textAreaInst) Placement = PlacementMode.MousePoint }; } - public override void StartGeneration(ITextRunConstructionContext context) { lineNameSections.Clear(); diff --git a/UndertaleModTool/Editors/UndertaleFontEditor/UndertaleFontEditor.xaml b/UndertaleModTool/Editors/UndertaleFontEditor/UndertaleFontEditor.xaml index 9bd05cd80..250f78c6b 100644 --- a/UndertaleModTool/Editors/UndertaleFontEditor/UndertaleFontEditor.xaml +++ b/UndertaleModTool/Editors/UndertaleFontEditor/UndertaleFontEditor.xaml @@ -116,15 +116,10 @@ - - - - + Hint: You can click on any glyph here to highlight it in the "Glyphs". - + @@ -204,7 +199,7 @@ - +