diff --git a/CUE4Parse b/CUE4Parse index fc2abf20..1b823977 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit fc2abf20867c2bd0079d46cdc7eaa4f521a61376 +Subproject commit 1b82397777d33851781e96134e5afef03028b557 diff --git a/FModel/Creator/Bases/FN/BaseIconStats.cs b/FModel/Creator/Bases/FN/BaseIconStats.cs index a297b0e0..13d2bfe1 100644 --- a/FModel/Creator/Bases/FN/BaseIconStats.cs +++ b/FModel/Creator/Bases/FN/BaseIconStats.cs @@ -89,6 +89,7 @@ public class BaseIconStats : BaseIcon weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"); //Headshot multiplier weaponRowValue.TryGetValue(out int clipSize, "ClipSize"); //Item magazine size weaponRowValue.TryGetValue(out float firingRate, "FiringRate"); //Item firing rate, value is shots per second + weaponRowValue.TryGetValue(out float swingTime, "SwingTime"); //Item swing rate, value is swing per second weaponRowValue.TryGetValue(out float armTime, "ArmTime"); //Time it takes for traps to be able to be set off weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime"); //Time it takes for a weapon to reload weaponRowValue.TryGetValue(out int bpc, "BulletsPerCartridge"); //Amount of pellets shot by a weapon at once, usually for shotguns @@ -124,7 +125,7 @@ public class BaseIconStats : BaseIcon _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 40)); } - var burstEquation = cartridgePerFire / (((cartridgePerFire - 1f) / burstFiringRate) + (1f / firingRate)); + var burstEquation = cartridgePerFire != 0f && burstFiringRate != 0f ? (cartridgePerFire / (((cartridgePerFire - 1f) / burstFiringRate) + (1f / firingRate))) : 0f; if (burstEquation != 0f) { _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), burstEquation, 11)); @@ -133,6 +134,10 @@ public class BaseIconStats : BaseIcon { _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 11)); } + else if (swingTime != 0f) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), swingTime, 11)); + } if (armTime != 0f) { diff --git a/FModel/Extensions/StringExtensions.cs b/FModel/Extensions/StringExtensions.cs index cc8b523c..359ad0e0 100644 --- a/FModel/Extensions/StringExtensions.cs +++ b/FModel/Extensions/StringExtensions.cs @@ -29,10 +29,16 @@ public static partial class StringExtensions { if (KismetRegex().IsMatch(lineToFind)) return s.GetKismetLineNumber(lineToFind); + if (int.TryParse(lineToFind, out var index)) return s.GetLineNumber(index); - lineToFind = $" \"Name\": \"{lineToFind}\","; + return s.GetNameLineNumberText($" \"Name\": \"{lineToFind}\","); + } + + [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) @@ -41,7 +47,6 @@ public static partial class StringExtensions if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase)) return lineNum; } - return -1; } diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index 198d8b59..9021fa43 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -501,6 +501,30 @@ + + + + + + + + + + + + + + + + + + diff --git a/FModel/Resources/Cpp.xshd b/FModel/Resources/Cpp.xshd index c7396ae4..f61b1264 100644 --- a/FModel/Resources/Cpp.xshd +++ b/FModel/Resources/Cpp.xshd @@ -1,195 +1,150 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - [?,.;()\[\]{}+\-/%*<>^=~!&]+ - - - __abstract - __box - __delegate - __gc - __identifier - __nogc - __pin - __property - __sealed - __try_cast - __typeof - __value - __event - __hook - __raise - __unhook - __interface - ref class - ref struct - value class - value struct - interface class - interface struct - enum class - enum struct - delegate - event - property - abstract - override - sealed - generic - where - finally - for each - gcnew - in - initonly - literal - nullptr - - - this - - - and - and_eq - bitand - bitor - new - not - not_eq - or - or_eq - xor - xor_eq - - - using - namespace - - - friend - - - private - protected - public - const - volatile - static - - - bool - char - unsigned - union - virtual - double - float - short - signed - void - class - enum - struct - - - false - true - - - do - for - while - - - break - continue - goto - return - - - catch - throw - try - - - case - else - if - switch - default - - - asm - auto - compl - mutable - const_cast - delete - dynamic_cast - explicit - export - extern - inline - int - long - operator - register - reinterpret_cast - sizeof - static_cast - template - typedef - typeid - typename - - - \# - - - // - - - /\* - \*/ - - - " - " - - - - - - ' - ' - - - - - [\d\w_]+(?=(\s*\()) - \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + UCLASS + USTRUCT + UPROPERTY + UFUNCTION + GENERATED_BODY + GENERATED_USTRUCT_BODY + GENERATED_UCLASS_BODY + + + + goto + return + throw + + + + + void + int + Int8 + Int16 + Int32 + Int64 + uint + UInt16 + UInt32 + UInt64 + float + double + bool + return + if + else + for + while + do + switch + case + break + continue + namespace + using + typedef + sizeof + new + delete + class + struct + enum + template + typename + const + static + mutable + volatile + override + virtual + explicit + friend + inline + constexpr + default + + + + nullptr + + + + true + True + false + False + NULL + + + + public + protected + private + + + + this + + + + (?<=[A-Za-z0-9_>&\]])&(?=\s*[A-Za-z_<]) + + \bLabel_\d+: + + + \b(0x[0-9a-fA-F]+|[0-9]+(\.[0-9]+)?)\b + + \bU[A-Z][A-Za-z0-9_]*\b(?=::) + [A-Za-z_][A-Za-z0-9_]*\s*(?=\() + + [\[\]\{\}] + + (\/\/.*|\/\*[\s\S]*?\*\/) + + + \b[A-Za-z_][A-Za-z0-9_]*\b(?=<) + + + \b[A-Z][A-Za-z0-9_]*(?:<[^>]+>)?[*&]?(?=\s+[*&]?[A-Za-z_][A-Za-z0-9_]*\s*(=|;|\)|,)) + + + (?<=<)\s*[A-Z][A-Za-z0-9_]*(?:<[^>]+>)?[*&]?\s*(?=[>,]) + + + \b(?<=class\s)[A-Za-z_][A-Za-z0-9_]* + + + \b(?<=public\s)[A-Za-z_][A-Za-z0-9_]* + + + \b(?:T|F|U|E)[A-Z][A-Za-z0-9_]*(?:<[^>]+>)?[*&]?(?=\s+[*&]?[A-Za-z_][A-Za-z0-9_]*\s*(?:=|,|\))) + \b(?<=[,(]\s*const\s)(?:T|F|U)[A-Z][A-Za-z0-9_]*[*&]?(?=\s) + + + \b(?<=\()\s*[TUF][A-Z][A-Za-z0-9_]*(?=\s*<) + \b(?<=\()\s*[TUF][A-Z][A-Za-z0-9_]*[*&]?(?=\s) + \b(?<=\(\s*const\s)[TUF][A-Z][A-Za-z0-9_]*[*&]?(?=\s) + + + diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index b04b2831..ef577f0b 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -203,6 +203,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 { diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 62c68ba3..86e31991 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -31,7 +31,6 @@ using CUE4Parse.UE4.Localization; using CUE4Parse.UE4.Objects.Core.Serialization; using CUE4Parse.UE4.Objects.Engine; using CUE4Parse.UE4.Oodle.Objects; -using CUE4Parse.UE4.Pak; using CUE4Parse.UE4.Readers; using CUE4Parse.UE4.Shaders; using CUE4Parse.UE4.Versions; @@ -63,6 +62,8 @@ using SkiaSharp; using UE4Config.Parsing; using Application = System.Windows.Application; using FGuid = CUE4Parse.UE4.Objects.Core.Misc.FGuid; +using CUE4Parse.UE4.Objects.UObject.Editor; + namespace FModel.ViewModels; @@ -361,7 +362,7 @@ public class CUE4ParseViewModel : ViewModel { var mappingsFolder = Path.Combine(UserSettings.Default.OutputDirectory, ".data"); if (endpoint.Path == "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']") endpoint.Path = "$.[0].['url','fileName']"; - var mappings = _apiEndpointView.DynamicApi.GetMappings(default, endpoint.Url, endpoint.Path); + var mappings = _apiEndpointView.DynamicApi.GetMappings(CancellationToken.None, endpoint.Url, endpoint.Path); if (mappings is { Length: > 0 }) { foreach (var mapping in mappings) @@ -436,7 +437,7 @@ public class CUE4ParseViewModel : ViewModel var ioStoreOnDemandPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud", inst[0].Value.SubstringAfterLast("/").SubstringBefore("\"")); if (!File.Exists(ioStoreOnDemandPath)) return; - await _apiEndpointView.EpicApi.VerifyAuth(default); + await _apiEndpointView.EpicApi.VerifyAuth(CancellationToken.None); await Provider.RegisterVfs(new IoChunkToc(ioStoreOnDemandPath), new IoStoreOnDemandOptions { ChunkBaseUri = new Uri("https://download.epicgames.com/ias/fortnite/", UriKind.Absolute), @@ -453,6 +454,7 @@ public class CUE4ParseViewModel : ViewModel public int LocalizedResourcesCount { get; set; } public bool LocalResourcesDone { get; set; } public bool HotfixedResourcesDone { get; set; } + public async Task LoadLocalizedResources() { var snapshot = LocalizedResourcesCount; @@ -466,6 +468,7 @@ public class CUE4ParseViewModel : ViewModel Utils.Typefaces = new Typefaces(this); } } + private Task LoadGameLocalizedResources() { if (LocalResourcesDone) return Task.CompletedTask; @@ -474,12 +477,13 @@ public class CUE4ParseViewModel : ViewModel LocalResourcesDone = Provider.TryChangeCulture(Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); }); } + private Task LoadHotfixedLocalizedResources() { if (!Provider.ProjectName.Equals("fortnitegame", StringComparison.OrdinalIgnoreCase) || HotfixedResourcesDone) return Task.CompletedTask; return Task.Run(() => { - var hotfixes = ApplicationService.ApiEndpointView.CentralApi.GetHotfixes(default, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); + var hotfixes = ApplicationService.ApiEndpointView.CentralApi.GetHotfixes(CancellationToken.None, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); if (hotfixes == null) return; Provider.Internationalization.Override(hotfixes); @@ -606,17 +610,30 @@ public class CUE4ParseViewModel : ViewModel break; } case "upluginmanifest": + case "code-workspace": + case "projectstore": + case "uefnproject": case "uproject": case "manifest": case "uplugin": case "archive": case "dnearchive": // Banishers: Ghosts of New Eden + case "gitignore": + case "LICENSE": + case "template": case "stUMeta": // LIS: Double Exposure case "vmodule": + case "glslfx": + case "cptake": case "uparam": // Steel Hunters + case "spi1d": case "verse": case "html": case "json": + case "uref": + case "cube": + case "usda": + case "ocio": case "ini": case "txt": case "log": @@ -634,9 +651,15 @@ public class CUE4ParseViewModel : ViewModel case "pem": case "tps": case "tgc": // State of Decay 2 + case "cpp": + case "apx": + case "udn": + case "doc": case "lua": + case "vdf": case "js": case "po": + case "md": case "h": { var data = Provider.SaveAsset(entry); @@ -695,8 +718,13 @@ public class CUE4ParseViewModel : ViewModel break; } case "xvag": + case "flac": case "at9": case "wem": + case "wav": + case "WAV": + case "ogg": + // todo: CSCore.MediaFoundation.MediaFoundationException The byte stream type of the given URL is unsupported. case "aif": { var data = Provider.SaveAsset(entry); SaveAndPlaySound(entry.PathWithoutExtension, entry.Extension, data); @@ -762,6 +790,14 @@ public class CUE4ParseViewModel : ViewModel break; } + case "stinfo": + { + var archive = entry.CreateReader(); + var ar = new FShaderTypeHashes(archive); + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(ar, Formatting.Indented), saveProperties, updateUi); + + break; + } case "res": // just skip case "luac": // compiled lua case "bytes": // wuthering waves @@ -796,7 +832,7 @@ public class CUE4ParseViewModel : ViewModel } } - private bool CheckExport(CancellationToken cancellationToken, IPackage pkg, int index, EBulkType bulk = EBulkType.None) // return true once you wanna stop searching for exports + private bool CheckExport(CancellationToken cancellationToken, IPackage pkg, int index, EBulkType bulk = EBulkType.None) // return true once you want to stop searching for exports { var isNone = bulk == EBulkType.None; var updateUi = !HasFlag(bulk, EBulkType.Auto); @@ -968,6 +1004,51 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(package, Formatting.Indented), false, false); } + public void Decompile(GameFile entry) + { + if (TabControl.CanAddTabs) TabControl.AddTab(entry); + else TabControl.SelectedTab.SoftReset(entry); + + TabControl.SelectedTab.TitleExtra = "Decompiled"; + TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("cpp"); + + UClassCookedMetaData cookedMetaData = null; + try + { + var editorPkg = Provider.LoadPackage(entry.Path.Replace(".uasset", ".o.uasset")); + cookedMetaData = editorPkg.GetExport("CookedClassMetaData"); + } + catch + { + // ignored + } + + var cppList = new List(); + var pkg = Provider.LoadPackage(entry); + for (var i = 0; i < pkg.ExportMapLength; i++) + { + var pointer = new FPackageIndex(pkg, i + 1).ResolvedObject; + if (pointer?.Object is null && pointer.Class?.Object?.Value is null) + continue; + + var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class?.Object?.Value as UStruct, pkg); + if (dummy is not UClass || pointer.Object.Value is not UClass blueprint) + continue; + + cppList.Add(blueprint.DecompileBlueprintToPseudo(cookedMetaData)); + } + + var cpp = cppList.Count > 1 ? string.Join("\n\n", cppList) : cppList.FirstOrDefault() ?? string.Empty; + if (entry.Path.Contains("_Verse.uasset")) + { + cpp = Regex.Replace(cpp, "__verse_0x[a-fA-F0-9]{8}_", ""); // UnmangleCasedName + } + cpp = Regex.Replace(cpp, @"CallFunc_([A-Za-z0-9_]+)_ReturnValue", "$1"); + + + TabControl.SelectedTab.SetDocumentText(cpp, false, false); + } + private void SaveAndPlaySound(string fullPath, string ext, byte[] data) { if (fullPath.StartsWith('/')) fullPath = fullPath[1..]; diff --git a/FModel/ViewModels/Commands/RightClickMenuCommand.cs b/FModel/ViewModels/Commands/RightClickMenuCommand.cs index 0801e81b..fdd2fb6b 100644 --- a/FModel/ViewModels/Commands/RightClickMenuCommand.cs +++ b/FModel/ViewModels/Commands/RightClickMenuCommand.cs @@ -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 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) { diff --git a/FModel/ViewModels/SearchViewModel.cs b/FModel/ViewModels/SearchViewModel.cs index 15585911..d488ca43 100644 --- a/FModel/ViewModels/SearchViewModel.cs +++ b/FModel/ViewModels/SearchViewModel.cs @@ -3,14 +3,23 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text.RegularExpressions; +using System.Threading.Tasks; using System.Windows.Data; using CUE4Parse.FileProvider.Objects; +using CUE4Parse.UE4.VirtualFileSystem; using FModel.Framework; namespace FModel.ViewModels; public class SearchViewModel : ViewModel { + public enum ESortSizeMode + { + None, + Ascending, + Descending + } + private string _filterText; public string FilterText { @@ -32,6 +41,13 @@ public class SearchViewModel : ViewModel set => SetProperty(ref _hasMatchCaseEnabled, value); } + private ESortSizeMode _currentSortSizeMode = ESortSizeMode.None; + public ESortSizeMode CurrentSortSizeMode + { + get => _currentSortSizeMode; + set => SetProperty(ref _currentSortSizeMode, value); + } + public int ResultsCount => SearchResults?.Count ?? 0; public RangeObservableCollection SearchResults { get; } public ICollectionView SearchResultsView { get; } @@ -39,15 +55,57 @@ public class SearchViewModel : ViewModel public SearchViewModel() { SearchResults = new RangeObservableCollection(); - SearchResultsView = new ListCollectionView(SearchResults); + SearchResultsView = new ListCollectionView(SearchResults) + { + Filter = e => ItemFilter(e, FilterText?.Trim().Split(' ') ?? []), + }; } public void RefreshFilter() { - if (SearchResultsView.Filter == null) - SearchResultsView.Filter = e => ItemFilter(e, FilterText.Trim().Split(' ')); - else - SearchResultsView.Refresh(); + SearchResultsView.Refresh(); + } + + public async Task CycleSortSizeMode() + { + CurrentSortSizeMode = CurrentSortSizeMode switch + { + ESortSizeMode.None => ESortSizeMode.Descending, + ESortSizeMode.Descending => ESortSizeMode.Ascending, + _ => ESortSizeMode.None + }; + + var sorted = await Task.Run(() => + { + var archiveDict = SearchResults + .OfType() + .Select(f => f.Vfs.Name) + .Distinct() + .Select((name, idx) => (name, idx)) + .ToDictionary(x => x.name, x => x.idx); + + var keyed = SearchResults.Select(f => + { + int archiveKey = f is VfsEntry ve && archiveDict.TryGetValue(ve.Vfs.Name, out var key) ? key : -1; + return (File: f, f.Size, ArchiveKey: archiveKey); + }); + + return CurrentSortSizeMode switch + { + ESortSizeMode.Ascending => keyed + .OrderBy(x => x.Size).ThenBy(x => x.ArchiveKey) + .Select(x => x.File).ToList(), + ESortSizeMode.Descending => keyed + .OrderByDescending(x => x.Size).ThenBy(x => x.ArchiveKey) + .Select(x => x.File).ToList(), + _ => keyed + .OrderBy(x => x.ArchiveKey).ThenBy(x => x.File.Path, StringComparer.OrdinalIgnoreCase) + .Select(x => x.File).ToList() + }; + }); + + SearchResults.Clear(); + SearchResults.AddRange(sorted.ToList()); } private bool ItemFilter(object item, IEnumerable filters) diff --git a/FModel/Views/Resources/Controls/Aed/JumpElementGenerator.cs b/FModel/Views/Resources/Controls/Aed/JumpElementGenerator.cs new file mode 100644 index 00000000..aa4963a6 --- /dev/null +++ b/FModel/Views/Resources/Controls/Aed/JumpElementGenerator.cs @@ -0,0 +1,34 @@ +using System.Text.RegularExpressions; +using ICSharpCode.AvalonEdit.Rendering; + +namespace FModel.Views.Resources.Controls; + +public class JumpElementGenerator : VisualLineElementGenerator +{ + private readonly Regex _JumpRegex = new( + @"\b(?:goto\s+Label_(?'target'\d+);)", + 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); + } +} diff --git a/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs new file mode 100644 index 00000000..41034e3e --- /dev/null +++ b/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs @@ -0,0 +1,75 @@ +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 +{ + 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 += jump => + { + var lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumberText($" Label_{jump}:"); // impossible for different indentation + if (lineNumber > -1) + { + var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); + AvalonEditor.YesWeEditor.Select(line.Offset, line.Length); + AvalonEditor.YesWeEditor.ScrollToLine(lineNumber); + } + }; + return a; + } + +} diff --git a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs index cdf50ae0..db81c600 100644 --- a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs +++ b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs @@ -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; diff --git a/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs b/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs index 3bb5a275..ed4b3ff7 100644 --- a/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs +++ b/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs @@ -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); diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index c960da54..69a239d0 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -70,6 +70,7 @@ M1.8 6q-.525 0-.887-.35Q.55 5.3.55 4.8V4q0-1.425 1.012-2.438Q2.575.55 4 .55h.8q.5 0 .85.362.35.363.35.888 0 .5-.35.85T4.8 3H4q-.425 0-.712.287Q3 3.575 3 4v.8q0 .5-.35.85T1.8 6ZM4 23.45q-1.425 0-2.438-1.012Q.55 21.425.55 20v-.8q0-.5.363-.85.362-.35.887-.35.5 0 .85.35t.35.85v.8q0 .425.288.712Q3.575 21 4 21h.8q.5 0 .85.35t.35.85q0 .525-.35.887-.35.363-.85.363Zm15.2 0q-.5 0-.85-.363-.35-.362-.35-.887 0-.5.35-.85t.85-.35h.8q.425 0 .712-.288Q21 20.425 21 20v-.8q0-.5.35-.85t.85-.35q.525 0 .888.35.362.35.362.85v.8q0 1.425-1.012 2.438Q21.425 23.45 20 23.45ZM22.2 6q-.5 0-.85-.35T21 4.8V4q0-.425-.288-.713Q20.425 3 20 3h-.8q-.5 0-.85-.35T18 1.8q0-.525.35-.888.35-.362.85-.362h.8q1.425 0 2.438 1.012Q23.45 2.575 23.45 4v.8q0 .5-.362.85-.363.35-.888.35ZM12 17.35l1-.575v-4.1l3.55-2.075V9.425l-1-.575L12 10.925 8.45 8.85l-1 .575V10.6L11 12.675v4.1Zm-1.325 2.325-4.55-2.65q-.625-.35-.975-.963-.35-.612-.35-1.337V9.45q0-.725.35-1.337.35-.613.975-.963l4.55-2.65Q11.3 4.15 12 4.15t1.325.35l4.55 2.65q.625.35.975.963.35.612.35 1.337v5.275q0 .725-.35 1.337-.35.613-.975.963l-4.55 2.65q-.625.35-1.325.35t-1.325-.35Z M3.5 1.75v11.5c0 .09.048.173.126.217a.75.75 0 0 1-.752 1.298A1.748 1.748 0 0 1 2 13.25V1.75C2 .784 2.784 0 3.75 0h5.586c.464 0 .909.185 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0 1 12.25 15h-.5a.75.75 0 0 1 0-1.5h.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177L9.513 1.573a.25.25 0 0 0-.177-.073H7.25a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5h-3a.25.25 0 0 0-.25.25Zm3.75 8.75h.5c.966 0 1.75.784 1.75 1.75v3a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1-.75-.75v-3c0-.966.784-1.75 1.75-1.75ZM6 5.25a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 6 5.25Zm.75 2.25h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 6.75A.75.75 0 0 1 8.75 6h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 6.75ZM8.75 3h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 9.75A.75.75 0 0 1 8.75 9h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 9.75Zm-1 2.5v2.25h1v-2.25a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25Z M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z + M8 16H4l6 6V2H8zm6-11v17h2V8h4l-6-6z