Merge branch 'dev' into dotnet-10

This commit is contained in:
Marlon 2026-01-27 14:22:24 +01:00
commit 0e54e9025f
No known key found for this signature in database
GPG Key ID: 5C65CA190C38792F
194 changed files with 7183 additions and 3645 deletions

@ -1 +1 @@
Subproject commit 7772c6ccf0f6f195876d20d4b5d49fe533fc564e
Subproject commit 6865562475cb0661843438c4a20bd8748ba9c5e2

View File

@ -1,4 +1,4 @@
<Application x:Class="FModel.App"
<Application x:Class="FModel.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
@ -9,6 +9,14 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/AdonisUI;component/ColorSchemes/Dark.xaml"/>
<ResourceDictionary Source="pack://application:,,,/AdonisUI.ClassicTheme;component/Resources.xaml"/>
<ResourceDictionary Source="Views/Resources/Colors.xaml" />
<ResourceDictionary Source="Views/Resources/Icons.xaml" />
<ResourceDictionary Source="Views/Resources/Controls/ContextMenus/FileContextMenu.xaml" />
<ResourceDictionary Source="Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml" />
<ResourceDictionary Source="Views/Resources/Resources.xaml" />
<ResourceDictionary Source="Views/Resources/Controls/TiledExplorer/Resources.xaml" />
</ResourceDictionary.MergedDictionaries>
<Color x:Key="{x:Static adonisUi:Colors.AccentColor}">#206BD4</Color>

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using System.Numerics;
@ -13,6 +13,7 @@ public static class Constants
public static readonly string APP_VERSION = FileVersionInfo.GetVersionInfo(APP_PATH).FileVersion;
public static readonly string APP_COMMIT_ID = FileVersionInfo.GetVersionInfo(APP_PATH).ProductVersion?.SubstringAfter('+');
public static readonly string APP_SHORT_COMMIT_ID = APP_COMMIT_ID[..7];
public static readonly DateTime APP_BUILD_DATE = File.GetLastWriteTime(APP_PATH);
public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000";
public static readonly FGuid ZERO_GUID = new(0U);

View File

