Blueprint "Decompile" option

turns blueprint logic into readable c++ code, option shows only when setting is on
This commit is contained in:
Krowe Moh 2025-07-05 09:01:29 +10:00
parent 394bcf356f
commit f64a333847
12 changed files with 1772 additions and 4 deletions

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,20 @@ public static partial class StringExtensions
return $"{size:# ###.##} {sizes[order]}".TrimStart();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetNameLineNumberText(this string s, string lineToFind)
{
using var reader = new StringReader(s);
var lineNum = 0;
while (reader.ReadLine() is { } line)
{
lineNum++;
if (line.Contains(lineToFind, StringComparison.OrdinalIgnoreCase))
return lineNum;
}
return -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetNameLineNumber(this string s, string lineToFind)
{

View File

@ -468,7 +468,7 @@
<controls:Breadcrumb Grid.Row="0" MaxWidth="{Binding ActualWidth, ElementName=AssetsSearchName}" HorizontalAlignment="Left" Margin="0 5 0 5"
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName, FallbackValue='No/Directory/Detected/In/Folder'}"/>
<ListBox Grid.Row="1" x:Name="AssetsListName" Style="{StaticResource AssetsListBox}" PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick" PreviewKeyDown="OnPreviewKeyDown">
<ListBox Grid.Row="1" x:Name="AssetsListName" Style="{StaticResource AssetsListBox}" PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick" PreviewKeyDown="OnPreviewKeyDown" ContextMenuOpening="AssetsListName_ContextMenuOpening">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Extract in New Tab" Command="{Binding DataContext.RightClickMenuCommand}">
@ -501,6 +501,21 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Name="Decompile" Header="Decompile Blueprint" Command="{Binding DataContext.RightClickMenuCommand}" Visibility="Collapsed">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Decompile" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.Header>

View File

@ -165,6 +165,18 @@ public partial class MainWindow
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
}
private void AssetsListName_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
var listBox = sender as ListBox;
var contextMenu = listBox.ContextMenu;
var decompItem = contextMenu.Items
.OfType<MenuItem>()
.FirstOrDefault(mi => mi.Name == "Decompile");
decompItem.Visibility = UserSettings.Default.ShowDecompileOption ? Visibility.Visible : Visibility.Collapsed;
}
private async void OnFolderExtractClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)

View File

