Skip to content

Commit

Permalink
Fixes #36; only inline a subobject once even if it's referenced multi…
Browse files Browse the repository at this point in the history
…ple times.

Fix: always inline empty subobjects (such objects have usually been inserted by the compiler)
Fix: try inline missing subobjects (e.g. a subobject only referenced within a struct literal).
  • Loading branch information
EliotVU committed Apr 25, 2024
1 parent 61fa3ae commit 5ac2022
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 31 deletions.
49 changes: 34 additions & 15 deletions src/Core/Classes/UDefaultProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -686,31 +686,50 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ
break;
}

Debug.Assert(UDecompilingState.s_inlinedSubObjects != null, "UDecompilingState.s_inlinedSubObjects != null");

bool isPendingInline =
UDecompilingState.s_inlinedSubObjects.TryGetValue(constantObject, out bool isInlined);
// If the object is part of the current container, then it probably was an inlined declaration.
bool shouldInline = constantObject.Outer == _Container &&
(deserializeFlags & DeserializeFlags.WithinStruct) == 0;
bool shouldInline = constantObject.Outer == _Container
&& !isPendingInline
&& !isInlined;
if (shouldInline)
{
// Unknown objects are only deserialized on demand.
constantObject.BeginDeserializing();
propertyValue = constantObject.Decompile() + "\r\n";

_TempFlags |= DoNotAppendName;
if ((deserializeFlags & DeserializeFlags.WithinArray) != 0)
{
_TempFlags |= ReplaceNameMarker;
propertyValue += $"{UDecompilingState.Tabs}%ARRAYNAME%={constantObject.Name}";
}
else
if ((deserializeFlags & DeserializeFlags.WithinStruct) == 0)
{
UDecompilingState.s_inlinedSubObjects.Add(constantObject, true);

// Unknown objects are only deserialized on demand.
constantObject.BeginDeserializing();

propertyValue = constantObject.Decompile() + "\r\n";

_TempFlags |= DoNotAppendName;
if ((deserializeFlags & DeserializeFlags.WithinArray) != 0)
{
_TempFlags |= ReplaceNameMarker;
propertyValue += $"{UDecompilingState.Tabs}%ARRAYNAME%={constantObject.Name}";

break;
}

propertyValue += $"{UDecompilingState.Tabs}{Name}={constantObject.Name}";

break;
}

// Within a struct, to be inlined later on!
UDecompilingState.s_inlinedSubObjects.Add(constantObject, false);
propertyValue = $"{Name}={constantObject.Name}";

break;
}

// =Class'Package.Group.Name'
propertyValue = PropertyDisplay.FormatLiteral(constantObject);
// Use shorthand for inlined objects.
propertyValue = isInlined
? constantObject.Name
: PropertyDisplay.FormatLiteral(constantObject);

break;
}
Expand Down
59 changes: 44 additions & 15 deletions src/Core/Classes/UObjectDecompiler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Diagnostics;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace UELib.Core
{
Expand Down Expand Up @@ -58,25 +60,52 @@ public virtual string FormatHeader()
protected string DecompileProperties()
{
if (Properties == null || Properties.Count == 0)
return UDecompilingState.Tabs + "// This object has no properties!\r\n";
return string.Empty;

var output = string.Empty;
for (var i = 0; i < Properties.Count; ++i)
string output = string.Empty;

// HACK: Start with a fresh scope.
var oldState = UDecompilingState.s_inlinedSubObjects;
UDecompilingState.s_inlinedSubObjects = new Dictionary<UObject, bool>();

try
{
//output += $"{UDecompilingState.Tabs}// {Properties[i].Type}\r\n";
string propOutput = Properties[i].Decompile();

// This is the first element of a static array
if (i + 1 < Properties.Count
&& Properties[i + 1].Name == Properties[i].Name
&& Properties[i].ArrayIndex <= 0
&& Properties[i + 1].ArrayIndex > 0)
string propertiesText = string.Empty;
for (int i = 0; i < Properties.Count; ++i)
{
propOutput = propOutput.Insert(Properties[i].Name.Length, "[0]");
//output += $"{UDecompilingState.Tabs}// {Properties[i].Type}\r\n";
string propertyText = Properties[i].Decompile();

// This is the first element of a static array
if (i + 1 < Properties.Count
&& Properties[i + 1].Name == Properties[i].Name
&& Properties[i].ArrayIndex <= 0
&& Properties[i + 1].ArrayIndex > 0)
{
propertyText = propertyText.Insert(Properties[i].Name.Length, "[0]");
}

propertiesText += $"{UDecompilingState.Tabs}{propertyText}\r\n";
}

// FORMAT: 'DEBUG[TAB /* 0xPOSITION */] TABS propertyOutput + NEWLINE
output += UDecompilingState.Tabs + propOutput + "\r\n";
// HACK: Inline sub-objects that we could not inline directly, such as in an array or struct.
// This will still miss sub-objects that have no reference.
var missingSubObjects = UDecompilingState.s_inlinedSubObjects
.Where((k, v) => k.Value == false)
.Select(k => k.Key);
foreach (var obj in missingSubObjects)
{
obj.BeginDeserializing();

string objectText = obj.Decompile();
output += $"{UDecompilingState.Tabs}{objectText}\r\n";
}

output += propertiesText;
}
finally
{
UDecompilingState.s_inlinedSubObjects = oldState;
}

return output;
Expand Down
13 changes: 12 additions & 1 deletion src/UDecompilingState.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
using System.Collections.Generic;
using UELib.Annotations;
using UELib.Core;

namespace UELib
{
public static class UDecompilingState
Expand All @@ -6,6 +10,13 @@ public static class UDecompilingState
"CA2211:NonConstantFieldsShouldNotBeVisible")]
public static string Tabs = string.Empty;

/// <summary>
/// Objects that have been inlined (if true) in the current decompiling state.
///
/// Internal because this is a hack patch to fix an issue where each object is inlined for every reference.
/// </summary>
[CanBeNull] internal static Dictionary<UObject, bool> s_inlinedSubObjects = new Dictionary<UObject, bool>();

public static void AddTabs(int count)
{
for (var i = 0; i < count; ++i)
Expand Down Expand Up @@ -55,4 +66,4 @@ public static string OffsetLabelName(uint offset)
return $"J0x{offset:X2}";
}
}
}
}

0 comments on commit 5ac2022

Please sign in to comment.