@ -1,320 +1,320 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Conversion.Textures;
using FModel.Settings;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseIcon : UCreator
{
public SKBitmap SeriesBackground { get; protected set; }
protected string ShortDescription { get; set; }
protected string CosmeticSource { get; set; }
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) { }
public void ParseForReward(bool isUsingDisplayAsset)
{
// rarity
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer")) GetSeries(componentContainer);
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
{
GetSeries(dataList);
Preview = Utils.GetBitmap(dataList);
}
// preview
if (Preview is null)
{
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
Preview = preview;
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
Preview = Utils.GetBitmap(itemDefinition);
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "BundleImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
Preview = Utils.GetBitmap(largePreview);
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
Preview = Utils.GetBitmap(s);
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
Preview = Utils.GetBitmap(otherPreview);
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
Preview = Utils.GetBitmap(materialInstancePreview);
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
Preview = Utils.GetBitmap(res);
}
// text
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "ItemDescription", "SetDescription", "BundleDescription", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
Description = description.Text;
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
Description = string.Join('\n', descriptions.Select(x => x.Text));
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
ShortDescription = shortDescription.Text;
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
// Only works on non-cataba designs
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
{
Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) };
Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) };
}
Description = Utils.RemoveHtmlTags(Description);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
CheckGameplayTags(dataList);
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
CosmeticSource = cosmeticItem.Name;
}
protected void Draw(SKCanvas c)
{
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
case EIconStyle.NoText:
DrawBackground(c);
DrawPreview(c);
DrawUserFacingFlags(c);
break;
default:
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
if (Description != ShortDescription)
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
DrawUserFacingFlags(c);
break;
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
Draw(c);
return new[] { ret };
}
private void GetSeries(FPackageIndex s)
{
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
GetSeries(export);
}
private void GetSeries(FInstancedStruct[] s)
{
if (s.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
GetSeries(dl.NonConstStruct?.Get<FPackageIndex>("Series"));
}
private void GetSeries(FStructFallback s)
{
if (!s.TryGetValue(out FPackageIndex[] components, "Components")) return;
if (components.FirstOrDefault(c => c.Name.Contains("Series")) is not { } seriesDef ||
!seriesDef.TryLoad(out var seriesDefObj) || seriesDefObj is null ||
!seriesDefObj.TryGetValue(out UObject series, "Series")) return;
GetSeries(series);
}
protected void GetSeries(UObject uObject)
{
if (uObject is UTexture2D texture2D)
{
SeriesBackground = texture2D.Decode().ToSkBitmap();
return;
}
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
{
SeriesBackground = Utils.GetBitmap(backgroundTexture);
}
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
colors.TryGetValue(out FLinearColor color1, "Color1") &&
colors.TryGetValue(out FLinearColor color2, "Color2") &&
colors.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
if (uObject.Name.Equals("PlatformSeries") &&
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
{
foreach (var vectorParameter in material.VectorParameterValues)
{
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
continue;
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
}
}
}
private void GetRarity(EFortRarity r)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
data.TryGetValue(out FLinearColor color1, "Color1") &&
data.TryGetValue(out FLinearColor color2, "Color2") &&
data.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
}
protected string GetCosmeticSet(string setName)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
return string.Empty;
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
return string.Empty;
var name = string.Empty;
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
name = displayName.Text;
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership", "\nPart of the <SetName>{0}</> set.");
return Utils.RemoveHtmlTags(string.Format(format, name));
}
protected (string, string, bool) GetInternalSID(string number)
{
if (!Utils.TryLoadObject("FortniteGame/Plugins/GameFeatures/BattlePassBase/Content/DataTables/Athena_SeasonTitles.Athena_SeasonTitles", out UDataTable seasonTitles) ||
!seasonTitles.TryGetDataTableRow(number, StringComparison.InvariantCulture, out var row) ||
!row.TryGetValue(out FText chapterText, "DisplayChapterText") ||
!row.TryGetValue(out FText seasonText, "DisplaySeasonText") ||
!row.TryGetValue(out FName displayType, "DisplayType"))
return (string.Empty, string.Empty, true);
var onlySeason = displayType.Text.EndsWith("::OnlySeason") || (chapterText.Text == seasonText.Text && !int.TryParse(seasonText.Text, out _));
return (chapterText.Text, seasonText.Text, onlySeason);
}
protected string GetCosmeticSeason(string seasonNumber)
{
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
(string chapterIdx, string seasonIdx, bool onlySeason) = GetInternalSID(s);
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
if (onlySeason) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, seasonIdx)));
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
var d = string.Format(chapterFormat, string.Format(chapter, chapterIdx), string.Format(season, seasonIdx));
return Utils.RemoveHtmlTags(string.Format(introduced, d));
}
protected void CheckGameplayTags(FInstancedStruct[] dataList)
{
if (dataList.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FGameplayTagContainer _, "Tags") ?? false) is { NonConstStruct: not null } tags)
{
CheckGameplayTags(tags.NonConstStruct.Get<FGameplayTagContainer>("Tags"));
}
}
protected virtual void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
CosmeticSource = action.Text["Athena.ItemAction.".Length..];
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
Description += GetCosmeticSet(set.Text);
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
Description += GetCosmeticSeason(season.Text);
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
}
protected void GetUserFacingFlags(IList<string> userFacingFlags)
{
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
return;
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
return;
UserFacingFlags = new Dictionary<string, SKBitmap>(userFacingFlags.Count);
foreach (var flag in userFacingFlags)
{
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
{
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
}
else
{
foreach (var category in tertiaryCategories)
{
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
{
UserFacingFlags[flag] = Utils.GetBitmap(texture);
}
}
}
}
}
private void DrawUserFacingFlags(SKCanvas c)
{
if (UserFacingFlags == null) return;
const int size = 25;
var x = Margin * (int) 2.5;
foreach (var flag in UserFacingFlags.Values.Where(flag => flag != null))
{
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
x += size;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Conversion.Textures;
using FModel.Settings;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseIcon : UCreator
{
public SKBitmap SeriesBackground { get; protected set; }
protected string ShortDescription { get; set; }
protected string CosmeticSource { get; set; }
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) { }
public void ParseForReward(bool isUsingDisplayAsset)
{
// rarity
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer")) GetSeries(componentContainer);
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
{
GetSeries(dataList);
Preview = Utils.GetBitmap(dataList);
}
// preview
if (Preview is null)
{
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
Preview = preview;
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
Preview = Utils.GetBitmap(itemDefinition);
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "BundleImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
Preview = Utils.GetBitmap(largePreview);
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
Preview = Utils.GetBitmap(s);
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
Preview = Utils.GetBitmap(otherPreview);
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
Preview = Utils.GetBitmap(materialInstancePreview);
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
Preview = Utils.GetBitmap(res);
}
// text
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "ItemDescription", "SetDescription", "BundleDescription", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
Description = description.Text;
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
Description = string.Join('\n', descriptions.Select(x => x.Text));
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
ShortDescription = shortDescription.Text;
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
// Only works on non-cataba designs
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
{
Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) };
Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) };
}
Description = Utils.RemoveHtmlTags(Description);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
CheckGameplayTags(dataList);
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
CosmeticSource = cosmeticItem.Name;
}
protected void Draw(SKCanvas c)
{
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
case EIconStyle.NoText:
DrawBackground(c);
DrawPreview(c);
DrawUserFacingFlags(c);
break;
default:
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
if (Description != ShortDescription)
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
DrawUserFacingFlags(c);
break;
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
Draw(c);
return new[] { ret };
}
private void GetSeries(FPackageIndex s)
{
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
GetSeries(export);
}
private void GetSeries(FInstancedStruct[] s)
{
if (s.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
GetSeries(dl.NonConstStruct?.Get<FPackageIndex>("Series"));
}
private void GetSeries(FStructFallback s)
{
if (!s.TryGetValue(out FPackageIndex[] components, "Components")) return;
if (components.FirstOrDefault(c => c.Name.Contains("Series")) is not { } seriesDef ||
!seriesDef.TryLoad(out var seriesDefObj) || seriesDefObj is null ||
!seriesDefObj.TryGetValue(out UObject series, "Series")) return;
GetSeries(series);
}
protected void GetSeries(UObject uObject)
{
if (uObject is UTexture2D texture2D)
{
SeriesBackground = texture2D.Decode().ToSkBitmap();
return;
}
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
{
SeriesBackground = Utils.GetBitmap(backgroundTexture);
}
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
colors.TryGetValue(out FLinearColor color1, "Color1") &&
colors.TryGetValue(out FLinearColor color2, "Color2") &&
colors.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
if (uObject.Name.Equals("PlatformSeries") &&
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
{
foreach (var vectorParameter in material.VectorParameterValues)
{
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
continue;
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
}
}
}
private void GetRarity(EFortRarity r)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
data.TryGetValue(out FLinearColor color1, "Color1") &&
data.TryGetValue(out FLinearColor color2, "Color2") &&
data.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
}
protected string GetCosmeticSet(string setName)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
return string.Empty;
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
return string.Empty;
var name = string.Empty;
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
name = displayName.Text;
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership", "\nPart of the <SetName>{0}</> set.");
return Utils.RemoveHtmlTags(string.Format(format, name));
}
protected (string, string, bool) GetInternalSID(string number)
{
if (!Utils.TryLoadObject("FortniteGame/Plugins/GameFeatures/BattlePassBase/Content/DataTables/Athena_SeasonTitles.Athena_SeasonTitles", out UDataTable seasonTitles) ||
!seasonTitles.TryGetDataTableRow(number, StringComparison.InvariantCulture, out var row) ||
!row.TryGetValue(out FText chapterText, "DisplayChapterText") ||
!row.TryGetValue(out FText seasonText, "DisplaySeasonText") ||
!row.TryGetValue(out FName displayType, "DisplayType"))
return (string.Empty, string.Empty, true);
var onlySeason = displayType.Text.EndsWith("::OnlySeason") || (chapterText.Text == seasonText.Text && !int.TryParse(seasonText.Text, out _));
return (chapterText.Text, seasonText.Text, onlySeason);
}
protected string GetCosmeticSeason(string seasonNumber)
{
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
(string chapterIdx, string seasonIdx, bool onlySeason) = GetInternalSID(s);
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
if (onlySeason) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, seasonIdx)));
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
var d = string.Format(chapterFormat, string.Format(chapter, chapterIdx), string.Format(season, seasonIdx));
return Utils.RemoveHtmlTags(string.Format(introduced, d));
}
protected void CheckGameplayTags(FInstancedStruct[] dataList)
{
if (dataList.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FGameplayTagContainer _, "Tags") ?? false) is { NonConstStruct: not null } tags)
{
CheckGameplayTags(tags.NonConstStruct.Get<FGameplayTagContainer>("Tags"));
}
}
protected virtual void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
CosmeticSource = action.Text["Athena.ItemAction.".Length..];
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
Description += GetCosmeticSet(set.Text);
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
Description += GetCosmeticSeason(season.Text);
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
}
protected void GetUserFacingFlags(IList<string> userFacingFlags)
{
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
return;
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
return;
UserFacingFlags = new Dictionary<string, SKBitmap>(userFacingFlags.Count);
foreach (var flag in userFacingFlags)
{
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
{
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
}
else
{
foreach (var category in tertiaryCategories)
{
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
{
UserFacingFlags[flag] = Utils.GetBitmap(texture);
}
}
}
}
}
private void DrawUserFacingFlags(SKCanvas c)
{
if (UserFacingFlags == null) return;
const int size = 25;
var x = Margin * (int) 2.5;
foreach (var flag in UserFacingFlags.Values.Where(flag => flag != null))
{
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
x += size;
}
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;

View File

@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;

View File

@ -1,266 +1,267 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using CUE4Parse.UE4.Assets.Exports;
using FModel.Creator.Bases;
using FModel.Creator.Bases.FN;
using FModel.Creator.Bases.MV;
namespace FModel.Creator;
public class CreatorPackage : IDisposable
{
private string _pkgName;
private string _exportType;
private Lazy<UObject> _object;
private EIconStyle _style;
public CreatorPackage(string packageName, string exportType, Lazy<UObject> uObject, EIconStyle style)
{
_pkgName = packageName;
_exportType = exportType;
_object = uObject;
_style = style;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UCreator ConstructCreator()
{
TryConstructCreator(out var creator);
return creator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryConstructCreator([MaybeNullWhen(false)] out UCreator creator)
{
switch (_exportType)
{
// Fortnite
case "AthenaConsumableEmoteItemDefinition":
case "AthenaSkyDiveContrailItemDefinition":
case "AthenaLoadingScreenItemDefinition":
case "AthenaVictoryPoseItemDefinition":
case "AthenaPetCarrierItemDefinition":
case "AthenaMusicPackItemDefinition":
case "AthenaBattleBusItemDefinition":
case "AthenaCharacterItemDefinition":
case "AthenaMapMarkerItemDefinition":
case "AthenaBackpackItemDefinition":
case "CosmeticShoesItemDefinition":
case "CosmeticCompanionItemDefinition":
case "CosmeticCompanionReactFXItemDefinition":
case "AthenaPickaxeItemDefinition":
case "AthenaGadgetItemDefinition":
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using CUE4Parse.UE4.Assets.Exports;
using FModel.Creator.Bases;
using FModel.Creator.Bases.FN;
using FModel.Creator.Bases.MV;
namespace FModel.Creator;
public class CreatorPackage : IDisposable
{
private string _pkgName;
private string _exportType;
private Lazy<UObject> _object;
private EIconStyle _style;
public CreatorPackage(string packageName, string exportType, Lazy<UObject> uObject, EIconStyle style)
{
_pkgName = packageName;
_exportType = exportType;
_object = uObject;
_style = style;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UCreator ConstructCreator()
{
TryConstructCreator(out var creator);
return creator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryConstructCreator([MaybeNullWhen(false)] out UCreator creator)
{
// TODO: convert to a type based system
switch (_exportType)
{
// Fortnite
case "AthenaConsumableEmoteItemDefinition":
case "AthenaSkyDiveContrailItemDefinition":
case "AthenaLoadingScreenItemDefinition":
case "AthenaVictoryPoseItemDefinition":
case "AthenaPetCarrierItemDefinition":
case "AthenaMusicPackItemDefinition":
case "AthenaBattleBusItemDefinition":
case "AthenaCharacterItemDefinition":
case "AthenaMapMarkerItemDefinition":
case "AthenaBackpackItemDefinition":
case "CosmeticShoesItemDefinition":
case "CosmeticCompanionItemDefinition":
case "CosmeticCompanionReactFXItemDefinition":
case "AthenaPickaxeItemDefinition":
case "AthenaGadgetItemDefinition":
case "AthenaGliderItemDefinition":
case "AthenaHatItemDefinition":
case "AthenaSprayItemDefinition":
case "AthenaDanceItemDefinition":
case "AthenaEmojiItemDefinition":
case "AthenaItemWrapDefinition":
case "AthenaToyItemDefinition":
case "FortHeroType":
case "FortTokenType":
case "FortAbilityKit":
case "FortWorkerType":
case "RewardGraphToken":
case "JunoKnowledgeBundle":
case "FortBannerTokenType":
case "FortVariantTokenType":
case "FortDecoItemDefinition":
case "FortStatItemDefinition":
case "FortAmmoItemDefinition":
case "FortEmoteItemDefinition":
case "FortBadgeItemDefinition":
case "SparksMicItemDefinition":
case "FortAwardItemDefinition":
case "FortStackItemDefinition":
case "FortWorldItemDefinition":
case "SparksAuraItemDefinition":
case "SparksDrumItemDefinition":
case "SparksBassItemDefinition":
case "FortGadgetItemDefinition":
case "AthenaCharmItemDefinition":
case "FortPlaysetItemDefinition":
case "FortGiftBoxItemDefinition":
case "FortOutpostItemDefinition":
case "FortVehicleItemDefinition":
case "FortMissionItemDefinition":
case "FortAccountItemDefinition":
case "SparksGuitarItemDefinition":
case "FortCardPackItemDefinition":
case "FortDefenderItemDefinition":
case "FortCurrencyItemDefinition":
case "FortResourceItemDefinition":
case "FortBackpackItemDefinition":
case "FortEventQuestMapDataAsset":
case "FortBuildingItemDefinition":
case "FortItemCacheItemDefinition":
case "FortWeaponModItemDefinition":
case "FortCodeTokenItemDefinition":
case "FortSchematicItemDefinition":
case "FortAlterableItemDefinition":
case "SparksKeyboardItemDefinition":
case "FortWorldMultiItemDefinition":
case "FortAlterationItemDefinition":
case "FortExpeditionItemDefinition":
case "FortIngredientItemDefinition":
case "FortConsumableItemDefinition":
case "StWFortAccoladeItemDefinition":
case "FortAccountBuffItemDefinition":
case "FortFOBCoreDecoItemDefinition":
case "FortPlayerPerksItemDefinition":
case "FortPlaysetPropItemDefinition":
case "FortPrerollDataItemDefinition":
case "JunoRecipeBundleItemDefinition":
case "FortHomebaseNodeItemDefinition":
case "FortNeverPersistItemDefinition":
case "FortPlayerAugmentItemDefinition":
case "FortSmartBuildingItemDefinition":
case "FortGiftBoxUnlockItemDefinition":
case "FortCreativeGadgetItemDefinition":
case "FortWeaponModItemDefinitionOptic":
case "RadioContentSourceItemDefinition":
case "FortPlaysetGrenadeItemDefinition":
case "JunoWeaponCreatureItemDefinition":
case "FortEventDependentItemDefinition":
case "FortPersonalVehicleItemDefinition":
case "FortGameplayModifierItemDefinition":
case "FortHardcoreModifierItemDefinition":
case "FortWeaponModItemDefinitionMagazine":
case "FortConsumableAccountItemDefinition":
case "FortConversionControlItemDefinition":
case "FortAccountBuffCreditItemDefinition":
case "JunoBuildInstructionsItemDefinition":
case "FortCharacterCosmeticItemDefinition":
case "JunoBuildingSetAccountItemDefinition":
case "FortEventCurrencyItemDefinitionRedir":
case "FortPersistentResourceItemDefinition":
case "FortWeaponMeleeOffhandItemDefinition":
case "FortHomebaseBannerIconItemDefinition":
case "FortVehicleCosmeticsVariantTokenType":
case "JunoBuildingPropAccountItemDefinition":
case "FortCampaignHeroLoadoutItemDefinition":
case "FortConditionalResourceItemDefinition":
case "FortChallengeBundleScheduleDefinition":
case "FortDailyRewardScheduleTokenDefinition":
case "FortVehicleCosmeticsItemDefinition_Body":
case "FortVehicleCosmeticsItemDefinition_Skin":
case "FortVehicleCosmeticsItemDefinition_Wheel":
case "FortCreativeRealEstatePlotItemDefinition":
case "FortDeployableBaseCloudSaveItemDefinition":
case "FortVehicleCosmeticsItemDefinition_Booster":
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
case "FortVehicleCosmeticsItemDefinition_DriftSmoke":
case "FortVehicleCosmeticsItemDefinition_EngineAudio":
creator = _style switch
{
EIconStyle.Cataba => new BaseCommunity(_object.Value, _style, "Cataba"),
_ => new BaseIcon(_object.Value, _style)
};
return true;
case "JunoAthenaCharacterItemOverrideDefinition":
case "JunoAthenaDanceItemOverrideDefinition":
creator = new BaseJuno(_object.Value, _style);
return true;
case "FortTandemCharacterData":
creator = new BaseTandem(_object.Value, _style);
return true;
case "FortTrapItemDefinition":
case "FortSpyTechItemDefinition":
case "FortAccoladeItemDefinition":
case "FortContextTrapItemDefinition":
case "FortWeaponMeleeItemDefinition":
case "FortWeaponRangedItemDefinition":
case "FortCreativeWeaponMeleeItemDefinition":
case "FortWeaponMeleeDualWieldItemDefinition":
case "FortCreativeWeaponRangedItemDefinition":
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
creator = new BaseIconStats(_object.Value, _style);
return true;
case "FortItemSeriesDefinition":
creator = new BaseSeries(_object.Value, _style);
return true;
case "MaterialInstanceConstant"
when _pkgName.Contains("/MI_OfferImages/", StringComparison.OrdinalIgnoreCase) ||
_pkgName.Contains("/RenderSwitch_Materials/", StringComparison.OrdinalIgnoreCase) ||
_pkgName.Contains("/MI_BPTile/", StringComparison.OrdinalIgnoreCase):
creator = new BaseMaterialInstance(_object.Value, _style);
return true;
case "AthenaItemShopOfferDisplayData":
creator = new BaseOfferDisplayData(_object.Value, _style);
return true;
case "FortMtxOfferData":
creator = new BaseMtxOffer(_object.Value, _style);
return true;
case "FortPlaylistAthena":
creator = new BasePlaylist(_object.Value, _style);
return true;
case "FortFeatItemDefinition":
case "FortQuestItemDefinition":
case "FortQuestItemDefinition_Athena":
case "FortQuestItemDefinition_Campaign":
case "AthenaDailyQuestDefinition":
case "FortUrgentQuestItemDefinition":
creator = new Bases.FN.BaseQuest(_object.Value, _style);
return true;
case "FortCompendiumItemDefinition":
case "FortCompendiumBundleDefinition":
case "FortChallengeBundleItemDefinition":
creator = new BaseBundle(_object.Value, _style);
return true;
// case "AthenaSeasonItemDefinition":
// creator = new BaseSeason(_object, _style);
// return true;
case "FortItemAccessTokenType":
creator = new BaseItemAccessToken(_object.Value, _style);
return true;
case "FortCreativeOption":
case "PlaylistUserOptionEnum":
case "PlaylistUserOptionBool":
case "PlaylistUserOptionString":
case "PlaylistUserOptionIntEnum":
case "PlaylistUserOptionIntRange":
case "PlaylistUserOptionColorEnum":
case "PlaylistUserOptionFloatEnum":
case "PlaylistUserOptionFloatRange":
case "PlaylistUserTintedIconIntEnum":
case "PlaylistUserOptionPrimaryAsset":
case "PlaylistUserOptionCollisionProfileEnum":
creator = new BaseUserControl(_object.Value, _style);
return true;
// PandaGame
case "CharacterData":
creator = new BaseFighter(_object.Value, _style);
return true;
case "PerkGroup":
creator = new BasePerkGroup(_object.Value, _style);
return true;
case "StatTrackingBundleData":
case "HydraSyncedDataAsset":
case "AnnouncerPackData":
case "CharacterGiftData":
case "ProfileIconData":
case "RingOutVfxData":
case "BannerData":
case "EmoteData":
case "TauntData":
case "SkinData":
case "PerkData":
creator = new BasePandaIcon(_object.Value, _style);
return true;
case "QuestData":
creator = new Bases.MV.BaseQuest(_object.Value, _style);
return true;
default:
creator = null;
return false;
}
}
public override string ToString() => $"{_exportType} | {_style}";
public void Dispose()
{
_object = null;
}
}
case "AthenaHatItemDefinition":
case "AthenaSprayItemDefinition":
case "AthenaDanceItemDefinition":
case "AthenaEmojiItemDefinition":
case "AthenaItemWrapDefinition":
case "AthenaToyItemDefinition":
case "FortHeroType":
case "FortTokenType":
case "FortAbilityKit":
case "FortWorkerType":
case "RewardGraphToken":
case "JunoKnowledgeBundle":
case "FortBannerTokenType":
case "FortVariantTokenType":
case "FortDecoItemDefinition":
case "FortStatItemDefinition":
case "FortAmmoItemDefinition":
case "FortEmoteItemDefinition":
case "FortBadgeItemDefinition":
case "SparksMicItemDefinition":
case "FortAwardItemDefinition":
case "FortStackItemDefinition":
case "FortWorldItemDefinition":
case "SparksAuraItemDefinition":
case "SparksDrumItemDefinition":
case "SparksBassItemDefinition":
case "FortGadgetItemDefinition":
case "AthenaCharmItemDefinition":
case "FortPlaysetItemDefinition":
case "FortGiftBoxItemDefinition":
case "FortOutpostItemDefinition":
case "FortVehicleItemDefinition":
case "FortMissionItemDefinition":
case "FortAccountItemDefinition":
case "SparksGuitarItemDefinition":
case "FortCardPackItemDefinition":
case "FortDefenderItemDefinition":
case "FortCurrencyItemDefinition":
case "FortResourceItemDefinition":
case "FortBackpackItemDefinition":
case "FortEventQuestMapDataAsset":
case "FortBuildingItemDefinition":
case "FortItemCacheItemDefinition":
case "FortWeaponModItemDefinition":
case "FortCodeTokenItemDefinition":
case "FortSchematicItemDefinition":
case "FortAlterableItemDefinition":
case "SparksKeyboardItemDefinition":
case "FortWorldMultiItemDefinition":
case "FortAlterationItemDefinition":
case "FortExpeditionItemDefinition":
case "FortIngredientItemDefinition":
case "FortConsumableItemDefinition":
case "StWFortAccoladeItemDefinition":
case "FortAccountBuffItemDefinition":
case "FortFOBCoreDecoItemDefinition":
case "FortPlayerPerksItemDefinition":
case "FortPlaysetPropItemDefinition":
case "FortPrerollDataItemDefinition":
case "JunoRecipeBundleItemDefinition":
case "FortHomebaseNodeItemDefinition":
case "FortNeverPersistItemDefinition":
case "FortPlayerAugmentItemDefinition":
case "FortSmartBuildingItemDefinition":
case "FortGiftBoxUnlockItemDefinition":
case "FortCreativeGadgetItemDefinition":
case "FortWeaponModItemDefinitionOptic":
case "RadioContentSourceItemDefinition":
case "FortPlaysetGrenadeItemDefinition":
case "JunoWeaponCreatureItemDefinition":
case "FortEventDependentItemDefinition":
case "FortPersonalVehicleItemDefinition":
case "FortGameplayModifierItemDefinition":
case "FortHardcoreModifierItemDefinition":
case "FortWeaponModItemDefinitionMagazine":
case "FortConsumableAccountItemDefinition":
case "FortConversionControlItemDefinition":
case "FortAccountBuffCreditItemDefinition":
case "JunoBuildInstructionsItemDefinition":
case "FortCharacterCosmeticItemDefinition":
case "JunoBuildingSetAccountItemDefinition":
case "FortEventCurrencyItemDefinitionRedir":
case "FortPersistentResourceItemDefinition":
case "FortWeaponMeleeOffhandItemDefinition":
case "FortHomebaseBannerIconItemDefinition":
case "FortVehicleCosmeticsVariantTokenType":
case "JunoBuildingPropAccountItemDefinition":
case "FortCampaignHeroLoadoutItemDefinition":
case "FortConditionalResourceItemDefinition":
case "FortChallengeBundleScheduleDefinition":
case "FortDailyRewardScheduleTokenDefinition":
case "FortVehicleCosmeticsItemDefinition_Body":
case "FortVehicleCosmeticsItemDefinition_Skin":
case "FortVehicleCosmeticsItemDefinition_Wheel":
case "FortCreativeRealEstatePlotItemDefinition":
case "FortDeployableBaseCloudSaveItemDefinition":
case "FortVehicleCosmeticsItemDefinition_Booster":
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
case "FortVehicleCosmeticsItemDefinition_DriftSmoke":
case "FortVehicleCosmeticsItemDefinition_EngineAudio":
creator = _style switch
{
EIconStyle.Cataba => new BaseCommunity(_object.Value, _style, "Cataba"),
_ => new BaseIcon(_object.Value, _style)
};
return true;
case "JunoAthenaCharacterItemOverrideDefinition":
case "JunoAthenaDanceItemOverrideDefinition":
creator = new BaseJuno(_object.Value, _style);
return true;
case "FortTandemCharacterData":
creator = new BaseTandem(_object.Value, _style);
return true;
case "FortTrapItemDefinition":
case "FortSpyTechItemDefinition":
case "FortAccoladeItemDefinition":
case "FortContextTrapItemDefinition":
case "FortWeaponMeleeItemDefinition":
case "FortWeaponRangedItemDefinition":
case "FortCreativeWeaponMeleeItemDefinition":
case "FortWeaponMeleeDualWieldItemDefinition":
case "FortCreativeWeaponRangedItemDefinition":
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
creator = new BaseIconStats(_object.Value, _style);
return true;
case "FortItemSeriesDefinition":
creator = new BaseSeries(_object.Value, _style);
return true;
case "MaterialInstanceConstant"
when _pkgName.Contains("/MI_OfferImages/", StringComparison.OrdinalIgnoreCase) ||
_pkgName.Contains("/RenderSwitch_Materials/", StringComparison.OrdinalIgnoreCase) ||
_pkgName.Contains("/MI_BPTile/", StringComparison.OrdinalIgnoreCase):
creator = new BaseMaterialInstance(_object.Value, _style);
return true;
case "AthenaItemShopOfferDisplayData":
creator = new BaseOfferDisplayData(_object.Value, _style);
return true;
case "FortMtxOfferData":
creator = new BaseMtxOffer(_object.Value, _style);
return true;
case "FortPlaylistAthena":
creator = new BasePlaylist(_object.Value, _style);
return true;
case "FortFeatItemDefinition":
case "FortQuestItemDefinition":
case "FortQuestItemDefinition_Athena":
case "FortQuestItemDefinition_Campaign":
case "AthenaDailyQuestDefinition":
case "FortUrgentQuestItemDefinition":
creator = new Bases.FN.BaseQuest(_object.Value, _style);
return true;
case "FortCompendiumItemDefinition":
case "FortCompendiumBundleDefinition":
case "FortChallengeBundleItemDefinition":
creator = new BaseBundle(_object.Value, _style);
return true;
// case "AthenaSeasonItemDefinition":
// creator = new BaseSeason(_object, _style);
// return true;
case "FortItemAccessTokenType":
creator = new BaseItemAccessToken(_object.Value, _style);
return true;
case "FortCreativeOption":
case "PlaylistUserOptionEnum":
case "PlaylistUserOptionBool":
case "PlaylistUserOptionString":
case "PlaylistUserOptionIntEnum":
case "PlaylistUserOptionIntRange":
case "PlaylistUserOptionColorEnum":
case "PlaylistUserOptionFloatEnum":
case "PlaylistUserOptionFloatRange":
case "PlaylistUserTintedIconIntEnum":
case "PlaylistUserOptionPrimaryAsset":
case "PlaylistUserOptionCollisionProfileEnum":
creator = new BaseUserControl(_object.Value, _style);
return true;
// PandaGame
case "CharacterData":
creator = new BaseFighter(_object.Value, _style);
return true;
case "PerkGroup":
creator = new BasePerkGroup(_object.Value, _style);
return true;
case "StatTrackingBundleData":
case "HydraSyncedDataAsset":
case "AnnouncerPackData":
case "CharacterGiftData":
case "ProfileIconData":
case "RingOutVfxData":
case "BannerData":
case "EmoteData":
case "TauntData":
case "SkinData":
case "PerkData":
creator = new BasePandaIcon(_object.Value, _style);
return true;
case "QuestData":
creator = new Bases.MV.BaseQuest(_object.Value, _style);
return true;
default:
creator = null;
return false;
}
}
public override string ToString() => $"{_exportType} | {_style}";
public void Dispose()
{
_object = null;
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using FModel.Extensions;
namespace FModel;
@ -65,16 +66,6 @@ public enum ELoadingMode
AllButPatched,
}
// public enum EUpdateMode
// {
// [Description("Stable")]
// Stable,
// [Description("Beta")]
// Beta,
// [Description("QA Testing")]
// Qa
// }
public enum ECompressedAudio
{
[Description("Play the decompressed data")]
@ -113,7 +104,56 @@ public enum EBulkType
Properties = 1 << 1,
Textures = 1 << 2,
Meshes = 1 << 3,
Skeletons = 1 << 4,
Animations = 1 << 5,
Audio = 1 << 6
Animations = 1 << 4,
Audio = 1 << 5,
Code = 1 << 6,
}
public enum EAssetCategory : uint
{
All = AssetCategoryExtensions.CategoryBase + (0 << 16),
Blueprints = AssetCategoryExtensions.CategoryBase + (1 << 16),
BlueprintGeneratedClass = Blueprints + 1,
WidgetBlueprintGeneratedClass = Blueprints + 2,
AnimBlueprintGeneratedClass = Blueprints + 3,
RigVMBlueprintGeneratedClass = Blueprints + 4,
UserDefinedEnum = Blueprints + 5,
UserDefinedStruct = Blueprints + 6,
//Metadata
Blueprint = Blueprints + 8,
CookedMetaData = Blueprints + 9,
Mesh = AssetCategoryExtensions.CategoryBase + (2 << 16),
StaticMesh = Mesh + 1,
SkeletalMesh = Mesh + 2,
CustomizableObject = Mesh + 3,
NaniteDisplacedMesh = Mesh + 4,
Texture = AssetCategoryExtensions.CategoryBase + (3 << 16),
Materials = AssetCategoryExtensions.CategoryBase + (4 << 16),
Material = Materials + 1,
MaterialEditorData = Materials + 2,
MaterialFunction = Materials + 3,
MaterialFunctionEditorData = Materials + 4,
MaterialParameterCollection = Materials + 5,
Animation = AssetCategoryExtensions.CategoryBase + (5 << 16),
Skeleton = Animation + 1,
Rig = Animation + 2,
Level = AssetCategoryExtensions.CategoryBase + (6 << 16),
World = Level + 1,
BuildData = Level + 2,
LevelSequence = Level + 3,
Foliage = Level + 4,
Data = AssetCategoryExtensions.CategoryBase + (7 << 16),
ItemDefinitionBase = Data + 1,
CurveBase = Data + 2,
PhysicsAsset = Data + 3,
ObjectRedirector = Data + 4,
PhysicalMaterial = Data + 5,
ByteCode = Data + 6,
Media = AssetCategoryExtensions.CategoryBase + (8 << 16),
Audio = Media + 1,
Video = Media + 2,
Font = Media + 3,
SoundBank = Media + 4,
AudioEvent = Media + 5,
Particle = AssetCategoryExtensions.CategoryBase + (9 << 16),
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace FModel.Extensions;
public static class AssetCategoryExtensions
{
public const uint CategoryBase = 0x00010000;
public static EAssetCategory GetBaseCategory(this EAssetCategory category)
{
return (EAssetCategory) ((uint) category & 0xFFFF0000);
}
public static bool IsOfCategory(this EAssetCategory item, EAssetCategory category)
{
return item.GetBaseCategory() == category.GetBaseCategory();
}
public static bool IsBaseCategory(this EAssetCategory category)
{
return category == category.GetBaseCategory();
}
public static IEnumerable<EAssetCategory> GetBaseCategories()
{
return Enum.GetValues<EAssetCategory>().Where(c => c.IsBaseCategory());
}
}

View File

@ -1,4 +1,4 @@
using System.Reflection;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml;
using ICSharpCode.AvalonEdit.Highlighting;

View File

@ -1,4 +1,4 @@
using System;
using System;
using CUE4Parse.FileProvider;
using CUE4Parse.FileProvider.Objects;
using CUE4Parse.UE4.Assets;

View File

@ -1,4 +1,4 @@
using SkiaSharp;
using SkiaSharp;
using System;
using System.Drawing;
using System.Drawing.Imaging;

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Runtime.CompilerServices;

View File

@ -166,8 +166,9 @@
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.9" />
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
<PackageReference Include="Svg.Skia" Version="3.2.1" />
<PackageReference Include="Twizzle.ImGui-Bundle.NET" Version="1.91.5.2" />
<PackageReference Include="VirtualizingWrapPanel" Version="2.3.2" />
</ItemGroup>
<ItemGroup>

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks.Dataflow;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Windows.Input;
namespace FModel.Framework;

View File

@ -1,4 +1,4 @@
using System;
using System;
using HarfBuzzSharp;
using SkiaSharp;
using SkiaSharp.HarfBuzz;

View File

@ -1,4 +1,4 @@
namespace FModel.Framework;
namespace FModel.Framework;
public class FStatus : ViewModel
{

View File

@ -1,26 +1,22 @@
using System;
using System;
using CUE4Parse.Compression;
using CUE4Parse.FileProvider.Objects;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Readers;
namespace FModel.Framework;
public class FakeGameFile : GameFile
public class FakeGameFile(string path) : GameFile(path, 0)
{
public FakeGameFile(string path) : base(path, 0)
{
}
public override bool IsEncrypted => false;
public override CompressionMethod CompressionMethod => CompressionMethod.None;
public override byte[] Read()
public override byte[] Read(FByteBulkDataHeader? header = null)
{
throw new NotImplementedException();
}
public override FArchive CreateReader()
public override FArchive CreateReader(FByteBulkDataHeader? header = null)
{
throw new NotImplementedException();
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

View File

@ -1,4 +1,4 @@
using System.Text;
using System.Text;
using System.Windows.Input;
namespace FModel.Framework;

View File

@ -1,4 +1,4 @@
using System;
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using RestSharp;

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace FModel.Framework;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.Reflection;
using Serilog.Core;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;

View File

@ -1,4 +1,4 @@
using System;
using System;
namespace FModel.Framework;

View File

@ -38,7 +38,7 @@ public static class Helper
else
{
var w = GetOpenedWindow<T>(windowName);
if (windowName == "Search View") w.WindowState = WindowState.Normal;
if (windowName == "Search For Packages") w.WindowState = WindowState.Normal;
w.Focus();
}
}

View File

@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FModel"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:inputs="clr-namespace:FModel.Views.Resources.Controls.Inputs"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:settings="clr-namespace:FModel.Settings"
xmlns:services="clr-namespace:FModel.Services"
@ -10,8 +11,18 @@
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
WindowStartupLocation="CenterScreen" Closing="OnClosing" Loaded="OnLoaded" PreviewKeyDown="OnWindowKeyDown"
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.85'}"
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.75'}">
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.95'}"
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.90'}">
<Window.TaskbarItemInfo>
<TaskbarItemInfo ProgressValue="1.0">
<TaskbarItemInfo.ProgressState>
<MultiBinding Converter="{converters:StatusToTaskbarStateConverter}">
<Binding Path="Status.Kind" />
<Binding Path="IsActive" RelativeSource="{RelativeSource AncestorType=Window}" />
</MultiBinding>
</TaskbarItemInfo.ProgressState>
</TaskbarItemInfo>
</Window.TaskbarItemInfo>
<adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
<Setter Property="Title" Value="{Binding DataContext.InitialWindowTitle, RelativeSource={RelativeSource Self}}" />
@ -30,158 +41,179 @@
</Style.Triggers>
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Views/Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="{adonisUi:Space 1}" />
<RowDefinition Height="8" />
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="Directory">
<MenuItem Header="Selector" Command="{Binding MenuCommand}" CommandParameter="Directory_Selector">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoryIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Menu Grid.Column="0">
<MenuItem Header="Directory">
<MenuItem Header="Selector" Command="{Binding MenuCommand}" CommandParameter="Directory_Selector">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoryIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="AES" Command="{Binding MenuCommand}" CommandParameter="Directory_AES" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource KeyIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Backup" Command="{Binding MenuCommand}" CommandParameter="Directory_Backup" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource BackupIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Archives Info" Command="{Binding MenuCommand}" CommandParameter="Directory_ArchivesInfo" IsEnabled="{Binding Status.IsReady}">
<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>
</MenuItem>
<MenuItem Header="AES" Command="{Binding MenuCommand}" CommandParameter="Directory_AES" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource KeyIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Header="Packages">
<MenuItem Header="Search" IsEnabled="{Binding Status.IsReady}" InputGestureText="Ctrl+Shift+F" Click="OnSearchViewClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="References" IsEnabled="{Binding Status.IsReady}" InputGestureText="Ctrl+Shift+R" Click="OnRefViewClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Favorite Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoriesIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Backup" Command="{Binding MenuCommand}" CommandParameter="Directory_Backup" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource BackupIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Header="Views">
<MenuItem Header="3D Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_3dViewer">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource MeshIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio Player" Command="{Binding MenuCommand}" CommandParameter="Views_AudioPlayer">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Image Merger" Command="{Binding MenuCommand}" CommandParameter="Views_ImageMerger">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource ImageMergerIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Archives Info" Command="{Binding MenuCommand}" CommandParameter="Directory_ArchivesInfo" IsEnabled="{Binding Status.IsReady}">
<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 Header="Settings" Command="{Binding MenuCommand}" CommandParameter="Settings" />
<MenuItem Header="Help" >
<MenuItem Header="Donate" Command="{Binding MenuCommand}" CommandParameter="Help_Donate">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GiftIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Releases" Command="{Binding MenuCommand}" CommandParameter="Help_Releases">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GitHubIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Bugs Report" Command="{Binding MenuCommand}" CommandParameter="Help_BugsReport">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource BugIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Discord Server" Command="{Binding MenuCommand}" CommandParameter="Help_Discord">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource DiscordIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="About FModel" Command="{Binding MenuCommand}" CommandParameter="Help_About">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</MenuItem>
<MenuItem Header="Packages">
<MenuItem Header="Search" IsEnabled="{Binding Status.IsReady}" InputGestureText="Ctrl+Shift+F" Click="OnSearchViewClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Favorite Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoriesIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Views">
<MenuItem Header="3D Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_3dViewer">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource MeshIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio Player" Command="{Binding MenuCommand}" CommandParameter="Views_AudioPlayer">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Image Merger" Command="{Binding MenuCommand}" CommandParameter="Views_ImageMerger">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource ImageMergerIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Settings" Command="{Binding MenuCommand}" CommandParameter="Settings" />
<MenuItem Header="Help" >
<MenuItem Header="Donate" Command="{Binding MenuCommand}" CommandParameter="Help_Donate">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GiftIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Releases" Command="{Binding MenuCommand}" CommandParameter="Help_Releases">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GitHubIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Bugs Report" Command="{Binding MenuCommand}" CommandParameter="Help_BugsReport">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource BugIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Discord Server" Command="{Binding MenuCommand}" CommandParameter="Help_Discord">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource DiscordIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="About FModel" Command="{Binding MenuCommand}" CommandParameter="Help_About">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
</Menu>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Preview New Explorer System" VerticalAlignment="Center" />
<CheckBox Grid.Column="1" Margin="5 2 5 0" Unchecked="FeaturePreviewOnUnchecked" KeyboardNavigation.TabNavigation="None" KeyboardNavigation.ControlTabNavigation="None"
IsChecked="{Binding FeaturePreviewNewAssetExplorer, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"/>
</Grid>
</Grid>
<Grid x:Name="RootGrid" Grid.Row="2">
<Grid.ColumnDefinitions>
@ -191,8 +223,8 @@
</Grid.ColumnDefinitions>
<GroupBox Grid.Column="0" adonisExtensions:LayerExtension.Layer="2"
Padding="{adonisUi:Space 0}" Background="Transparent">
<TabControl x:Name="LeftTabControl" SelectionChanged="OnTabItemChange">
Padding="0" Background="Transparent">
<TabControl x:Name="LeftTabControl" SelectionChanged="OnTabItemChange" SelectedIndex="{Binding SelectedLeftTabIndex, Mode=TwoWay}">
<TabItem Style="{StaticResource TabItemFillSpace}" Header="Archives">
<DockPanel>
<Grid DockPanel.Dock="Top">
@ -257,7 +289,8 @@
</Grid>
</DockPanel>
</TabItem>
<TabItem Style="{StaticResource TabItemFillSpace}" Header="Folders">
<TabItem Style="{StaticResource TabItemFillSpace}"
Header="Folders">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@ -315,94 +348,12 @@
</StackPanel>
</Grid>
<Separator Grid.Row="1" Style="{StaticResource CustomSeparator}" Margin="0" />
<TreeView Grid.Row="2" x:Name="AssetsFolderName" Style="{StaticResource AssetsFolderTreeView}" PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick">
<TreeView.ContextMenu>
<ContextMenu>
<!-- <MenuItem Header="Extract Folder's Packages" Click="OnFolderExtractClick"> -->
<!-- <MenuItem.Icon> -->
<!-- <Viewbox Width="16" Height="16"> -->
<!-- <Canvas Width="24" Height="24"> -->
<!-- <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExtractIcon}" /> -->
<!-- </Canvas> -->
<!-- </Viewbox> -->
<!-- </MenuItem.Icon> -->
<!-- </MenuItem> -->
<!-- <Separator /> -->
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)" Click="OnFolderExportClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Properties (.json)" Click="OnFolderSaveClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Textures" Click="OnFolderTextureClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Models" Click="OnFolderModelClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Animations" Click="OnFolderAnimationClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Audio" Click="OnFolderAudioClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Favorite Directory" Click="OnFavoriteDirectoryClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoriesAddIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Copy Directory Path" Click="OnCopyDirectoryPathClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView Grid.Row="2"
x:Name="AssetsFolderName"
Style="{StaticResource AssetsFolderTreeView}"
SelectedItemChanged="OnAssetsTreeSelectedItemChanged"
PreviewKeyDown="OnFoldersPreviewKeyDown"
PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick">
</TreeView>
<Separator Grid.Row="3" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
<StackPanel Grid.Row="4" Orientation="Vertical" Margin="0 0 0 5">
@ -437,32 +388,11 @@
Header="{Binding SelectedItem.AssetsList.Assets.Count, FallbackValue=0, ElementName=AssetsFolderName}"
HeaderStringFormat="{}{0} Packages">
<DockPanel>
<Grid DockPanel.Dock="Top" ZIndex="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" ZIndex="1" HorizontalAlignment="Left" Margin="5 2 0 0">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
</Canvas>
</Viewbox>
</Grid>
<TextBox Grid.Column="0" Grid.ColumnSpan="2" x:Name="AssetsSearchName" AcceptsTab="False" AcceptsReturn="False"
Padding="25 0 0 0" HorizontalAlignment="Stretch" TextChanged="OnFilterTextChanged"
adonisExtensions:WatermarkExtension.Watermark="Search by name..." />
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button ToolTip="Clear Search Filter" Padding="5" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" Click="OnDeleteSearchClick">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource BackspaceIcon}"/>
</Canvas>
</Viewbox>
</Button>
</StackPanel>
</Grid>
<inputs:SearchTextBox DockPanel.Dock="Top" x:Name="AssetsSearchTextBox"
Text="{Binding SelectedItem.SearchText, ElementName=AssetsFolderName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ClearButtonClick="OnClearFilterClick" />
<Grid DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@ -471,217 +401,15 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<controls:Breadcrumb Grid.Row="0" MaxWidth="{Binding ActualWidth, ElementName=AssetsSearchName}" HorizontalAlignment="Left" Margin="0 5 0 5"
<controls:Breadcrumb Grid.Row="0" HorizontalAlignment="Left" Margin="0 5 0 5"
MaxWidth="{Binding ActualWidth, ElementName=AssetsSearchTextBox}"
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.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Extract in New Tab" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Extract_New_Tab" />
<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 ExtractIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Show Metadata" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Show_Metadata" />
<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>
<MenuItem Header="Decompile Blueprint" Command="{Binding DataContext.RightClickMenuCommand}">
<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.Style>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Style.Triggers>
<DataTrigger Binding="{Binding ShowDecompileOption, Source={x:Static settings:UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<Separator />
<MenuItem Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock
Text="{Binding DataContext.SelectedItem.Extension,
FallbackValue='Export Raw Data',
StringFormat='Export Raw Data (.{0})',
RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Export_Data" />
<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 ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Properties" />
<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 SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Textures" />
<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 TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Model" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Models" />
<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 ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Animation" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Animations" />
<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 AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Audio" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Audio" />
<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 AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Copy">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Header="Package Path" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Path" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
<MenuItem Header="Package Name" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Name" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
<MenuItem Header="Directory Path" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Directory_Path" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
<MenuItem Header="Package Path w/o Extension" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Path_No_Extension" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
<MenuItem Header="Package Name w/o Extension" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Name_No_Extension" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
<ListBox Grid.Row="1" x:Name="AssetsListName"
Style="{StaticResource AssetsListBox}"
PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick"
PreviewKeyDown="OnPreviewKeyDown" />
<Separator Grid.Row="2" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
<StackPanel Grid.Row="3" Orientation="Vertical" Margin="0 0 0 5">
<Grid HorizontalAlignment="Stretch">
@ -697,15 +425,15 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.Offset, ElementName=AssetsListName, FallbackValue=0, StringFormat='{}0x{0:X}'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.Asset.Offset, ElementName=AssetsListName, FallbackValue=0, StringFormat='{}0x{0:X}'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Offset" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.Size, ElementName=AssetsListName, FallbackValue=0, Converter={x:Static converters:SizeToStringConverter.Instance}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.Asset.Size, ElementName=AssetsListName, FallbackValue=0, Converter={x:Static converters:SizeToStringConverter.Instance}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="Size" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.CompressionMethod, ElementName=AssetsListName, FallbackValue='Unknown'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.Asset.CompressionMethod, ElementName=AssetsListName, FallbackValue='Unknown'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="Compression Method" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.IsEncrypted, ElementName=AssetsListName, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.Asset.IsEncrypted, ElementName=AssetsListName, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="Is Encrypted" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Vfs.Name, ElementName=AssetsListName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Asset.Vfs.Name, ElementName=AssetsListName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
</Grid>
</StackPanel>
@ -720,14 +448,130 @@
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}" />
<GroupBox Grid.Column="2" adonisExtensions:LayerExtension.Layer="2"
Padding="{adonisUi:Space 0}" Background="Transparent">
Padding="0" Background="Transparent">
<Grid Margin="0 0 3 0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl Grid.Row="0" x:Name="TabControlName" Style="{StaticResource GameFilesTabControl}" />
<Grid Grid.Row="0">
<Border BorderThickness="1" Padding="10"
BorderBrush="{DynamicResource {x:Static adonisUi:Brushes.Layer3BorderBrush}}"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
Visibility="{Binding IsAssetsExplorerVisible, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" adonisExtensions:LayerExtension.Layer="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" MinWidth="150" />
</Grid.ColumnDefinitions>
<inputs:SearchTextBox x:Name="AssetsExplorerSearch" Grid.Column="0"
Text="{Binding SelectedItem.SearchText, ElementName=AssetsFolderName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ClearButtonClick="OnClearFilterClick" />
<ComboBox x:Name="CategoriesSelector" Grid.Column="2"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedItem.SelectedCategory, ElementName=AssetsFolderName, Mode=TwoWay}" />
</Grid>
<controls:Breadcrumb Grid.Row="1" Margin="0 5 0 0"
HorizontalAlignment="Left"
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName}" />
<ListBox x:Name="AssetsExplorer" Grid.Row="2"
ItemsSource="{Binding SelectedItem.CombinedEntries, ElementName=AssetsFolderName, IsAsync=True}"
Style="{StaticResource TiledExplorer}"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
PreviewKeyDown="OnPreviewKeyDown" />
</Grid>
</Border>
<TabControl x:Name="TabControlName"
Style="{StaticResource GameFilesTabControl}"
Visibility="{Binding IsAssetsExplorerVisible, Converter={x:Static converters:InvertBoolToVisibilityConverter.Instance}, ConverterParameter=Hidden}" />
</Grid>
<Border Grid.Row="0"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Margin="12,12,20,12"
Opacity="0.5"
Visibility="{Binding FeaturePreviewNewAssetExplorer, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer2BackgroundBrush}}"
CornerRadius="8"
Padding="4"
Effect="{DynamicResource ShadowEffect}">
<Border.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To="0.5"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center">
<ToggleButton Width="32"
Height="32"
Cursor="Hand"
Margin="0,0,2,0"
IsChecked="{Binding IsAssetsExplorerVisible, Mode=TwoWay}"
Style="{StaticResource AssetsExplorerToggleButtonStyle}" />
<ToggleButton Width="32"
Height="32"
Cursor="Hand"
Checked="OnPreviewTexturesToggled"
Focusable="False"
IsChecked="{Binding PreviewTexturesAssetExplorer, Source={x:Static settings:UserSettings.Default}, Mode=TwoWay}">
<ToggleButton.Style>
<Style TargetType="ToggleButton"
BasedOn="{StaticResource ModernToggleButtonStyle}">
<Setter Property="ToolTip"
Value="Preview Textures (OFF)" />
<Style.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter Property="Background"
Value="MediumPurple" />
<Setter Property="ToolTip"
Value="Preview Textures (ON)" />
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Data="{StaticResource TextureIconAlt}"
Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</Canvas>
</Viewbox>
</ToggleButton>
</StackPanel>
</Border>
<Expander Grid.Row="1" Margin="0 5 0 5" ExpandDirection="Down"
IsExpanded="{Binding IsLoggerExpanded, Source={x:Static settings:UserSettings.Default}}">
<Grid>
@ -740,7 +584,7 @@
<controls:CustomRichTextBox Grid.Column="0" Grid.ColumnSpan="3" x:Name="LogRtbName" Style="{StaticResource CustomRichTextBox}" />
<StackPanel Grid.Column="2" Orientation="Vertical" VerticalAlignment="Bottom" Margin="-5 -5 5 5">
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Open Output Folder" Padding="0,4,0,4"
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Open_Output_Directory">
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Open_Output_Directory" Focusable="False">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource FolderIcon}" />
@ -748,7 +592,7 @@
</Viewbox>
</Button>
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Clear Logs" Padding="0,4,0,4"
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Clear_Logs">
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Clear_Logs" Focusable="False">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TrashIcon}"/>

View File

@ -4,9 +4,8 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using AdonisUI.Controls;
using CUE4Parse.FileProvider.Objects;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
@ -30,14 +29,54 @@ public partial class MainWindow
{
CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection { new KeyGesture(Key.F12) }), OnMappingsReload));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (_, _) => OnOpenAvalonFinder()));
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseBack, (_, _) =>
{
if (UserSettings.Default.FeaturePreviewNewAssetExplorer && !_applicationView.IsAssetsExplorerVisible)
{
// back browsing the json view will reopen the assets explorer
_applicationView.IsAssetsExplorerVisible = true;
return;
}
if (LeftTabControl.SelectedIndex == 2)
{
LeftTabControl.SelectedIndex = 1;
}
else if (LeftTabControl.SelectedIndex == 1 && AssetsFolderName.SelectedItem is TreeItem { Parent: TreeItem parent })
{
AssetsFolderName.Focus();
parent.IsSelected = true;
}
}));
DataContext = _applicationView;
InitializeComponent();
AssetsExplorer.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
AssetsListName.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
AssetsExplorer.SelectionChanged += (_, e) => SyncSelection(AssetsListName, e);
AssetsListName.SelectionChanged += (_, e) => SyncSelection(AssetsExplorer, e);
FLogger.Logger = LogRtbName;
YesWeCats = this;
}
// Hack to sync selection between packages tab and explorer
private void SyncSelection(ListBox target, SelectionChangedEventArgs e)
{
foreach (var added in e.AddedItems.OfType<GameFileViewModel>())
{
if (!target.SelectedItems.Contains(added))
target.SelectedItems.Add(added);
}
foreach (var removed in e.RemovedItems.OfType<GameFileViewModel>())
{
if (target.SelectedItems.Contains(removed))
target.SelectedItems.Remove(removed);
}
}
private void OnClosing(object sender, CancelEventArgs e)
{
_discordHandler.Dispose();
@ -61,14 +100,18 @@ public partial class MainWindow
break;
}
await ApplicationViewModel.InitOodle();
await ApplicationViewModel.InitZlib();
await Task.WhenAll(
ApplicationViewModel.InitOodle(),
ApplicationViewModel.InitZlib()
);
await _applicationView.CUE4Parse.Initialize();
await _applicationView.AesManager.InitAes();
await _applicationView.UpdateProvider(true);
#if !DEBUG
await _applicationView.CUE4Parse.InitInformation();
#endif
await Task.WhenAll(
_applicationView.CUE4Parse.VerifyConsoleVariables(),
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
@ -107,10 +150,33 @@ public partial class MainWindow
}
else if (_applicationView.Status.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
OnSearchViewClick(null, null);
else if (e.Key == Key.Left && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage)
else if (_applicationView.Status.IsReady && e.Key == Key.R && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
OnRefViewClick(null, null);
else if (e.Key == Key.F3)
OnOpenAvalonFinder();
else if (e.Key == Key.Left && !_applicationView.IsAssetsExplorerVisible && _applicationView.CUE4Parse.TabControl.SelectedTab is { HasImage: true })
_applicationView.CUE4Parse.TabControl.SelectedTab.GoPreviousImage();
else if (e.Key == Key.Right && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage)
else if (e.Key == Key.Right && !_applicationView.IsAssetsExplorerVisible && _applicationView.CUE4Parse.TabControl.SelectedTab is { HasImage: true })
_applicationView.CUE4Parse.TabControl.SelectedTab.GoNextImage();
else if (_applicationView.Status.IsReady && _applicationView.IsAssetsExplorerVisible && Keyboard.Modifiers.HasFlag(ModifierKeys.Alt))
{
CategoriesSelector.SelectedIndex = e.SystemKey switch
{
Key.D0 or Key.NumPad0 => 0,
Key.D1 or Key.NumPad1 => 1,
Key.D2 or Key.NumPad2 => 2,
Key.D3 or Key.NumPad3 => 3,
Key.D4 or Key.NumPad4 => 4,
Key.D5 or Key.NumPad5 => 5,
Key.D6 or Key.NumPad6 => 6,
Key.D7 or Key.NumPad7 => 7,
Key.D8 or Key.NumPad8 => 8,
Key.D9 or Key.NumPad9 => 9,
_ => CategoriesSelector.SelectedIndex
};
}
else if (_applicationView.Status.IsReady && UserSettings.Default.FeaturePreviewNewAssetExplorer && UserSettings.Default.SwitchAssetExplorer.IsTriggered(e.Key))
_applicationView.IsAssetsExplorerVisible = !_applicationView.IsAssetsExplorerVisible;
else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.AddTab();
else if (UserSettings.Default.AssetRemoveTab.IsTriggered(e.Key))
@ -119,15 +185,22 @@ public partial class MainWindow
_applicationView.CUE4Parse.TabControl.GoLeftTab();
else if (UserSettings.Default.AssetRightTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.GoRightTab();
else if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex > 0)
LeftTabControl.SelectedIndex--;
else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex < LeftTabControl.Items.Count - 1)
LeftTabControl.SelectedIndex++;
else if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key) && _applicationView.SelectedLeftTabIndex > 0)
_applicationView.SelectedLeftTabIndex--;
else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key) && _applicationView.SelectedLeftTabIndex < LeftTabControl.Items.Count - 1)
_applicationView.SelectedLeftTabIndex++;
}
private void OnSearchViewClick(object sender, RoutedEventArgs e)
{
Helper.OpenWindow<AdonisWindow>("Search View", () => new SearchView().Show());
var searchView = Helper.GetWindow<SearchView>("Search For Packages", () => new SearchView().Show());
searchView.FocusTab(ESearchViewTab.SearchView);
}
private void OnRefViewClick(object sender, RoutedEventArgs e)
{
var searchView = Helper.GetWindow<SearchView>("Search For Packages", () => new SearchView().Show());
searchView.FocusTab(ESearchViewTab.RefView);
}
private void OnTabItemChange(object sender, SelectionChangedEventArgs e)
@ -135,7 +208,18 @@ public partial class MainWindow
if (e.OriginalSource is not TabControl tabControl)
return;
(tabControl.SelectedItem as System.Windows.Controls.TabItem)?.Focus();
switch (tabControl.SelectedIndex)
{
case 0:
DirectoryFilesListBox.Focus();
break;
case 1:
AssetsFolderName.Focus();
break;
case 2:
AssetsListName.Focus();
break;
}
}
private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e)
@ -145,142 +229,79 @@ public partial class MainWindow
private void OnOpenAvalonFinder()
{
_applicationView.CUE4Parse.TabControl.SelectedTab.HasSearchOpen = true;
AvalonEditor.YesWeSearch.Focus();
AvalonEditor.YesWeSearch.SelectAll();
if (_applicationView.IsAssetsExplorerVisible)
{
AssetsExplorerSearch.TextBox.Focus();
AssetsExplorerSearch.TextBox.SelectAll();
}
else if (_applicationView.CUE4Parse.TabControl.SelectedTab is { } tab)
{
tab.HasSearchOpen = true;
AvalonEditor.YesWeSearch.Focus();
AvalonEditor.YesWeSearch.SelectAll();
}
}
private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is not TreeView { SelectedItem: TreeItem treeItem } || treeItem.Folders.Count > 0) return;
LeftTabControl.SelectedIndex++;
_applicationView.SelectedLeftTabIndex++;
}
private void OnPreviewTexturesToggled(object sender, RoutedEventArgs e) => ItemContainerGenerator_StatusChanged(AssetsExplorer.ItemContainerGenerator, EventArgs.Empty);
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (sender is not ItemContainerGenerator { Status: GeneratorStatus.ContainersGenerated } generator)
return;
var foundVisibleItem = false;
var itemCount = generator.Items.Count;
for (var i = 0; i < itemCount; i++)
{
var container = generator.ContainerFromIndex(i);
if (container == null)
{
if (foundVisibleItem) break; // we're past the visible range already
continue; // keep scrolling to find visible items
}
if (container is FrameworkElement { IsVisible: true } && generator.Items[i] is GameFileViewModel file)
{
foundVisibleItem = true;
file.OnIsVisible();
}
}
}
private void OnAssetsTreeSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (sender is not TreeView { SelectedItem: TreeItem }) return;
_applicationView.IsAssetsExplorerVisible = true;
_applicationView.SelectedLeftTabIndex = 1;
}
private async void OnAssetsListMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is not ListBox listBox) return;
var selectedItems = listBox.SelectedItems.Cast<GameFile>().ToList();
var selectedItems = listBox.SelectedItems.OfType<GameFileViewModel>().Select(gvm => gvm.Asset).ToArray();
if (selectedItems.Length == 0) return;
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
}
private async void OnFolderExtractClick(object sender, RoutedEventArgs e)
private void OnClearFilterClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractFolder(cancellationToken, folder); });
folder.SearchText = string.Empty;
folder.SelectedCategory = EAssetCategory.All;
}
}
private async void OnFolderExportClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExportFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully exported ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.RawDataDirectory, true);
});
}
}
private async void OnFolderSaveClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.SaveFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.PropertiesDirectory, true);
});
}
}
private async void OnFolderTextureClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.TextureFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved textures from ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.TextureDirectory, true);
});
}
}
private async void OnFolderModelClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ModelFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved models from ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true);
});
}
}
private async void OnFolderAnimationClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.AnimationFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved animations from ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true);
});
}
}
private async void OnFolderAudioClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.AudioFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved audio from ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.AudioDirectory, true);
});
}
}
private void OnFavoriteDirectoryClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
FLogger.Append(ELog.Information, () =>
FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new favorite directory", Constants.WHITE, true));
}
private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
Clipboard.SetText(folder.PathAtThisPoint);
}
private void OnDeleteSearchClick(object sender, RoutedEventArgs e)
{
AssetsSearchName.Text = string.Empty;
AssetsListName.ScrollIntoView(AssetsListName.SelectedItem);
}
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
{
if (sender is not TextBox textBox || AssetsFolderName.SelectedItem is not TreeItem folder)
return;
var filters = textBox.Text.Trim().Split(' ');
folder.AssetsList.AssetsView.Filter = o => { return o is GameFile entry && filters.All(x => entry.Name.Contains(x, StringComparison.OrdinalIgnoreCase)); };
}
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (!_applicationView.Status.IsReady || sender is not ListBox listBox) return;
@ -290,14 +311,67 @@ public partial class MainWindow
private async void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (!_applicationView.Status.IsReady || sender is not ListBox listBox) return;
if (!_applicationView.Status.IsReady || sender is not ListBox listBox)
return;
if (e.Key != Key.Enter)
return;
if (listBox.SelectedItem == null)
return;
switch (e.Key)
switch (listBox.SelectedItem)
{
case Key.Enter:
var selectedItems = listBox.SelectedItems.Cast<GameFile>().ToList();
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
case GameFileViewModel file:
_applicationView.IsAssetsExplorerVisible = false;
ApplicationService.ApplicationView.SelectedLeftTabIndex = 2;
await _threadWorkerView.Begin(cancellationToken => _applicationView.CUE4Parse.ExtractSelected(cancellationToken, [file.Asset]));
break;
case TreeItem folder:
ApplicationService.ApplicationView.SelectedLeftTabIndex = 1;
var parent = folder.Parent;
while (parent != null)
{
parent.IsExpanded = true;
parent = parent.Parent;
}
var childFolder = folder;
while (childFolder.Folders.Count == 1 && childFolder.AssetsList.Assets.Count == 0)
{
childFolder.IsExpanded = true;
childFolder = childFolder.Folders[0];
}
childFolder.IsExpanded = true;
childFolder.IsSelected = true;
break;
}
}
private void FeaturePreviewOnUnchecked(object sender, RoutedEventArgs e)
{
_applicationView.IsAssetsExplorerVisible = false;
}
private async void OnFoldersPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Enter || sender is not TreeView treeView || treeView.SelectedItem is not TreeItem folder)
return;
if ((folder.IsExpanded || folder.Folders.Count == 0) && folder.AssetsList.Assets.Count > 0)
{
_applicationView.SelectedLeftTabIndex++;
return;
}
var childFolder = folder;
while (childFolder.Folders.Count == 1 && childFolder.AssetsList.Assets.Count == 0)
{
childFolder.IsExpanded = true;
childFolder = childFolder.Folders[0];
}
childFolder.IsExpanded = true;
childFolder.IsSelected = true;
}
}