@ -202,6 +202,13 @@ namespace FModel.Settings
set => SetProperty(ref _keepDirectoryStructure, value);
}
private bool _showDecompileOption = false;
public bool ShowDecompileOption
{
get => _showDecompileOption;
set => SetProperty(ref _showDecompileOption, value);
}
private ECompressedAudio _compressedAudioMode = ECompressedAudio.PlayDecompressed;
public ECompressedAudio CompressedAudioMode
{

View File

@ -60,6 +60,13 @@ using SkiaSharp;
using UE4Config.Parsing;
using Application = System.Windows.Application;
using FGuid = CUE4Parse.UE4.Objects.Core.Misc.FGuid;
using CUE4Parse.UE4.Assets.Objects.Properties;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Kismet;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.GameplayTags;
using System.Text;
namespace FModel.ViewModels;
@ -946,8 +953,495 @@ public class CUE4ParseViewModel : ViewModel
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("");
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(package, Formatting.Indented), false, false);
}
}
public void Decompile(GameFile entry)
{
var package = Provider.LoadPackage(entry);
if (TabControl.CanAddTabs)
TabControl.AddTab(entry);
else
TabControl.SelectedTab.SoftReset(entry);
TabControl.SelectedTab.TitleExtra = "Decompiled";
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("txt");
var pkg = Provider.LoadPackage(entry);
var outputBuilder = new StringBuilder();
for (var i = 0; i < pkg.ExportMapLength; i++)
{
var pointer = new FPackageIndex(pkg, i + 1).ResolvedObject;
if (pointer?.Object is null)
continue;
var dummy = ((AbstractUePackage) pkg).ConstructObject(
pointer.Class?.Object?.Value as UStruct, pkg);
switch (dummy)
{
case UBlueprintGeneratedClass _:
case UVerseClass _:
{
var blueprintGeneratedClass = pkg.ExportsLazy.FirstOrDefault(e => e.Value is UBlueprintGeneratedClass)?.Value as UBlueprintGeneratedClass;
var verseClass = pkg.ExportsLazy.Where(export => export.Value is UVerseClass).Select(export => (UVerseClass) export.Value).FirstOrDefault();
var stringsarray = new List<string>();
if (blueprintGeneratedClass != null)
{
{
var mainClass = blueprintGeneratedClass?.Name ?? verseClass?.Name;
var superStructName = blueprintGeneratedClass?.SuperStruct?.Name ?? verseClass?.SuperStruct?.Name ?? string.Empty;
outputBuilder.AppendLine(
$"class {KismetExtensions.GetPrefix(blueprintGeneratedClass?.GetType().Name ?? verseClass?.GetType().Name)}{mainClass} : public {KismetExtensions.GetPrefix(blueprintGeneratedClass?.GetType().Name ?? verseClass?.GetType().Name)}{superStructName}\n{{\npublic:");
foreach (var export in pkg.ExportsLazy)
{
if (export.Value is not UBlueprintGeneratedClass)
if (export.Value.Name.StartsWith("Default__") &&
export.Value.Name.EndsWith(mainClass ?? string.Empty))
{
var exportObject = export.Value;
foreach (var key in exportObject.Properties)
{
stringsarray.Add(key.Name.PlainText);
string placeholder = $"{key.Name}placenolder";
string result = key.Tag.GenericValue.ToString();
string keyName = key.Name.PlainText.Replace(" ", "");
var propertyTag = key.Tag.GetValue(typeof(object));
void ShouldAppend(string? value)
{
if (value == null)
return;
if (outputBuilder.ToString().Contains(placeholder))
{
outputBuilder.Replace(placeholder, value);
}
else
{
outputBuilder.AppendLine(
$"\t{KismetExtensions.GetPropertyType(propertyTag)} {keyName} = {value};");
}
}
if (key.Tag.GenericValue is FScriptStruct structTag)
{
if (structTag.StructType is FVector vector)
{
ShouldAppend(
$"FVector({vector.X}, {vector.Y}, {vector.Z})");
}
else if (structTag.StructType is FGuid guid)
{
ShouldAppend(
$"FGuid({guid.A}, {guid.B}, {guid.C}, {guid.D})");
}
else if (structTag.StructType is TIntVector3<int> vector3)
{
ShouldAppend(
$"FVector({vector3.X}, {vector3.Y}, {vector3.Z})");
}
else if (structTag.StructType is TIntVector3<float>
floatVector3)
{
ShouldAppend(
$"FVector({floatVector3.X}, {floatVector3.Y}, {floatVector3.Z})");
}
else if (structTag.StructType is TIntVector2<float>
floatVector2)
{
ShouldAppend(
$"FVector2D({floatVector2.X}, {floatVector2.Y})");
}
else if (structTag.StructType is FVector2D vector2d)
{
ShouldAppend($"FVector2D({vector2d.X}, {vector2d.Y})");
}
else if (structTag.StructType is FRotator rotator)
{
ShouldAppend(
$"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})");
}
else if (structTag.StructType is FStructFallback fallback)
{
string formattedTags;
if (fallback.Properties.Count > 0)
{
formattedTags = "[\n" + string.Join(",\n",
fallback.Properties.Select(tag =>
{
string tagDataFormatted;
if (tag.Tag is TextProperty text)
{
tagDataFormatted = $"\"{text.Value.Text}\"";
}
else if (tag.Tag is NameProperty name)
{
tagDataFormatted = $"\"{name.Value.Text}\"";
}
else if (tag.Tag is ObjectProperty
objectproperty)
{
tagDataFormatted =
$"\"{objectproperty.Value}\"";
}
else if (tag.Tag.GenericValue is FScriptStruct innerStruct && innerStruct.StructType is FStructFallback nestedFallback)
{
if (nestedFallback.Properties.Count > 0)
{
tagDataFormatted = "{ " + string.Join(", ",
nestedFallback.Properties.Select(nested =>
{
string nestedVal;
if (nested.Tag is TextProperty textProp)
nestedVal = $"\"{textProp.Value.Text}\"";
else if (nested.Tag is NameProperty nameProp)
nestedVal = $"\"{nameProp.Value.Text}\"";
else
nestedVal = $"\"{nested.Tag.GenericValue}\"";
return $"\"{nested.Name}\": {nestedVal}";
})) + " }";
}
else
{
tagDataFormatted = "{}";
}
}
else
{
tagDataFormatted = tag.Tag.GenericValue != null ? tag.Tag.GenericValue.ToString() : "{}";
}
;
return $"\t\t{{ \"{tag.Name}\": {tagDataFormatted} }}";
})) + "\n\t]";
}
else
{
formattedTags = "[]";
}
ShouldAppend(formattedTags);
}
else if (structTag.StructType is FGameplayTagContainer
gameplayTag)
{
var tags = gameplayTag.GameplayTags.ToList();
if (tags.Count > 1)
{
var formattedTags = "[\n" + string.Join(",\n",
tags.Select(tag =>
$"\t\t\"{tag.TagName}\"")) +
"\n\t]";
ShouldAppend(formattedTags);
}
else if (tags.Any())
{
ShouldAppend($"\"{tags.First().TagName}\"");
}
else
{
ShouldAppend("[]");
}
}
else if (structTag.StructType is FLinearColor color)
{
ShouldAppend(
$"FLinearColor({color.R}, {color.G}, {color.B}, {color.A})");
}
else
{
ShouldAppend($"\"{result}\"");
}
}
else if (key.Tag.GetType().Name == "ObjectProperty" ||
key.Tag.GetType().Name == "TextProperty" ||
key.PropertyType == "StrProperty" ||
key.PropertyType == "NameProperty" ||
key.PropertyType == "ClassProperty")
{
ShouldAppend($"\"{result}\"");
}
else if (key.Tag.GenericValue is UScriptSet set)
{
var formattedSet = "[\n" + string.Join(",\n",
set.Properties.Select(p =>
$"\t\"{p.GenericValue}\"")) +
"\n\t]";
ShouldAppend(formattedSet);
}
else if (key.Tag.GenericValue is UScriptMap map)
{
var formattedMap = "[\n" + string.Join(",\n",
map.Properties.Select(kvp =>
$"\t{{\n\t\t\"{kvp.Key}\": \"{kvp.Value}\"\n\t}}")) +
"\n\t]";
ShouldAppend(formattedMap);
}
else if (key.Tag.GenericValue is UScriptArray array)
{
var formattedArray = "[\n" + string.Join(",\n",
array.Properties.Select(p =>
{
if (p.GenericValue is FScriptStruct vectorInArray &&
vectorInArray.StructType is FVector vector)
{
return
$"FVector({vector.X}, {vector.Y}, {vector.Z})";
}
if (p.GenericValue is FScriptStruct
vector2dInArray &&
vector2dInArray
.StructType is FVector2D vector2d)
{
return $"FVector2D({vector2d.X}, {vector2d.Y})";
}
if (p.GenericValue is FScriptStruct structInArray &&
structInArray.StructType is FRotator rotator)
{
return
$"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})";
}
else if
(p.GenericValue is FScriptStruct
fallbacksInArray &&
fallbacksInArray.StructType is FStructFallback
fallback)
{
string formattedTags;
if (fallback.Properties.Count > 0)
{
formattedTags = "\t[\n" + string.Join(",\n",
fallback.Properties.Select(tag =>
{
string tagDataFormatted;
if (tag.Tag is TextProperty text)
{
tagDataFormatted =
$"\"{text.Value.Text}\"";
}
else if (tag.Tag is NameProperty
name)
{
tagDataFormatted =
$"\"{name.Value.Text}\"";
}
else if (tag.Tag is ObjectProperty
objectproperty)
{
tagDataFormatted =
$"\"{objectproperty.Value}\"";
}
else
{
tagDataFormatted =
$"\"{tag.Tag.GenericValue}\"";
}
return
$"\t\t\"{tag.Name}\": {tagDataFormatted}";
})) + "\n\t]";
}
else
{
formattedTags = "{}";
}
return formattedTags;
}
else if
(p.GenericValue is FScriptStruct
gameplayTagsInArray &&
gameplayTagsInArray.StructType is
FGameplayTagContainer gameplayTag)
{
var tags = gameplayTag.GameplayTags.ToList();
if (tags.Count > 1)
{
var formattedTags =
"[\n" + string.Join(",\n",
tags.Select(tag =>
$"\t\t\"{tag.TagName}\"")) +
"\n\t]";
return formattedTags;
}
else
{
return $"\"{tags.First().TagName}\"";
}
}
return $"\t\t\"{p.GenericValue}\"";
})) + "\n\t]";
ShouldAppend(formattedArray);
}
else if (key.Tag.GenericValue is FMulticastScriptDelegate multicast)
{
var list = multicast.InvocationList;
ShouldAppend(list.Length == 0 ? "[]" : $"[{string.Join(", ", list.Select(x => $"\"{x.FunctionName}\""))}]");
}
else if (key.Tag.GenericValue is bool boolResult)
{
ShouldAppend(boolResult.ToString().ToLower());
}
else
{
ShouldAppend(result);
}
}
}
else
{
//outputBuilder.Append($"\nType: {export.Value.Name}");
}
}
}
var childProperties = blueprintGeneratedClass?.ChildProperties ??
verseClass?.ChildProperties;
if (childProperties != null)
{
foreach (FProperty property in childProperties)
{
if (!stringsarray.Contains(property.Name.PlainText))
outputBuilder.AppendLine(
$"\t{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || KismetExtensions.GetPropertyProperty(property) ? "*" : string.Empty)} {property.Name.PlainText.Replace(" ", "")} = {property.Name.PlainText.Replace(" ", "")}placenolder;");
}
}
var funcMapOrder =
blueprintGeneratedClass?.FuncMap?.Keys.Select(fname => fname.ToString())
.ToList() ?? verseClass?.FuncMap.Keys.Select(fname => fname.ToString())
.ToList();
var functions = pkg.ExportsLazy
.Where(e => e.Value is UFunction)
.Select(e => (UFunction) e.Value)
.OrderBy(f =>
{
if (funcMapOrder != null)
{
var functionName = f.Name.ToString();
int indexx = funcMapOrder.IndexOf(functionName);
return indexx >= 0 ? indexx : int.MaxValue;
}
return int.MaxValue;
})
.ThenBy(f => f.Name.ToString())
.ToList();
var jumpCodeOffsetsMap = new Dictionary<string, List<int>>();
foreach (var function in functions.AsEnumerable().Reverse())
{
if (function?.ScriptBytecode == null)
continue;
foreach (var property in function.ScriptBytecode)
{
string? label = null;
int? offset = null;
switch (property.Token)
{
case EExprToken.EX_JumpIfNot:
label = ((EX_JumpIfNot) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0];
offset = (int) ((EX_JumpIfNot) property).CodeOffset;
break;
case EExprToken.EX_Jump:
label = ((EX_Jump) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0];
offset = (int) ((EX_Jump) property).CodeOffset;
break;
case EExprToken.EX_LocalFinalFunction:
{
EX_FinalFunction op = (EX_FinalFunction) property;
label = op.StackNode?.Name?.ToString()?.Split('.').Last().Split('[')[0];
if (op.Parameters.Length == 1 && op.Parameters[0] is EX_IntConst intConst)
offset = intConst.Value;
break;
}
}
if (!string.IsNullOrEmpty(label) && offset.HasValue)
{
if (!jumpCodeOffsetsMap.TryGetValue(label, out var list))
jumpCodeOffsetsMap[label] = list = new List<int>();
list.Add(offset.Value);
}
}
}
foreach (var function in functions)
{
string argsList = "";
string returnFunc = "void";
if (function?.ChildProperties != null)
{
foreach (FProperty property in function.ChildProperties)
{
if (property.Name.PlainText == "ReturnValue")
{
returnFunc =
$"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}";
}
else if (!(property.Name.ToString().EndsWith("_ReturnValue") ||
property.Name.ToString().StartsWith("CallFunc_") ||
property.Name.ToString().StartsWith("K2Node_") ||
property.Name.ToString()
.StartsWith("Temp_")) || // removes useless args
property.PropertyFlags.HasFlag(EPropertyFlags.Edit))
{
argsList +=
$"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}{(property.PropertyFlags.HasFlag(EPropertyFlags.OutParm) ? "&" : string.Empty)} {Regex.Replace(property.Name.ToString(), @"^__verse_0x[0-9A-Fa-f]+_", "")}, ";
}
}
}
argsList = argsList.TrimEnd(',', ' ');
outputBuilder.AppendLine($"\n\t{returnFunc} {function.Name.Replace(" ", "")}({argsList})\n\t{{");
if (function?.ScriptBytecode != null)
{
var jumpCodeOffsets = jumpCodeOffsetsMap.TryGetValue(function.Name, out var list) ? list : new List<int>();
foreach (KismetExpression property in function.ScriptBytecode)
{
KismetExtensions.ProcessExpression(property.Token, property, outputBuilder, jumpCodeOffsets);
}
}
else
{
outputBuilder.Append(
"\n\t // This function does not have Bytecode \n\n");
outputBuilder.Append("\t}\n");
}
}
outputBuilder.Append("\n\n}");
}
else
{
continue;
}
break;
}
}
}
string pattern = $@"\w+placenolder";
string updatedOutput = Regex.Replace(outputBuilder.ToString(), pattern, "nullptr");
TabControl.SelectedTab.SetDocumentText(updatedOutput, false, false);
}
private void SaveAndPlaySound(string fullPath, string ext, byte[] data)
{
if (fullPath.StartsWith("/")) fullPath = fullPath[1..];

View File

@ -1,4 +1,4 @@
using System.Collections;
using System.Collections;
using System.Linq;
using System.Threading;
using CUE4Parse.FileProvider.Objects;
@ -44,6 +44,14 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
contextViewModel.CUE4Parse.ShowMetadata(entry);
}
break;
case "Assets_Decompile":
foreach (var entry in entries)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Decompile(entry);
}
break;
case "Assets_Export_Data":
foreach (var entry in entries)
{

View File

@ -0,0 +1,35 @@
using System.Text.RegularExpressions;
using FModel.Extensions;
using ICSharpCode.AvalonEdit.Rendering;
namespace FModel.Views.Resources.Controls;
public class JumpElementGenerator : VisualLineElementGenerator
{
private readonly Regex _JumpRegex = new(
@"\b(?:goto\s+Label_(?'target'\d+);|ExecuteUbergraph_(?'target'\d+)\(10\);)",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private Match FindMatch(int startOffset)
{
var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);
return _JumpRegex.Match(relevantText);
}
public override int GetFirstInterestedOffset(int startOffset)
{
var m = FindMatch(startOffset);
return m.Success ? startOffset + m.Index : -1;
}
public override VisualLineElement ConstructElement(int offset)
{
var m = FindMatch(offset);
if (!m.Success || m.Index != 0 ||
!m.Groups.TryGetValue("target", out var g))
return null;
return new JumpVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1);
}
}

