Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Underanalyzer compiler integration #2056

Open
wants to merge 12 commits into
base: underanalyzer
Choose a base branch
from
2 changes: 1 addition & 1 deletion Underanalyzer
Submodule Underanalyzer updated 93 files
+27 −0 .github/ISSUE_TEMPLATE/compiler-bug-report.md
+20 −2 README.md
+17 −3 Underanalyzer/Compiler/Bytecode/BytecodeContext.cs
+16 −2 Underanalyzer/Compiler/Bytecode/ControlFlowContext.cs
+37 −8 Underanalyzer/Compiler/Bytecode/FunctionEntry.cs
+35 −14 Underanalyzer/Compiler/CompileContext.cs
+11 −5 Underanalyzer/Compiler/FunctionScope.cs
+7 −1 Underanalyzer/Compiler/IBuiltins.cs
+1 −1 Underanalyzer/Compiler/ICodeBuilder.cs
+10 −9 Underanalyzer/Compiler/Lexer/Identifiers.cs
+3 −3 Underanalyzer/Compiler/Lexer/LexContext.cs
+1 −1 Underanalyzer/Compiler/Lexer/Strings.cs
+1 −1 Underanalyzer/Compiler/Lexer/Tags.cs
+41 −2 Underanalyzer/Compiler/Lexer/Token.cs
+10 −3 Underanalyzer/Compiler/Nodes/AccessorNode.cs
+24 −6 Underanalyzer/Compiler/Nodes/AssignNode.cs
+1 −1 Underanalyzer/Compiler/Nodes/BinaryChainNode.cs
+23 −0 Underanalyzer/Compiler/Nodes/BlockNode.cs
+1 −0 Underanalyzer/Compiler/Nodes/BreakNode.cs
+9 −1 Underanalyzer/Compiler/Nodes/ContinueNode.cs
+13 −1 Underanalyzer/Compiler/Nodes/DotVariableNode.cs
+3 −1 Underanalyzer/Compiler/Nodes/ForLoopNode.cs
+41 −1 Underanalyzer/Compiler/Nodes/FunctionCallNode.cs
+25 −11 Underanalyzer/Compiler/Nodes/FunctionDeclNode.cs
+8 −1 Underanalyzer/Compiler/Nodes/RepeatLoopNode.cs
+15 −3 Underanalyzer/Compiler/Nodes/SimpleFunctionCallNode.cs
+34 −11 Underanalyzer/Compiler/Nodes/SimpleVariableNode.cs
+18 −17 Underanalyzer/Compiler/Nodes/TryCatchNode.cs
+21 −0 Underanalyzer/Compiler/Parser/Expressions.cs
+3 −1 Underanalyzer/Compiler/Parser/Statements.cs
+15 −0 Underanalyzer/Decompiler/AST/BlockSimulator.cs
+7 −0 Underanalyzer/Decompiler/AST/IConditionalValueNode.cs
+13 −0 Underanalyzer/Decompiler/AST/Nodes/ArrayInitNode.cs
+13 −0 Underanalyzer/Decompiler/AST/Nodes/AssetReferenceNode.cs
+35 −3 Underanalyzer/Decompiler/AST/Nodes/AssignNode.cs
+8 −0 Underanalyzer/Decompiler/AST/Nodes/BinaryNode.cs
+9 −0 Underanalyzer/Decompiler/AST/Nodes/BlockLocalVarDeclNode.cs
+157 −112 Underanalyzer/Decompiler/AST/Nodes/BlockNode.cs
+14 −0 Underanalyzer/Decompiler/AST/Nodes/BooleanNode.cs
+12 −0 Underanalyzer/Decompiler/AST/Nodes/BreakNode.cs
+13 −0 Underanalyzer/Decompiler/AST/Nodes/ConditionalNode.cs
+12 −0 Underanalyzer/Decompiler/AST/Nodes/ContinueNode.cs
+9 −0 Underanalyzer/Decompiler/AST/Nodes/DoUntilLoopNode.cs
+14 −0 Underanalyzer/Decompiler/AST/Nodes/DoubleNode.cs
+9 −0 Underanalyzer/Decompiler/AST/Nodes/EnumDeclNode.cs
+13 −0 Underanalyzer/Decompiler/AST/Nodes/EnumValueNode.cs
+10 −0 Underanalyzer/Decompiler/AST/Nodes/ExitNode.cs
+68 −28 Underanalyzer/Decompiler/AST/Nodes/ForLoopNode.cs
+30 −0 Underanalyzer/Decompiler/AST/Nodes/FunctionCallNode.cs
+35 −0 Underanalyzer/Decompiler/AST/Nodes/FunctionDeclNode.cs
+13 −0 Underanalyzer/Decompiler/AST/Nodes/FunctionReferenceNode.cs
+16 −0 Underanalyzer/Decompiler/AST/Nodes/IfNode.cs
+13 −0 Underanalyzer/Decompiler/AST/Nodes/InstanceTypeNode.cs
+14 −0 Underanalyzer/Decompiler/AST/Nodes/Int16Node.cs
+14 −0 Underanalyzer/Decompiler/AST/Nodes/Int32Node.cs
+14 −0 Underanalyzer/Decompiler/AST/Nodes/Int64Node.cs
+12 −0 Underanalyzer/Decompiler/AST/Nodes/LocalVarDeclNode.cs
+13 −0 Underanalyzer/Decompiler/AST/Nodes/MacroValueNode.cs
+26 −0 Underanalyzer/Decompiler/AST/Nodes/NewObjectNode.cs
+13 −0 Underanalyzer/Decompiler/AST/Nodes/NullishCoalesceNode.cs
+23 −2 Underanalyzer/Decompiler/AST/Nodes/PredefinedDoubleNode.cs
+9 −0 Underanalyzer/Decompiler/AST/Nodes/RepeatLoopNode.cs
+10 −0 Underanalyzer/Decompiler/AST/Nodes/ReturnNode.cs
+9 −0 Underanalyzer/Decompiler/AST/Nodes/ShortCircuitNode.cs
+9 −0 Underanalyzer/Decompiler/AST/Nodes/StaticInitNode.cs
+14 −0 Underanalyzer/Decompiler/AST/Nodes/StringNode.cs
+23 −0 Underanalyzer/Decompiler/AST/Nodes/StructNode.cs
+10 −0 Underanalyzer/Decompiler/AST/Nodes/SwitchCaseNode.cs
+9 −0 Underanalyzer/Decompiler/AST/Nodes/SwitchNode.cs
+18 −0 Underanalyzer/Decompiler/AST/Nodes/ThrowNode.cs
+30 −1 Underanalyzer/Decompiler/AST/Nodes/TryCatchNode.cs
+13 −0 Underanalyzer/Decompiler/AST/Nodes/UnaryNode.cs
+23 −0 Underanalyzer/Decompiler/AST/Nodes/VariableCallNode.cs
+21 −0 Underanalyzer/Decompiler/AST/Nodes/VariableHashNode.cs
+24 −3 Underanalyzer/Decompiler/AST/Nodes/VariableNode.cs
+22 −5 Underanalyzer/Decompiler/AST/Nodes/WhileLoopNode.cs
+9 −0 Underanalyzer/Decompiler/AST/Nodes/WithLoopNode.cs
+8 −3 Underanalyzer/Decompiler/ControlFlow/Fragment.cs
+13 −1 Underanalyzer/Decompiler/ControlFlow/ShortCircuit.cs
+22 −3 Underanalyzer/Decompiler/ControlFlow/Switch.cs
+5 −0 Underanalyzer/Decompiler/GlobalFunctions.cs
+17 −1 Underanalyzer/IGameContext.cs
+32 −3 Underanalyzer/Mock/BuiltinsMock.cs
+3 −10 Underanalyzer/Mock/CodeBuilderMock.cs
+4 −0 Underanalyzer/Mock/GameContextMock.cs
+2 −2 Underanalyzer/Mock/VMAssemblyMock.cs
+755 −4 UnderanalyzerTest/BytecodeContext.GenerateCode.cs
+5 −2 UnderanalyzerTest/DecompileContext.DecompileToString.Settings.cs
+25 −0 UnderanalyzerTest/LexContext.Tokenize.cs
+25 −0 UnderanalyzerTest/ParseContext.Parse.cs
+4 −0 UnderanalyzerTest/ParseContext.ParseAndPostProcess.cs
+222 −3 UnderanalyzerTest/RoundTrip.cs
+8 −8 UnderanalyzerTest/TestUtil.cs
466 changes: 2 additions & 464 deletions UndertaleModCli/Program.UMTLibInherited.cs