View File

@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// <auto-generated>
// Ce code a été généré par un outil.
// Version du runtime :4.0.30319.42000

View File

@ -1,4 +1,4 @@
using FModel.ViewModels;
using FModel.ViewModels;
namespace FModel.Services
{

View File

@ -1,4 +1,4 @@
using System;
using System;
using DiscordRPC;
using FModel.Extensions;
using FModel.Settings;

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using FModel.Framework;
namespace FModel.Settings;

View File

@ -1,109 +1,109 @@
using System.Linq;
using FModel.Framework;
using FModel.ViewModels.ApiEndpoints;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace FModel.Settings;
public class EndpointSettings : ViewModel
{
public static EndpointSettings[] Default(string gameName)
{
switch (gameName)
{
case "Fortnite":
case "Fortnite [LIVE]":
return new EndpointSettings[]
{
new("https://uedb.dev/svc/api/v1/fortnite/aes", "$.['mainKey','dynamicKeys']"),
new("https://uedb.dev/svc/api/v1/fortnite/mappings", "$.mappings.ZStandard")
};
default:
return new EndpointSettings[] { new(), new() };
}
}
private string _url;
public string Url
{
get => _url;
set => SetProperty(ref _url, value);
}
private string _path;
public string Path
{
get => _path;
set => SetProperty(ref _path, value);
}
private bool _overwrite;
public bool Overwrite
{
get => _overwrite;
set => SetProperty(ref _overwrite, value);
}
private string _filePath;
public string FilePath
{
get => _filePath;
set => SetProperty(ref _filePath, value);
}
private bool _isValid;
public bool IsValid
{
get => _isValid;
set
{
SetProperty(ref _isValid, value);
RaisePropertyChanged(nameof(Label));
}
}
[JsonIgnore]
public string Label => IsValid ?
"Your endpoint configuration is valid! Please, avoid any unnecessary modifications!" :
"Your endpoint configuration DOES NOT seem to be valid yet! Please, test it out!";
public EndpointSettings() {}
public EndpointSettings(string url, string path)
{
Url = url;
Path = path;
IsValid = !string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(path); // be careful with this
}
public void TryValidate(DynamicApiEndpoint endpoint, EEndpointType type, out JToken response)
{
response = null;
if (string.IsNullOrEmpty(Url) || string.IsNullOrEmpty(Path))
{
IsValid = false;
}
else switch (type)
{
case EEndpointType.Aes:
{
var r = endpoint.GetAesKeys(default, Url, Path);
response = JToken.FromObject(r);
IsValid = r.IsValid;
break;
}
case EEndpointType.Mapping:
{
var r = endpoint.GetMappings(default, Url, Path);
response = JToken.FromObject(r);
IsValid = r.Any(x => x.IsValid);
break;
}
default:
{
IsValid = false;
break;
}
}
}
}
using System.Linq;
using FModel.Framework;
using FModel.ViewModels.ApiEndpoints;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace FModel.Settings;
public class EndpointSettings : ViewModel
{
public static EndpointSettings[] Default(string gameName)
{
return gameName switch
{
"Fortnite" or "Fortnite [LIVE]" => [
new("https://uedb.dev/svc/api/v1/fortnite/aes", "$.['mainKey','dynamicKeys']"),
new("https://uedb.dev/svc/api/v1/fortnite/mappings", "$.mappings.ZStandard")
],
"VALORANT" or "VALORANT [LIVE]" => [
new("https://uedb.dev/svc/api/v1/valorant/aes", "$.['mainKey','dynamicKeys']"),
new("https://uedb.dev/svc/api/v1/valorant/mappings", "$.mappings.ZStandard")
],
_ => [new(), new()],
};
}
private string _url;
public string Url
{
get => _url;
set => SetProperty(ref _url, value);
}
private string _path;
public string Path
{
get => _path;
set => SetProperty(ref _path, value);
}
private bool _overwrite;
public bool Overwrite
{
get => _overwrite;
set => SetProperty(ref _overwrite, value);
}
private string _filePath;
public string FilePath
{
get => _filePath;
set => SetProperty(ref _filePath, value);
}
private bool _isValid;
public bool IsValid
{
get => _isValid;
set
{
SetProperty(ref _isValid, value);
RaisePropertyChanged(nameof(Label));
}
}
[JsonIgnore]
public string Label => IsValid ?
"Your endpoint configuration is valid! Please, avoid any unnecessary modifications!" :
"Your endpoint configuration DOES NOT seem to be valid yet! Please, test it out!";
public EndpointSettings() {}
public EndpointSettings(string url, string path)
{
Url = url;
Path = path;
IsValid = !string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(path); // be careful with this
}
public void TryValidate(DynamicApiEndpoint endpoint, EEndpointType type, out JToken response)
{
response = null;
if (string.IsNullOrEmpty(Url) || string.IsNullOrEmpty(Path))
{
IsValid = false;
}
else switch (type)
{
case EEndpointType.Aes:
{
var r = endpoint.GetAesKeys(default, Url, Path);
response = JToken.FromObject(r);
IsValid = r.IsValid;
break;
}
case EEndpointType.Mapping:
{
var r = endpoint.GetMappings(default, Url, Path);
response = JToken.FromObject(r);
IsValid = r.Any(x => x.IsValid);
break;
}
default:
{
IsValid = false;
break;
}
}
}
}

View File

@ -3,14 +3,14 @@ using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Input;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Nanite;
using CUE4Parse.UE4.Versions;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Animations;
using CUE4Parse.UE4.Versions;
using CUE4Parse_Conversion.Meshes;
using CUE4Parse_Conversion.Textures;
using CUE4Parse_Conversion.UEFormat.Enums;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Nanite;
using FModel.Framework;
using FModel.ViewModels;
using FModel.ViewModels.ApiEndpoints.Models;
@ -307,6 +307,13 @@ namespace FModel.Settings
set => SetProperty(ref _dirRightTab, value);
}
private Hotkey _switchAssetExplorer = new(Key.Z);
public Hotkey SwitchAssetExplorer
{
get => _switchAssetExplorer;
set => SetProperty(ref _switchAssetExplorer, value);
}
private Hotkey _assetLeftTab = new(Key.Q);
public Hotkey AssetLeftTab
{
@ -516,5 +523,19 @@ namespace FModel.Settings
get => _saveHdrTexturesAsHdr;
set => SetProperty(ref _saveHdrTexturesAsHdr, value);
}
private bool _featurePreviewNewAssetExplorer = true;
public bool FeaturePreviewNewAssetExplorer
{
get => _featurePreviewNewAssetExplorer;
set => SetProperty(ref _featurePreviewNewAssetExplorer, value);
}
private bool _previewTexturesAssetExplorer = true;
public bool PreviewTexturesAssetExplorer
{
get => _previewTexturesAssetExplorer;
set => SetProperty(ref _previewTexturesAssetExplorer, value);
}
}
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using CUE4Parse.UE4.Objects.Core.Serialization;
using FModel.Framework;