View File

@ -0,0 +1,80 @@
using System;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using FModel.Extensions;
using FModel.Services;
using FModel.ViewModels;
using ICSharpCode.AvalonEdit.Rendering;
namespace FModel.Views.Resources.Controls;
public class JumpVisualLineText : VisualLineText
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public delegate void JumpOnClick(string Jump);
public event JumpOnClick OnJumpClicked;
private readonly string _Jump;
public JumpVisualLineText(string Jump, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
{
_Jump = Jump;
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var relativeOffset = startVisualColumn - VisualColumn;
var text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset + relativeOffset, DocumentLength - relativeOffset);
if (text.Count != 2) // ": "
TextRunProperties.SetForegroundBrush(Brushes.Plum);
return new TextCharacters(text.Text, text.Offset, text.Count, TextRunProperties);
}
private bool JumpIsClickable() => !string.IsNullOrEmpty(_Jump) && Keyboard.Modifiers == ModifierKeys.None;
protected override void OnQueryCursor(QueryCursorEventArgs e)
{
if (!JumpIsClickable())
return;
e.Handled = true;
e.Cursor = Cursors.Hand;
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left || !JumpIsClickable())
return;
if (e.Handled || OnJumpClicked == null)
return;
OnJumpClicked(_Jump);
e.Handled = true;
}
protected override VisualLineText CreateInstance(int length)
{
var a = new JumpVisualLineText(_Jump, ParentVisualLine, length);
a.OnJumpClicked += async (Jump) =>
{
var lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumberText($"Label_{_Jump}:");
if (lineNumber > -1)
{
var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
return;
}
};
return a;
}
}