Large diffs are not rendered by default.

137 changes: 129 additions & 8 deletions UndertaleModCli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using System.Threading.Tasks;
using UndertaleModLib.Models;
using Newtonsoft.Json;
using UndertaleModLib.Compiler;

namespace UndertaleModCli;

Expand Down Expand Up @@ -160,7 +161,7 @@ public static int Main(string[] args)
dumpCommand,
replaceCommand
};
rootCommand.Description = "CLI tool for modding, decompiling and unpacking Undertale (and other Game Maker: Studio games)!";
rootCommand.Description = "CLI tool for modding, decompiling and unpacking Undertale (and other GameMaker games)!";
Parser commandLine = new CommandLineBuilder(rootCommand)
.UseDefaults() // automatically configures dotnet-suggest
.Build();
Expand Down Expand Up @@ -672,18 +673,138 @@ private void DumpAllTextures()
/// <param name="fileToReplace">File path which should replace the code entry.</param>
private void ReplaceCodeEntryWithFile(string codeEntry, FileInfo fileToReplace)
{
UndertaleCode code = Data.Code.ByName(codeEntry);
if (Verbose)
Console.WriteLine("Replacing " + codeEntry);

if (code == null)
// Read source code from file
string gmlCode = File.ReadAllText(fileToReplace.FullName);

// Link code to object events manually only if collision events are used
CompileResult result = CompileResult.UnsuccessfulResult;
bool manualLink = false;
const string objectPrefix = "gml_Object_";
if (codeEntry.StartsWith(objectPrefix, StringComparison.Ordinal))
{
Console.Error.WriteLine($"Data file does not contain a code entry named {codeEntry}!");
return;
// Parse object event. First, find positions of last two underscores in name.
int lastUnderscore = codeEntry.LastIndexOf('_');
int secondLastUnderscore = codeEntry.LastIndexOf('_', lastUnderscore - 1);
if (lastUnderscore <= 0 || secondLastUnderscore <= 0)
{
Console.Error.WriteLine($"Failed to parse object code entry name: \"{codeEntry}\"");
return;
}

// Extract object name, event type, and event subtype
ReadOnlySpan<char> objectName = codeEntry.AsSpan(new Range(objectPrefix.Length, secondLastUnderscore));
ReadOnlySpan<char> eventType = codeEntry.AsSpan(new Range(secondLastUnderscore + 1, lastUnderscore));
if (!uint.TryParse(codeEntry.AsSpan(lastUnderscore + 1), out uint eventSubtype))
{
// No number at the end of the name; parse it out as best as possible (may technically be ambiguous sometimes...).
// It should be a collision event, though.
manualLink = true;
ReadOnlySpan<char> nameAfterPrefix = codeEntry.AsSpan(objectPrefix.Length);
const string collisionSeparator = "_Collision_";
int collisionSeparatorPos = nameAfterPrefix.LastIndexOf(collisionSeparator);
if (collisionSeparatorPos != -1)
{
// Split out the actual object name and the collision subtype
objectName = nameAfterPrefix[0..collisionSeparatorPos];
ReadOnlySpan<char> collisionSubtype = nameAfterPrefix[(collisionSeparatorPos + collisionSeparator.Length)..];

if (Data.IsVersionAtLeast(2, 3))
{
// GameMaker 2.3+ uses the object name for the collision subtype
int objectIndex = Data.GameObjects.IndexOfName(collisionSubtype);
if (objectIndex >= 0)
{
// Object already exists; use its ID as a subtype
eventSubtype = (uint)objectIndex;
}
else
{
// Need to create a new object
eventSubtype = (uint)Data.GameObjects.Count;
Data.GameObjects.Add(new()
{
Name = Data.Strings.MakeString(collisionSubtype.ToString())
});
}
}
else
{
// Pre-2.3 GMS2 versions use GUIDs... need to resolve it
eventSubtype = ReduceCollisionValue(GetCollisionValueFromCodeNameGUID(codeEntry));
ReassignGUIDs(collisionSubtype.ToString(), ReduceCollisionValue(GetCollisionValueFromCodeNameGUID(codeEntry)));
}
}
else
{
Console.Error.WriteLine($"Failed to parse event type and subtype for \"{codeEntry}\".");
return;
}
}
else if (eventType.SequenceEqual("Collision"))
{
// Handle collision events with object ID at the end of the name
manualLink = true;
if (eventSubtype >= Data.GameObjects.Count)
{
if (ScriptQuestion($"Object of ID {eventSubtype} was not found.\nAdd new object? (will be ID {Data.GameObjects.Count})"))
{
// Create new object at end of game object list
eventSubtype = (uint)Data.GameObjects.Count;
Data.GameObjects.Add(new()
{
Name = Data.Strings.MakeString(
SimpleTextInput("Enter object name", $"Enter object name for ID {eventSubtype}", "", false))
});
}
else
{
// It *needs* to have a valid value, make the user specify one
eventSubtype = ReduceCollisionValue([uint.MaxValue]);
}
}
}

// If manually linking, do so
if (manualLink)
{
// Create new object if necessary
UndertaleGameObject obj = Data.GameObjects.ByName(objectName);
if (obj is null)
{
obj = new()
{
Name = Data.Strings.MakeString(objectName.ToString())
};
Data.GameObjects.Add(obj);
}

// Link to object's event with a blank code entry
UndertaleCode manualCode = UndertaleCode.CreateEmptyEntry(Data, codeEntry);
CodeImportGroup.LinkEvent(obj, manualCode, EventType.Collision, eventSubtype);

// Perform code import using manual code entry
CodeImportGroup group = new(Data);
group.QueueReplace(manualCode, gmlCode);
result = group.Import();
}
}

if (Verbose)
Console.WriteLine("Replacing " + codeEntry);
// When not manually linking, just let a code import group do it during importing
if (!manualLink)
{
CodeImportGroup group = new(Data);
group.QueueReplace(codeEntry, gmlCode);
result = group.Import();
}

ImportGMLString(codeEntry, File.ReadAllText(fileToReplace.FullName));
// Error if import failed
if (!result.Successful)
{
Console.Error.WriteLine("Code import unsuccessful:\n" + result.PrintAllErrors(false));
}
}

/// <summary>
Expand Down
Loading