View File

@ -1,4 +1,4 @@
using System.Text;
using System.Text;
using System.Threading.Tasks;
using FModel.Framework;
using FModel.Services;

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;

View File

@ -1,4 +1,4 @@
using RestSharp;
using RestSharp;
using RestSharp.Interceptors;
namespace FModel.ViewModels.ApiEndpoints;

View File

@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CUE4Parse.Utils;

View File

@ -1,4 +1,4 @@
using System;
using System;
using AdonisUI.Controls;
using System.Collections.Generic;
using System.Threading;

View File

@ -1,4 +1,4 @@
using System;
using System;
using FModel.ViewModels.ApiEndpoints.Models;
using RestSharp;
using System.Threading.Tasks;

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FModel.Framework;

View File

@ -5,10 +5,8 @@ using RestSharp;
namespace FModel.ViewModels.ApiEndpoints;
public class GitHubApiEndpoint : AbstractApiProvider
public class GitHubApiEndpoint(RestClient client) : AbstractApiProvider(client)
{
public GitHubApiEndpoint(RestClient client) : base(client) { }
public async Task<GitHubCommit[]> GetCommitHistoryAsync(string branch = "dev", int page = 1, int limit = 30)
{
var request = new FRestRequest(Constants.GH_COMMITS_HISTORY);
@ -25,4 +23,11 @@ public class GitHubApiEndpoint : AbstractApiProvider
var response = await _client.ExecuteAsync<GitHubRelease>(request).ConfigureAwait(false);
return response.Data;
}
public async Task<Author> GetUserAsync(string username)
{
var request = new FRestRequest($"https://api.github.com/users/{username}");
var response = await _client.ExecuteAsync<Author>(request).ConfigureAwait(false);
return response.Data;
}
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;
using I = Newtonsoft.Json.JsonIgnoreAttribute;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;

View File

@ -1,4 +1,5 @@
using System;
using System;
using System.Linq;
using System.Windows;
using AdonisUI.Controls;
using AutoUpdaterDotNET;
@ -37,8 +38,7 @@ public class GitHubAsset : ViewModel
public class GitHubCommit : ViewModel
{
private string _sha;
[J("sha")]
public string Sha
[J("sha")] public string Sha
{
get => _sha;
set
@ -52,6 +52,35 @@ public class GitHubCommit : ViewModel
[J("commit")] public Commit Commit { get; set; }
[J("author")] public Author Author { get; set; }
private Author[] _coAuthors = [];
public Author[] CoAuthors
{
get => _coAuthors;
set
{
SetProperty(ref _coAuthors, value);
RaisePropertyChanged(nameof(Authors));
RaisePropertyChanged(nameof(AuthorNames));
}
}
public Author[] Authors => Author != null ? new[] { Author }.Concat(CoAuthors).ToArray() : CoAuthors;
public string AuthorNames
{
get
{
var authors = Authors;
return authors.Length switch
{
0 => string.Empty,
1 => authors[0].Login,
2 => $"{authors[0].Login} and {authors[1].Login}",
_ => string.Join(", ", authors.Take(authors.Length - 1).Select(a => a.Login)) + $", and {authors[^1].Login}"
};
}
}
private GitHubAsset _asset;
public GitHubAsset Asset
{

View File

@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;
using I = Newtonsoft.Json.JsonIgnoreAttribute;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;
using I = Newtonsoft.Json.JsonIgnoreAttribute;

View File

@ -11,6 +11,7 @@ using CUE4Parse.Compression;
using CUE4Parse.Encryption.Aes;
using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.UE4.VirtualFileSystem;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
@ -43,6 +44,32 @@ public class ApplicationViewModel : ViewModel
private init => SetProperty(ref _status, value);
}
public IEnumerable<EAssetCategory> Categories { get; } = AssetCategoryExtensions.GetBaseCategories();
private bool _isAssetsExplorerVisible;
public bool IsAssetsExplorerVisible
{
get => _isAssetsExplorerVisible;
set
{
if (value && !UserSettings.Default.FeaturePreviewNewAssetExplorer)
return;
SetProperty(ref _isAssetsExplorerVisible, value);
}
}
private int _selectedLeftTabIndex;
public int SelectedLeftTabIndex
{
get => _selectedLeftTabIndex;
set
{
if (value is < 0 or > 2) return;
SetProperty(ref _selectedLeftTabIndex, value);
}
}
public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this);
private RightClickMenuCommand _rightClickMenuCommand;
public MenuCommand MenuCommand => _menuCommand ??= new MenuCommand(this);
@ -50,7 +77,7 @@ public class ApplicationViewModel : ViewModel
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
private CopyCommand _copyCommand;
public string InitialWindowTitle => $"FModel ({Constants.APP_SHORT_COMMIT_ID})";
public string InitialWindowTitle => $"FModel ({Constants.APP_SHORT_COMMIT_ID} - {Constants.APP_BUILD_DATE:MMM d, yyyy})";
public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown";
public string TitleExtra => $"({UserSettings.Default.CurrentDir.UeVersion}){(Build != EBuildKind.Release ? $" ({Build})" : "")}";
@ -196,32 +223,37 @@ public class ApplicationViewModel : ViewModel
public static async Task InitVgmStream()
{
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
if (File.Exists(vgmZipFilePath)) return;
var vgmFileInfo = new FileInfo(vgmZipFilePath);
await ApplicationService.ApiEndpointView.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
if (new FileInfo(vgmZipFilePath).Length > 0)
if (!vgmFileInfo.Exists || vgmFileInfo.LastWriteTimeUtc < DateTime.UtcNow.AddMonths(-4))
{
var zipDir = Path.GetDirectoryName(vgmZipFilePath)!;
await using var zipFs = File.OpenRead(vgmZipFilePath);
using var zip = new ZipArchive(zipFs, ZipArchiveMode.Read);
await ApplicationService.ApiEndpointView.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
vgmFileInfo.Refresh();
foreach (var entry in zip.Entries)
if (vgmFileInfo.Length > 0)
{
var entryPath = Path.Combine(zipDir, entry.FullName);
await using var entryFs = File.Create(entryPath);
await using var entryStream = entry.Open();
await entryStream.CopyToAsync(entryFs);
var zipDir = Path.GetDirectoryName(vgmZipFilePath)!;
await using var zipFs = File.OpenRead(vgmZipFilePath);
using var zip = new ZipArchive(zipFs, ZipArchiveMode.Read);
foreach (var entry in zip.Entries)
{
var entryPath = Path.Combine(zipDir, entry.FullName);
await using var entryFs = File.Create(entryPath);
await using var entryStream = entry.Open();
await entryStream.CopyToAsync(entryFs);
}
}
else
{
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download VgmStream", Constants.WHITE, true));
}
}
else
{
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download VgmStream", Constants.WHITE, true));
}
}
public static async Task InitImGuiSettings(bool forceDownload)
{
var imgui = "imgui.ini";
const string imgui = "imgui.ini";
var imguiPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", imgui);
if (File.Exists(imgui)) File.Move(imgui, imguiPath, true);
@ -234,7 +266,7 @@ public class ApplicationViewModel : ViewModel
}
}
public static async ValueTask InitOodle()
public static async Task InitOodle()
{
if (File.Exists(OodleHelper.OODLE_DLL_NAME_OLD))
{
@ -245,7 +277,11 @@ public class ApplicationViewModel : ViewModel
catch { /* ignored */}
}
var oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME);
var oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME_OLD);
if (!File.Exists(oodlePath))
{
oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME);
}
if (!File.Exists(oodlePath))
{
@ -259,7 +295,7 @@ public class ApplicationViewModel : ViewModel
OodleHelper.Initialize(oodlePath);
}
public static async ValueTask InitZlib()
public static async Task InitZlib()
{
var zlibPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", ZlibHelper.DLL_NAME);
var zlibFileInfo = new FileInfo(zlibPath);

View File

@ -1,12 +1,15 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using CUE4Parse.FileProvider.Objects;
using CUE4Parse.UE4.Versions;
using CUE4Parse.UE4.VirtualFileSystem;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
@ -14,11 +17,11 @@ namespace FModel.ViewModels;
public class TreeItem : ViewModel
{
private string _header;
private readonly string _header;
public string Header
{
get => _header;
private set => SetProperty(ref _header, value);
private init => SetProperty(ref _header, value);
}
private bool _isExpanded;
@ -56,10 +59,91 @@ public class TreeItem : ViewModel
private set => SetProperty(ref _version, value);
}
private string _searchText = string.Empty;
public string SearchText
{
get => _searchText;
set
{
if (SetProperty(ref _searchText, value))
{
RefreshFilters();
}
}
}
private EAssetCategory _selectedCategory = EAssetCategory.All;
public EAssetCategory SelectedCategory
{
get => _selectedCategory;
set
{
if (SetProperty(ref _selectedCategory, value))
_ = OnSelectedCategoryChanged();
}
}
public string PathAtThisPoint { get; }
public AssetsListViewModel AssetsList { get; }
public RangeObservableCollection<TreeItem> Folders { get; }
public ICollectionView FoldersView { get; }
public AssetsListViewModel AssetsList { get; } = new();
public RangeObservableCollection<TreeItem> Folders { get; } = [];
private ICollectionView _foldersView;
public ICollectionView FoldersView
{
get
{
_foldersView ??= new ListCollectionView(Folders)
{
SortDescriptions = { new SortDescription(nameof(Header), ListSortDirection.Ascending) }
};
return _foldersView;
}
}
private ICollectionView? _filteredFoldersView;
public ICollectionView? FilteredFoldersView
{
get
{
_filteredFoldersView ??= new ListCollectionView(Folders)
{
SortDescriptions = { new SortDescription(nameof(Header), ListSortDirection.Ascending) },
Filter = e => ItemFilter(e, SearchText.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries))
};
return _filteredFoldersView;
}
}
private CompositeCollection _combinedEntries;
public CompositeCollection CombinedEntries
{
get
{
if (_combinedEntries == null)
{
void CreateCombinedEntries()
{
_combinedEntries = new CompositeCollection
{
new CollectionContainer { Collection = FilteredFoldersView },
new CollectionContainer { Collection = AssetsList.AssetsView }
};
}
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(CreateCombinedEntries);
}
else
{
CreateCombinedEntries();
}
}
return _combinedEntries;
}
}
public TreeItem Parent { get; init; }
public TreeItem(string header, GameFile entry, string pathHere)
{
@ -71,9 +155,43 @@ public class TreeItem : ViewModel
Version = vfsEntry.Vfs.Ver;
}
PathAtThisPoint = pathHere;
AssetsList = new AssetsListViewModel();
Folders = new RangeObservableCollection<TreeItem>();
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
AssetsList.AssetsView.Filter = o => ItemFilter(o, SearchText.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries));
}
private void RefreshFilters()
{
AssetsList.AssetsView.Refresh();
FilteredFoldersView?.Refresh();
}
private bool ItemFilter(object item, IEnumerable<string> filters)
{
var f = filters.ToArray();
switch (item)
{
case GameFileViewModel entry:
{
bool matchesSearch = f.Length == 0 || f.All(x => entry.Asset.Name.Contains(x, StringComparison.OrdinalIgnoreCase));
bool matchesCategory = SelectedCategory == EAssetCategory.All || entry.AssetCategory.IsOfCategory(SelectedCategory);
return matchesSearch && matchesCategory;
}
case TreeItem folder:
{
bool matchesSearch = f.Length == 0 || f.All(x => folder.Header.Contains(x, StringComparison.OrdinalIgnoreCase));
bool matchesCategory = SelectedCategory == EAssetCategory.All;
return matchesSearch && matchesCategory;
}
}
return false;
}
private async Task OnSelectedCategoryChanged()
{
await Task.WhenAll(AssetsList.Assets.Select(asset => asset.ResolveAsync(EResolveCompute.Category)));
RefreshFilters();
}
public override string ToString() => $"{Header} | {Folders.Count} Folders | {AssetsList.Assets.Count} Files";
@ -86,7 +204,7 @@ public class AssetsFolderViewModel
public AssetsFolderViewModel()
{
Folders = new RangeObservableCollection<TreeItem>();
Folders = [];
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
}
@ -103,6 +221,7 @@ public class AssetsFolderViewModel
foreach (var entry in entries)
{
TreeItem lastNode = null;
TreeItem parentItem = null;
var folders = entry.Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
var builder = new StringBuilder(64);
var parentNode = treeItems;
@ -127,20 +246,30 @@ public class AssetsFolderViewModel
if (lastNode == null)
{
var nodePath = builder.ToString();
lastNode = new TreeItem(folder, entry, nodePath[..^1]);
lastNode = new TreeItem(folder, entry, nodePath[..^1])
{
Parent = parentItem
};
lastNode.Folders.SetSuppressionState(true);
lastNode.AssetsList.Assets.SetSuppressionState(true);
parentNode.Add(lastNode);
}
parentItem = lastNode;
parentNode = lastNode.Folders;
}
lastNode?.AssetsList.Assets.Add(entry);
lastNode?.AssetsList.Add(entry);
}
if (treeItems.Count > 0)
{
var projectName = ApplicationService.ApplicationView.CUE4Parse.Provider.ProjectName;
(treeItems.FirstOrDefault(x => x.Header.Equals(projectName, StringComparison.OrdinalIgnoreCase)) ?? treeItems[0]).IsSelected = true;
}
Folders.AddRange(treeItems);
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(entries);
ApplicationService.ApplicationView.CUE4Parse.SearchVm.ChangeCollection(entries);
foreach (var folder in Folders)
InvokeOnCollectionChanged(folder);