View File

@ -43,6 +43,7 @@ public partial class AvalonEditor
MyAvalonEditor.TextArea.TextView.LinkTextBackgroundBrush = null;
MyAvalonEditor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Cornsilk;
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator());
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new JumpElementGenerator());
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator());
ApplicationService.ApplicationView.CUE4Parse.TabControl.OnTabRemove += OnTabClose;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Text.RegularExpressions;
using System.Windows.Input;
using System.Windows.Media;
@ -32,6 +32,7 @@ public partial class PropertiesPopout
MyAvalonEditor.TextArea.TextView.LinkTextBackgroundBrush = null;
MyAvalonEditor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Cornsilk;
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator());
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new JumpElementGenerator());
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator());
_manager = new JsonFoldingStrategies(MyAvalonEditor);
_manager.UpdateFoldings(MyAvalonEditor.Document);

View File

@ -44,6 +44,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -227,6 +228,10 @@
<TextBlock Grid.Row="17" Grid.Column="0" Text="Serialize Inlined Shader Maps" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="17" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ReadShaderMaps, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/>
<TextBlock Grid.Row="18" Grid.Column="0" Text="Show Blueprint Decompile" VerticalAlignment="Center" Margin="0 5 0 5" ToolTip="Shows Decompiled Blueprints in a cpp format" />
<CheckBox Grid.Row="18" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ShowDecompileOption, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="CreatorTemplate">