Skip to content

Commit

Permalink
Initial support for typed asset refs
Browse files Browse the repository at this point in the history
Additionally includes some extra support for newer asset types, such as animation curves, sequences, and particle systems.
  • Loading branch information
colinator27 committed Jul 11, 2023
1 parent 32cd3ea commit 3e9675d
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 27 deletions.
8 changes: 8 additions & 0 deletions UndertaleModLib/Compiler/AssemblyWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,14 @@ 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);
Expand Down
52 changes: 36 additions & 16 deletions UndertaleModLib/Compiler/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Threading.Tasks;
using UndertaleModLib.Models;
using static UndertaleModLib.Compiler.Compiler.AssemblyWriter;
using AssetRefType = UndertaleModLib.Decompiler.Decompiler.ExpressionAssetRef.RefType;

namespace UndertaleModLib.Compiler
{
Expand All @@ -17,6 +18,7 @@ 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<string, string> LocalVars = new Dictionary<string, string>();
public Dictionary<string, string> GlobalVars = new Dictionary<string, string>();
Expand Down Expand Up @@ -80,18 +82,21 @@ public void Setup(bool redoAssets = false)
private void MakeAssetDictionary()
{
assetIds.Clear();
AddAssetsFromList(Data?.GameObjects);
AddAssetsFromList(Data?.Sprites);
AddAssetsFromList(Data?.Sounds);
AddAssetsFromList(Data?.Backgrounds);
AddAssetsFromList(Data?.Paths);
AddAssetsFromList(Data?.Fonts);
AddAssetsFromList(Data?.Timelines);
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);
if (!GMS2_3)
AddAssetsFromList(Data?.Scripts);
AddAssetsFromList(Data?.Shaders);
AddAssetsFromList(Data?.Rooms);
AddAssetsFromList(Data?.AudioGroups);
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);

scripts.Clear();
if (Data?.Scripts != null)
Expand All @@ -116,15 +121,30 @@ private void MakeAssetDictionary()
}
}

private void AddAssetsFromList<T>(IList<T> list) where T : UndertaleNamedResource
private void AddAssetsFromList<T>(IList<T> list, AssetRefType type) where T : UndertaleNamedResource
{
if (list == null)
return;
for (int i = 0; i < list.Count; i++)
if (TypedAssetRefs)
{
string name = list[i].Name?.Content;
if (name != null)
assetIds[name] = 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;
}
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions UndertaleModLib/Compiler/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public enum Kind
Number,
String,
Constant,
Int64
Int64,
Reference
}

public ExpressionConstant(double val)
Expand Down Expand Up @@ -1338,7 +1339,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)
if (left.Kind != Statement.StatementKind.ExprConstant || left.Constant.kind == ExpressionConstant.Kind.Reference /* TODO: will this ever change? */)
result.Children.Add(left);
else
combine = true;
Expand Down Expand Up @@ -2707,6 +2708,8 @@ private static bool ResolveIdentifier(CompileContext context, string identifier,
}
return false;
}
if (context.TypedAssetRefs)
constant.kind = ExpressionConstant.Kind.Reference;
constant.valueNumber = (double)index;
return true;
}
Expand Down
13 changes: 11 additions & 2 deletions UndertaleModLib/Decompiler/Assembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public static class Assembler
{ -7, "setstatic" },
{ -8, "savearef" },
{ -9, "restorearef" },
{ -10, "chknullish" }
{ -10, "chknullish" },
{ -11, "pushref" }
};
public static Dictionary<string, short> NameToBreakID = new Dictionary<string, short>()
{
Expand All @@ -36,7 +37,8 @@ public static class Assembler
{ "setstatic", -7 },
{ "savearef", -8 },
{ "restorearef", -9 },
{ "chknullish", -10 }
{ "chknullish", -10 },
{ "pushref", -11 }
};

// TODO: Improve the error messages
Expand Down Expand Up @@ -217,7 +219,14 @@ public static UndertaleInstruction AssembleOne(string source, IList<UndertaleFun

case UndertaleInstruction.InstructionType.BreakInstruction:
if (breakId != 0)
{
instr.Value = breakId;
if (breakId == -11) // pushref
{
// parse additional int argument
instr.IntArgument = Int32.Parse(line);
}
}
else
instr.Value = Int16.Parse(line);
line = "";
Expand Down
3 changes: 3 additions & 0 deletions UndertaleModLib/Decompiler/AssetTypeResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public enum AssetIDType
GameObject, // or GameObjectInstance or InstanceType, these are all interchangable
Script,
Shader,
AnimCurve,
Sequence,
ParticleSystem,

EventType, // For event_perform

Expand Down
130 changes: 127 additions & 3 deletions UndertaleModLib/Decompiler/Decompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public class DecompileContext
public UndertaleCode TargetCode;
public UndertaleGameObject Object;
public static bool GMS2_3;
public bool AssetResolutionEnabled => !GlobalContext.Data.IsVersionAtLeast(2023, 8);

public DecompileContext(GlobalDecompileContext globalContext, UndertaleCode code, bool computeObject = true)
{
Expand Down Expand Up @@ -490,7 +491,7 @@ public override string ToString(DecompileContext context)
break;*/
}

if (context.GlobalContext.Data != null && AssetType != AssetIDType.Other)
if ((context.AssetResolutionEnabled || AssetType == AssetIDType.Script) && context.GlobalContext.Data != null && AssetType != AssetIDType.Other)
{
IList assetList = null;
switch (AssetType)
Expand Down Expand Up @@ -575,6 +576,128 @@ 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
{
Expand Down Expand Up @@ -1596,8 +1719,6 @@ 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)
Expand Down Expand Up @@ -2586,6 +2707,9 @@ 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;
}
}

Expand Down
Loading

0 comments on commit 3e9675d

Please sign in to comment.