View File

@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Windows.Data;
using CUE4Parse.FileProvider.Objects;
using FModel.Framework;
@ -7,15 +7,20 @@ namespace FModel.ViewModels;
public class AssetsListViewModel
{
public RangeObservableCollection<GameFile> Assets { get; }
public ICollectionView AssetsView { get; }
public RangeObservableCollection<GameFileViewModel> Assets { get; } = [];
public AssetsListViewModel()
private ICollectionView _assetsView;
public ICollectionView AssetsView
{
Assets = new RangeObservableCollection<GameFile>();
AssetsView = new ListCollectionView(Assets)
get
{
SortDescriptions = { new SortDescription("Path", ListSortDirection.Ascending) }
};
_assetsView ??= new ListCollectionView(Assets)
{
SortDescriptions = { new SortDescription("Asset.Path", ListSortDirection.Ascending) }
};
return _assetsView;
}
}
public void Add(GameFile gameFile) => Assets.Add(new GameFileViewModel(gameFile));
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
using AdonisUI.Controls;
using AdonisUI.Controls;
using FModel.Framework;
using FModel.Settings;
using FModel.Views;

View File

@ -1,4 +1,4 @@
using FModel.Framework;
using FModel.Framework;
namespace FModel.ViewModels.Commands;

View File

@ -1,4 +1,4 @@
using FModel.Framework;
using FModel.Framework;
namespace FModel.ViewModels.Commands;

View File

@ -1,4 +1,5 @@
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
@ -18,8 +19,16 @@ public class CopyCommand : ViewModelCommand<ApplicationViewModel>
if (parameter is not object[] parameters || parameters[0] is not string trigger)
return;
var entries = ((IList) parameters[1]).Cast<GameFile>().ToArray();
if (!entries.Any()) return;
var entries = (parameters[1] as IEnumerable)?.OfType<object>()
.SelectMany(item => item switch
{
GameFile gf => new[] { gf },
GameFileViewModel gvm => new[] { gvm.Asset },
_ => []
}) ?? [];
if (!entries.Any())
return;
var sb = new StringBuilder();
switch (trigger)

View File

@ -1,4 +1,4 @@
using FModel.Framework;
using FModel.Framework;
using FModel.Settings;
namespace FModel.ViewModels.Commands;

View File

@ -1,4 +1,4 @@
using System;
using System;
using FModel.Framework;
using FModel.Services;
@ -21,7 +21,7 @@ public class GoToCommand : ViewModelCommand<CustomDirectoriesViewModel>
public TreeItem JumpTo(string directory)
{
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
_applicationView.SelectedLeftTabIndex = 1; // folders tab
var root = _applicationView.CUE4Parse.AssetsFolder.Folders;
if (root is not { Count: > 0 }) return null;
@ -54,4 +54,4 @@ public class GoToCommand : ViewModelCommand<CustomDirectoriesViewModel>
return null;
}
}
}

View File

@ -1,4 +1,4 @@
using AdonisUI.Controls;
using AdonisUI.Controls;
using FModel.Extensions;
using FModel.Framework;
using FModel.Views.Resources.Controls;

View File

@ -55,8 +55,9 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
#endif
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
Helper.CloseWindow<AdonisWindow>("Search View"); // close search window if opened
_applicationView.SelectedLeftTabIndex = 1; // folders tab
_applicationView.IsAssetsExplorerVisible = true;
Helper.CloseWindow<AdonisWindow>("Search For Packages"); // close search window if opened
await Task.WhenAll(
_applicationView.CUE4Parse.LoadLocalizedResources(), // load locres if not already loaded,
@ -70,7 +71,11 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
case ELoadingMode.Multiple:
{
var l = (IList) parameter;
if (l.Count < 1) return;
if (l.Count == 0)
{
UserSettings.Default.LoadingMode = ELoadingMode.All;
goto case ELoadingMode.All;
}
var directoryFilesToShow = l.Cast<FileItem>();
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);

View File

@ -32,6 +32,7 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.ProjectName).Show());
break;
case "Directory_ArchivesInfo":
ApplicationService.ApplicationView.IsAssetsExplorerVisible = false;
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false, false);

View File

@ -1,4 +1,4 @@
using System;
using System;
using FModel.Framework;
using FModel.Settings;

View File

@ -4,6 +4,8 @@ using System.Threading;
using CUE4Parse.FileProvider.Objects;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using FModel.Views.Resources.Controls;
namespace FModel.ViewModels.Commands;
@ -20,16 +22,28 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
if (parameter is not object[] parameters || parameters[0] is not string trigger)
return;
var entries = ((IList) parameters[1]).Cast<GameFile>().ToArray();
if (!entries.Any()) return;
var param = (parameters[1] as IEnumerable)?.OfType<object>().ToArray() ?? [];
if (param.Length == 0) return;
var updateUi = entries.Length > 1 ? EBulkType.Auto : EBulkType.None;
var folders = param.OfType<TreeItem>().ToArray();
var assets = param.SelectMany(item => item switch
{
GameFile gf => new[] { gf }, // search view passes GameFile directly
GameFileViewModel gvm => new[] { gvm.Asset },
_ => []
}).ToArray();
if (folders.Length == 0 && assets.Length == 0)
return;
var updateUi = assets.Length > 1 ? EBulkType.Auto : EBulkType.None;
await _threadWorkerView.Begin(cancellationToken =>
{
switch (trigger)
{
#region Asset Commands
case "Assets_Extract_New_Tab":
foreach (var entry in entries)
foreach (var entry in assets)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
@ -37,15 +51,22 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
}
break;
case "Assets_Show_Metadata":
foreach (var entry in entries)
foreach (var entry in assets)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.ShowMetadata(entry);
}
break;
case "Assets_Show_References":
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.FindReferences(assets.FirstOrDefault());
}
break;
case "Assets_Decompile":
foreach (var entry in entries)
foreach (var entry in assets)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
@ -53,7 +74,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
}
break;
case "Assets_Export_Data":
foreach (var entry in entries)
foreach (var entry in assets)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
@ -61,7 +82,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
}
break;
case "Assets_Save_Properties":
foreach (var entry in entries)
foreach (var entry in assets)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
@ -69,7 +90,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
}
break;
case "Assets_Save_Textures":
foreach (var entry in entries)
foreach (var entry in assets)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
@ -77,7 +98,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
}
break;
case "Assets_Save_Models":
foreach (var entry in entries)
foreach (var entry in assets)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
@ -85,7 +106,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
}
break;
case "Assets_Save_Animations":
foreach (var entry in entries)
foreach (var entry in assets)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
@ -93,13 +114,100 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
}
break;
case "Assets_Save_Audio":
foreach (var entry in entries)
foreach (var entry in assets)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Audio | updateUi);
}
break;
#endregion
#region Folder Commands
case "Folders_Export_Data":
foreach (var folder in folders)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.ExportFolder(cancellationToken, folder);
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully exported ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.RawDataDirectory, true);
});
}
break;
case "Folders_Save_Properties":
foreach (var folder in folders)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.SaveFolder(cancellationToken, folder);
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.PropertiesDirectory, true);
});
}
break;
case "Folders_Save_Textures":
foreach (var folder in folders)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.TextureFolder(cancellationToken, folder);
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved textures from ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.TextureDirectory, true);
});
}
break;
case "Folders_Save_Models":
foreach (var folder in folders)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.ModelFolder(cancellationToken, folder);
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved models from ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true);
});
}
break;
case "Folders_Save_Animations":
foreach (var folder in folders)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.AnimationFolder(cancellationToken, folder);
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved animations from ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true);
});
}
break;
case "Folders_Save_Audio":
foreach (var folder in folders)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.AudioFolder(cancellationToken, folder);
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved audio from ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.AudioDirectory, true);
});
}
break;
#endregion
}
});
}

View File

@ -31,6 +31,9 @@ public class TabCommand : ViewModelCommand<TabItem>
case "Close_Other_Tabs":
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(tabViewModel);
break;
case "Find_References":
_applicationView.CUE4Parse.FindReferences(tabViewModel.Entry);
break;
case "Asset_Export_Data":
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(tabViewModel.Entry));
break;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

View File

@ -0,0 +1,459 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using CUE4Parse.FileProvider.Objects;
using CUE4Parse.GameTypes.FN.Assets.Exports.DataAssets;
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
using CUE4Parse.GameTypes.SMG.UE4.Assets.Objects;
using CUE4Parse.UE4.Assets;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Assets.Exports.BuildData;
using CUE4Parse.UE4.Assets.Exports.Component;
using CUE4Parse.UE4.Assets.Exports.CriWare;
using CUE4Parse.UE4.Assets.Exports.CustomizableObject;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Engine.Font;
using CUE4Parse.UE4.Assets.Exports.Fmod;
using CUE4Parse.UE4.Assets.Exports.Foliage;
using CUE4Parse.UE4.Assets.Exports.Internationalization;
using CUE4Parse.UE4.Assets.Exports.LevelSequence;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Material.Editor;
using CUE4Parse.UE4.Assets.Exports.Nanite;
using CUE4Parse.UE4.Assets.Exports.Niagara;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Assets.Exports.Sound;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Exports.Wwise;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Engine;
using CUE4Parse.UE4.Objects.Engine.Animation;
using CUE4Parse.UE4.Objects.Engine.Curves;
using CUE4Parse.UE4.Objects.MediaAssets;
using CUE4Parse.UE4.Objects.Niagara;
using CUE4Parse.UE4.Objects.PhysicsEngine;
using CUE4Parse.UE4.Objects.RigVM;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse.UE4.Objects.UObject.Editor;
using CUE4Parse.Utils;
using CUE4Parse_Conversion.Textures;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using Serilog;
using SkiaSharp;
using Svg.Skia;
namespace FModel.ViewModels;
public class GameFileViewModel(GameFile asset) : ViewModel
{
private const int MaxPreviewSize = 128;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public EResolveCompute Resolved { get; private set; } = EResolveCompute.None;
public GameFile Asset { get; } = asset;
private string _resolvedAssetType = asset.Extension;
public string ResolvedAssetType
{
get => _resolvedAssetType;
private set => SetProperty(ref _resolvedAssetType, value);
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
private EAssetCategory _assetCategory = EAssetCategory.All;
public EAssetCategory AssetCategory
{
get => _assetCategory;
private set
{
SetProperty(ref _assetCategory, value);
Resolved |= EResolveCompute.Category; // blindly assume category is resolved when set, even if unchanged
}
}
private EBulkType _assetActions = EBulkType.None;
public EBulkType AssetActions
{
get => _assetActions;
private set
{
SetProperty(ref _assetActions, value);
}
}
private ImageSource _previewImage;
public ImageSource PreviewImage
{
get => _previewImage;
private set
{
if (SetProperty(ref _previewImage, value))
{
Resolved |= EResolveCompute.Preview;
}
}
}
private int _numTextures = 0;
public int NumTextures
{
get => _numTextures;
private set => SetProperty(ref _numTextures, value);
}
public Task ExtractAsync()
=> ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
_applicationView.CUE4Parse.ExtractSelected(cancellationToken, [Asset]));
public Task ResolveAsync(EResolveCompute resolve)
{
try
{
return ResolveInternalAsync(resolve);
}
catch (Exception e)
{
Log.Error(e, "Failed to resolve asset {AssetName} ({Resolver})", Asset.Path, resolve.ToStringBitfield());
Resolved = EResolveCompute.All;
return Task.CompletedTask;
}
}
private Task ResolveInternalAsync(EResolveCompute resolve)
{
if (!_applicationView.IsAssetsExplorerVisible || !UserSettings.Default.PreviewTexturesAssetExplorer)
{
resolve &= ~EResolveCompute.Preview;
}
resolve &= ~Resolved;
if (resolve == EResolveCompute.None)
return Task.CompletedTask;
if (!Asset.IsUePackage || _applicationView.CUE4Parse is null)
return ResolveByExtensionAsync(resolve);
return ResolveByPackageAsync(resolve);
}
private Task ResolveByPackageAsync(EResolveCompute resolve)
{
if (Asset.Extension is "umap")
{
AssetCategory = EAssetCategory.World;
AssetActions = EBulkType.Meshes | EBulkType.Textures | EBulkType.Audio | EBulkType.Code;
ResolvedAssetType = "World";
Resolved |= EResolveCompute.Preview;
return Task.CompletedTask;
}
if (Asset.NameWithoutExtension.EndsWith("_BuiltData"))
{
AssetCategory = EAssetCategory.BuildData;
AssetActions = EBulkType.Textures;
ResolvedAssetType = "MapBuildDataRegistry";
Resolved |= EResolveCompute.Preview;
return Task.CompletedTask;
}
return Task.Run(() =>
{
// TODO: cache and reuse packages
var pkg = _applicationView.CUE4Parse?.Provider.LoadPackage(Asset);
if (pkg is null)
throw new InvalidOperationException($"Failed to load {Asset.Path} as UE package.");
var mainIndex = pkg.GetExportIndex(Asset.NameWithoutExtension, StringComparison.OrdinalIgnoreCase);
if (mainIndex < 0) mainIndex = pkg.GetExportIndex($"{Asset.NameWithoutExtension}_C", StringComparison.OrdinalIgnoreCase);
if (mainIndex < 0) mainIndex = 0;
var pointer = new FPackageIndex(pkg, mainIndex + 1).ResolvedObject;
if (pointer?.Object is null)
return;
var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class?.Object?.Value as UStruct, pkg);
ResolvedAssetType = dummy.ExportType;
(AssetCategory, AssetActions) = dummy switch
{
URigVMBlueprintGeneratedClass => (EAssetCategory.RigVMBlueprintGeneratedClass, EBulkType.Code),
UAnimBlueprintGeneratedClass => (EAssetCategory.AnimBlueprintGeneratedClass, EBulkType.Code),
UWidgetBlueprintGeneratedClass => (EAssetCategory.WidgetBlueprintGeneratedClass, EBulkType.Code),
UBlueprintGeneratedClass or UFunction => (EAssetCategory.BlueprintGeneratedClass, EBulkType.Code),
UUserDefinedEnum => (EAssetCategory.UserDefinedEnum, EBulkType.None),
UUserDefinedStruct => (EAssetCategory.UserDefinedStruct, EBulkType.Code),
UBlueprintCore => (EAssetCategory.Blueprint, EBulkType.Code),
UClassCookedMetaData or UStructCookedMetaData or UEnumCookedMetaData => (EAssetCategory.CookedMetaData, EBulkType.None),
UStaticMesh => (EAssetCategory.StaticMesh, EBulkType.Meshes),
USkeletalMesh => (EAssetCategory.SkeletalMesh, EBulkType.Meshes),
UCustomizableObject => (EAssetCategory.CustomizableObject, EBulkType.None),
UNaniteDisplacedMesh => (EAssetCategory.NaniteDisplacedMesh, EBulkType.None),
UTexture => (EAssetCategory.Texture, EBulkType.Textures),
UMaterialInterface => (EAssetCategory.Material, EBulkType.None),
UMaterialInterfaceEditorOnlyData => (EAssetCategory.MaterialEditorData, EBulkType.None),
UMaterialFunction => (EAssetCategory.MaterialFunction, EBulkType.None),
UMaterialFunctionEditorOnlyData => (EAssetCategory.MaterialFunctionEditorData, EBulkType.None),
UMaterialParameterCollection => (EAssetCategory.MaterialParameterCollection, EBulkType.None),
UAnimationAsset => (EAssetCategory.Animation, EBulkType.Animations),
USkeleton => (EAssetCategory.Skeleton, EBulkType.Meshes),
URig => (EAssetCategory.Rig, EBulkType.None),
UWorld => (EAssetCategory.World, EBulkType.Meshes | EBulkType.Textures | EBulkType.Audio | EBulkType.Code),
UMapBuildDataRegistry => (EAssetCategory.BuildData, EBulkType.Textures),
ULevelSequence => (EAssetCategory.LevelSequence, EBulkType.Code),
UFoliageType => (EAssetCategory.Foliage, EBulkType.None),
UItemDefinitionBase => (EAssetCategory.ItemDefinitionBase, EBulkType.Textures),
UDataAsset or UDataTable or UCurveTable or UStringTable => (EAssetCategory.Data, EBulkType.None),
UCurveBase => (EAssetCategory.CurveBase, EBulkType.None),
UPhysicsAsset => (EAssetCategory.PhysicsAsset, EBulkType.None),
UObjectRedirector => (EAssetCategory.ObjectRedirector, EBulkType.None),
UPhysicalMaterial => (EAssetCategory.PhysicalMaterial, EBulkType.None),
USoundAtomCue or UAkAudioEvent or USoundCue or UFMODEvent => (EAssetCategory.AudioEvent, EBulkType.Audio),
UFMODBank or UAkAudioBank or UAtomWaveBank or UAkInitBank => (EAssetCategory.SoundBank, EBulkType.Audio),
UWwiseAssetLibrary or USoundBase or UAkMediaAssetData or UAtomCueSheet
or USoundAtomCueSheet or UAkAudioType or UExternalSource or UExternalSourceBank => (EAssetCategory.Audio, EBulkType.Audio),
UFileMediaSource => (EAssetCategory.Video, EBulkType.None),
UFont or UFontFace or USMGLocaleFontUMG => (EAssetCategory.Font, EBulkType.None),
UNiagaraSystem or UNiagaraScriptBase or UParticleSystem => (EAssetCategory.Particle, EBulkType.None),
_ => (EAssetCategory.All, EBulkType.None),
};
switch (AssetCategory)
{
case EAssetCategory.Texture when pointer.Object.Value is UTexture texture:
{
if (!resolve.HasFlag(EResolveCompute.Preview))
break;
if (pointer.Object.Value is UTexture2DArray textureArray && textureArray.GetFirstMip() is { SizeZ: > 1 } firstMip)
NumTextures = firstMip.SizeZ;
var img = texture.Decode(MaxPreviewSize, UserSettings.Default.CurrentDir.TexturePlatform);
if (img != null)
{
using var bitmap = img.ToSkBitmap();
using var image = bitmap.Encode(SKEncodedImageFormat.Png, 100);
SetPreviewImage(image);
}
break;
}
case EAssetCategory.ItemDefinitionBase:
if (!resolve.HasFlag(EResolveCompute.Preview))
break;
if (pointer.Object.Value is UItemDefinitionBase itemDef)
{
if (LookupPreview(itemDef.DataList)) break;
if (itemDef is UAthenaPickaxeItemDefinition pickaxe && pickaxe.WeaponDefinition.TryLoad(out UItemDefinitionBase weaponDef))
{
LookupPreview(weaponDef.DataList);
}
bool LookupPreview(FInstancedStruct[] dataList)
{
foreach (var data in dataList)
{
if (!data.NonConstStruct.TryGetValue(out FSoftObjectPath icon, "Icon", "LargeIcon") ||
!icon.TryLoad<UTexture2D>(out var texture))
continue;
var img = texture.Decode(MaxPreviewSize, UserSettings.Default.CurrentDir.TexturePlatform);
if (img == null) return false;
using var bitmap = img.ToSkBitmap();
using var image = bitmap.Encode(SKEncodedImageFormat.Png, 100);
SetPreviewImage(image);
return true;
}
return false;
}
}
break;
default:
Resolved |= EResolveCompute.Preview;
break;
}
});
}
private Task ResolveByExtensionAsync(EResolveCompute resolve)
{
Resolved |= EResolveCompute.Preview;
switch (Asset.Extension)
{
case "uproject":
case "uefnproject":
case "upluginmanifest":
case "uplugin":
case "ini":
case "locmeta":
case "locres":
case "verse":
case "lua":
case "luac":
case "json5":
case "json":
case "bin":
case "txt":
case "log":
case "pem":
case "xml":
AssetCategory = EAssetCategory.Data;
break;
case "ushaderbytecode":
AssetCategory = EAssetCategory.ByteCode;
break;
case "wav":
case "awb": // This is technically soundbank and should be below but I want it to be distinguishable from "acb"
case "xvag":
case "flac":
case "at9":
case "wem":
case "ogg":
AssetCategory = EAssetCategory.Audio;
AssetActions = EBulkType.Audio;
break;
case "acb":
case "bank":
case "bnk":
case "pck":
AssetCategory = EAssetCategory.SoundBank;
AssetActions = EBulkType.Audio;
break;
case "ufont":
case "otf":
case "ttf":
AssetCategory = EAssetCategory.Font;
break;
case "mp4":
AssetCategory = EAssetCategory.Video;
break;
case "jpg":
case "png":
case "bmp":
case "svg":
{
Resolved |= ~EResolveCompute.Preview;
AssetCategory = EAssetCategory.Texture;
AssetActions = EBulkType.Textures;
if (!resolve.HasFlag(EResolveCompute.Preview))
break;
return Task.Run(() =>
{
var data = _applicationView.CUE4Parse.Provider.SaveAsset(Asset);
using var stream = new MemoryStream(data);
stream.Position = 0;
SKBitmap bitmap;
if (Asset.Extension == "svg")
{
var svg = new SKSvg();
svg.Load(stream);
if (svg.Picture == null)
return;
bitmap = new SKBitmap(MaxPreviewSize, MaxPreviewSize);
using var canvas = new SKCanvas(bitmap);
canvas.Clear(SKColors.Transparent);
var bounds = svg.Picture.CullRect;
float scale = Math.Min(MaxPreviewSize / bounds.Width, MaxPreviewSize / bounds.Height);
canvas.Scale(scale);
canvas.Translate(-bounds.Left, -bounds.Top);
canvas.DrawPicture(svg.Picture);
}
else
{
bitmap = SKBitmap.Decode(stream);
}
using var image = bitmap.Encode(Asset.Extension == "jpg" ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100);
SetPreviewImage(image);
bitmap.Dispose();
});
}
default:
AssetCategory = EAssetCategory.All; // just so it sets resolved
break;
}
return Task.CompletedTask;
}
private void SetPreviewImage(SKData data)
{
using var ms = new MemoryStream(data.ToArray());
ms.Position = 0;
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = ms;
bitmap.EndInit();
bitmap.Freeze();
Application.Current.Dispatcher.InvokeAsync(() => PreviewImage = bitmap);
}
private CancellationTokenSource _previewCts;
public void OnIsVisible()
{
if (Resolved == EResolveCompute.All)
return;
_previewCts?.Cancel();
_previewCts = new CancellationTokenSource();
var token = _previewCts.Token;
Task.Delay(100, token).ContinueWith(t =>
{
if (t.IsCanceled) return;
ResolveAsync(EResolveCompute.All);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
[Flags]
public enum EResolveCompute
{
None = 0,
Category = 1 << 0,
Preview = 1 << 1,
All = Category | Preview
}

View File

@ -87,8 +87,8 @@ public class GameSelectorViewModel : ViewModel
.OrderBy(value => ((int)value & 0xFF) == 0);
private IEnumerable<DirectorySettings> EnumerateDetectedGames()
{
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_7);
yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_7);
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_8);
yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_8);
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks", EGame.GAME_RogueCompany);
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks", EGame.GAME_UE4_21);
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks", EGame.GAME_Borderlands3);

View File

@ -1,6 +1,5 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@ -20,7 +19,7 @@ public class SearchViewModel : ViewModel
Descending
}
private string _filterText;
private string _filterText = string.Empty;
public string FilterText
{
get => _filterText;
@ -48,22 +47,45 @@ public class SearchViewModel : ViewModel
set => SetProperty(ref _currentSortSizeMode, value);
}
public int ResultsCount => SearchResults?.Count ?? 0;
private int _resultsCount = 0;
public int ResultsCount
{
get => _resultsCount;
private set => SetProperty(ref _resultsCount, value);
}
private GameFile _refFile;
public GameFile RefFile
{
get => _refFile;
private set => SetProperty(ref _refFile, value);
}
public RangeObservableCollection<GameFile> SearchResults { get; }
public ICollectionView SearchResultsView { get; }
public ListCollectionView SearchResultsView { get; }
public SearchViewModel()
{
SearchResults = new RangeObservableCollection<GameFile>();
SearchResults = [];
SearchResultsView = new ListCollectionView(SearchResults)
{
Filter = e => ItemFilter(e, FilterText?.Trim().Split(' ') ?? []),
Filter = e => ItemFilter(e, FilterText.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries)),
};
ResultsCount = SearchResultsView.Count;
}
public void RefreshFilter()
{
SearchResultsView.Refresh();
ResultsCount = SearchResultsView.Count;
}
public void ChangeCollection(IEnumerable<GameFile> files, GameFile refFile = null)
{
SearchResults.Clear();
SearchResults.AddRange(files);
RefFile = refFile;
ResultsCount = SearchResultsView.Count;
}
public async Task CycleSortSizeMode()
@ -105,7 +127,7 @@ public class SearchViewModel : ViewModel
});
SearchResults.Clear();
SearchResults.AddRange(sorted.ToList());
SearchResults.AddRange(sorted);
}
private bool ItemFilter(object item, IEnumerable<string> filters)

View File

@ -40,10 +40,6 @@ public class ThreadWorkerViewModel : ViewModel
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private readonly AsyncQueue<Action<CancellationToken>> _jobs;
private const string _at = " at ";
private const char _dot = '.';
private const char _colon = ':';
private const string _gray = "#999";
public ThreadWorkerViewModel()
{
@ -104,37 +100,7 @@ public class ThreadWorkerViewModel : ViewModel
CurrentCancellationTokenSource = null; // kill token
Log.Error("{Exception}", e);
FLogger.Append(ELog.Error, () =>
{
if ((e.InnerException ?? e) is { TargetSite.DeclaringType: not null } exception)
{
if (exception.TargetSite.ToString() == "CUE4Parse.FileProvider.GameFile get_Item(System.String)")
{
FLogger.Text(e.Message, Constants.WHITE, true);
}
else
{
var t = exception.GetType();
FLogger.Text(t.Namespace + _dot, Constants.GRAY);
FLogger.Text(t.Name, Constants.WHITE);
FLogger.Text(_colon + " ", Constants.GRAY);
FLogger.Text(exception.Message, Constants.RED, true);
FLogger.Text(_at, _gray);
FLogger.Text(exception.TargetSite.DeclaringType.FullName + _dot, Constants.GRAY);
FLogger.Text(exception.TargetSite.Name, Constants.YELLOW);
var p = exception.TargetSite.GetParameters();
var parameters = new string[p.Length];
for (int i = 0; i < parameters.Length; i++)
{
parameters[i] = p[i].ParameterType.Name + " " + p[i].Name;
}
FLogger.Text("(" + string.Join(", ", parameters) + ")", Constants.GRAY, true);
}
}
});
FLogger.Append(e);
return;
}
}

View File

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Data;
using CUE4Parse.Utils;
@ -13,7 +15,7 @@ using FModel.Views.Resources.Converters;
namespace FModel.ViewModels;
public class UpdateViewModel : ViewModel
public partial class UpdateViewModel : ViewModel
{
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
@ -25,7 +27,7 @@ public class UpdateViewModel : ViewModel
public UpdateViewModel()
{
Commits = new RangeObservableCollection<GitHubCommit>();
Commits = [];
CommitsView = new ListCollectionView(Commits)
{
GroupDescriptions = { new PropertyGroupDescription("Commit.Author.Date", new DateTimeToDateConverter()) }
@ -35,43 +37,128 @@ public class UpdateViewModel : ViewModel
RemindMeCommand.Execute(this, null);
}
public async Task Load()
public async Task LoadAsync()
{
Commits.AddRange(await _apiEndpointView.GitHubApi.GetCommitHistoryAsync());
var commits = await _apiEndpointView.GitHubApi.GetCommitHistoryAsync();
if (commits == null || commits.Length == 0)
return;
var qa = await _apiEndpointView.GitHubApi.GetReleaseAsync("qa");
var assets = qa.Assets.OrderByDescending(x => x.CreatedAt).ToList();
Commits.AddRange(commits);
for (var i = 0; i < assets.Count; i++)
try
{
var asset = assets[i];
asset.IsLatest = i == 0;
var commitSha = asset.Name.SubstringBeforeLast(".zip");
var commit = Commits.FirstOrDefault(x => x.Sha == commitSha);
if (commit != null)
{
commit.Asset = asset;
}
else
{
Commits.Add(new GitHubCommit
{
Sha = commitSha,
Commit = new Commit
{
Message = $"FModel ({commitSha[..7]})",
Author = new Author { Name = asset.Uploader.Login, Date = asset.CreatedAt }
},
Author = asset.Uploader,
Asset = asset
});
}
_ = LoadCoAuthors();
_ = LoadAssets();
}
catch
{
//
}
}
private Task LoadCoAuthors()
{
return Task.Run(async () =>
{
var coAuthorMap = new Dictionary<GitHubCommit, HashSet<string>>();
foreach (var commit in Commits)
{
if (!commit.Commit.Message.Contains("Co-authored-by"))
continue;
var regex = GetCoAuthorRegex();
var matches = regex.Matches(commit.Commit.Message);
if (matches.Count == 0) continue;
commit.Commit.Message = regex.Replace(commit.Commit.Message, string.Empty).Trim();
coAuthorMap[commit] = [];
foreach (Match match in matches)
{
if (match.Groups.Count < 3) continue;
var username = match.Groups[1].Value;
if (username.Equals("Asval", StringComparison.OrdinalIgnoreCase))
{
username = "4sval"; // found out the hard way co-authored usernames can't be trusted
}
coAuthorMap[commit].Add(username);
}
}
if (coAuthorMap.Count == 0) return;
var uniqueUsernames = coAuthorMap.Values.SelectMany(x => x).Distinct().ToArray();
var authorCache = new Dictionary<string, Author>();
foreach (var username in uniqueUsernames)
{
try
{
var author = await _apiEndpointView.GitHubApi.GetUserAsync(username);
if (author != null)
authorCache[username] = author;
}
catch
{
//
}
}
foreach (var (commit, usernames) in coAuthorMap)
{
var coAuthors = usernames
.Where(username => authorCache.ContainsKey(username))
.Select(username => authorCache[username])
.ToArray();
if (coAuthors.Length > 0)
commit.CoAuthors = coAuthors;
}
});
}
private Task LoadAssets()
{
return Task.Run(async () =>
{
var qa = await _apiEndpointView.GitHubApi.GetReleaseAsync("qa");
var assets = qa.Assets.OrderByDescending(x => x.CreatedAt).ToList();
for (var i = 0; i < assets.Count; i++)
{
var asset = assets[i];
asset.IsLatest = i == 0;
var commitSha = asset.Name.SubstringBeforeLast(".zip");
var commit = Commits.FirstOrDefault(x => x.Sha == commitSha);
if (commit != null)
{
commit.Asset = asset;
}
else
{
Commits.Add(new GitHubCommit
{
Sha = commitSha,
Commit = new Commit
{
Message = $"FModel ({commitSha[..7]})",
Author = new Author { Name = asset.Uploader.Login, Date = asset.CreatedAt }
},
Author = asset.Uploader,
Asset = asset
});
}
}
});
}
public void DownloadLatest()
{
Commits.FirstOrDefault(x => x.IsDownloadable && x.Asset.IsLatest)?.Download();
}
[GeneratedRegex(@"Co-authored-by:\s*(.+?)\s*<(.+?)>", RegexOptions.IgnoreCase | RegexOptions.Multiline, "en-US")]
private static partial Regex GetCoAuthorRegex();
}

View File

@ -11,13 +11,6 @@
<Setter Property="Title" Value="About" />
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<StackPanel Margin="30 10">
<StackPanel HorizontalAlignment="Center" Margin="0 0 0 30">
<TextBlock Text="{Binding Source={x:Static local:Constants.APP_VERSION}, StringFormat={}FModel {0}}" FontSize="15" FontWeight="500" Foreground="#9DA3DD" FontStretch="Expanded" />

View File

@ -1,4 +1,4 @@
using System.Windows;
using System.Windows;
using FModel.ViewModels;
namespace FModel.Views;

View File

@ -14,13 +14,6 @@
<Setter Property="Title" Value="AES Manager" />
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>

View File

@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Windows;
using FModel.Services;
using FModel.ViewModels;

View File

@ -16,13 +16,6 @@
<Setter Property="Title" Value="Audio Player" />
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="350" />

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;

View File

@ -12,13 +12,6 @@
<Setter Property="Title" Value="Backup Manager" />
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid Column="2" adonisExtensions:LayerExtension.Layer="2" Margin="10" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />

View File

@ -1,4 +1,4 @@
using System.Windows;
using System.Windows;
using FModel.ViewModels;
namespace FModel.Views;

View File

@ -1,4 +1,4 @@
using System.Windows;
using System.Windows;
using FModel.Settings;
namespace FModel.Views;

View File

@ -14,13 +14,6 @@
<Setter Property="Title" Value="Directory Selector" />
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using FModel.ViewModels;

View File

@ -14,14 +14,6 @@
<Setter Property="Title" Value="Image Merger"/>
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>

View File

@ -1,4 +1,4 @@
using AdonisUI.Controls;
using AdonisUI.Controls;
using FModel.Extensions;
using FModel.Settings;
using FModel.Views.Resources.Controls;

View File

@ -0,0 +1,45 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="NeutralBrush" Color="White" />
<SolidColorBrush x:Key="BlueprintBrush" Color="DodgerBlue" />
<SolidColorBrush x:Key="BlueprintWidgetBrush" Color="DarkViolet" />
<SolidColorBrush x:Key="BlueprintAnimBrush" Color="Crimson" />
<SolidColorBrush x:Key="BlueprintRigVMBrush" Color="Teal" />
<SolidColorBrush x:Key="CookedMetaDataBrush" Color="Yellow" />
<SolidColorBrush x:Key="UserDefinedEnumBrush" Color="DarkGoldenrod" />
<SolidColorBrush x:Key="UserDefinedStructBrush" Color="Tan" />
<SolidColorBrush x:Key="CustomizableObjectBrush" Color="DarkRed" />
<SolidColorBrush x:Key="NaniteDisplacedMeshBrush" Color="ForestGreen" />
<SolidColorBrush x:Key="MaterialBrush" Color="BurlyWood" />
<SolidColorBrush x:Key="MaterialEditorBrush" Color="Yellow" />
<SolidColorBrush x:Key="RigBrush" Color="YellowGreen" />
<SolidColorBrush x:Key="BinaryBrush" Color="Yellow" />
<SolidColorBrush x:Key="TextureBrush" Color="MediumPurple" />
<SolidColorBrush x:Key="ConfigBrush" Color="LightSlateGray" />
<SolidColorBrush x:Key="AudioBrush" Color="MediumSeaGreen" />
<SolidColorBrush x:Key="SoundBankBrush" Color="DarkSeaGreen" />
<SolidColorBrush x:Key="AudioEventBrush" Color="DarkTurquoise" />
<SolidColorBrush x:Key="VideoBrush" Color="IndianRed" />
<SolidColorBrush x:Key="DataTableBrush" Color="SteelBlue" />
<SolidColorBrush x:Key="CurveBrush" Color="HotPink" />
<SolidColorBrush x:Key="PluginBrush" Color="GreenYellow" />
<SolidColorBrush x:Key="ProjectBrush" Color="DeepSkyBlue" />
<SolidColorBrush x:Key="LocalizationBrush" Color="CornflowerBlue" />
<SolidColorBrush x:Key="WorldBrush" Color="Orange" />
<SolidColorBrush x:Key="BuildDataBrush" Color="Tomato" />
<SolidColorBrush x:Key="LevelSequenceBrush" Color="Coral" />
<SolidColorBrush x:Key="FoliageBrush" Color="ForestGreen" />
<SolidColorBrush x:Key="ParticleBrush" Color="Gold" />
<SolidColorBrush x:Key="AnimationBrush" Color="Coral" />
<SolidColorBrush x:Key="LuaBrush" Color="DarkBlue" />
<SolidColorBrush x:Key="JsonXmlBrush" Color="LightGreen" />
<SolidColorBrush x:Key="CodeBrush" Color="SandyBrown" />
</ResourceDictionary>

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;

View File

@ -1,4 +1,4 @@
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
using FModel.Extensions;
using ICSharpCode.AvalonEdit.Rendering;

View File

@ -1,4 +1,4 @@
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
using ICSharpCode.AvalonEdit.Rendering;
namespace FModel.Views.Resources.Controls;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Rendering;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using CSCore;

View File

@ -1,4 +1,4 @@
using System;
using System;
using FModel.ViewModels;
namespace FModel.Views.Resources.Controls.Aup;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using CSCore;
using NVorbis;

View File

@ -1,4 +1,4 @@
using System;
using System;
namespace FModel.Views.Resources.Controls.Aup;

View File

@ -1,4 +1,4 @@
using System;
using System;
namespace FModel.Views.Resources.Controls.Aup;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using CSCore.DSP;

Some files were not shown because too many files have changed in this diff Show More