diff --git a/FModel.sln b/FModel.sln
index fb6bcab7..840c08ec 100644
--- a/FModel.sln
+++ b/FModel.sln
@@ -1,25 +1,29 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
-VisualStudioVersion = 16.0.28803.452
+VisualStudioVersion = 16.0.29806.167
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FModel", "FModel\FModel.csproj", "{8AAB27BD-18D7-4164-8BBC-AB534D55D30F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel\FModel.csproj", "{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {8AAB27BD-18D7-4164-8BBC-AB534D55D30F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8AAB27BD-18D7-4164-8BBC-AB534D55D30F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8AAB27BD-18D7-4164-8BBC-AB534D55D30F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8AAB27BD-18D7-4164-8BBC-AB534D55D30F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|x64.ActiveCfg = Debug|x64
+ {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|x64.Build.0 = Debug|x64
+ {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|Any CPU.ActiveCfg = Release|x64
+ {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|x64.ActiveCfg = Release|x64
+ {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {42CA5618-EB78-4DDF-95A4-BD589CC52791}
+ SolutionGuid = {FE6EA91D-BBB8-4FBC-875C-25AD92EDB519}
EndGlobalSection
EndGlobal
diff --git a/FModel/App.config b/FModel/App.config
deleted file mode 100644
index c897b019..00000000
--- a/FModel/App.config
+++ /dev/null
@@ -1,213 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Default
-
-
- English
-
-
- False
-
-
- False
-
-
-
-
-
- 0
-
-
- 0
-
-
- 3
-
-
- 3
-
-
- {BundleName} - {Date}
-
-
- False
-
-
-
-
-
- 0
-
-
- False
-
-
- True
-
-
- False
-
-
- True
-
-
- 21:32:43
-
-
- 29:161:242
-
-
- True
-
-
- True
-
-
- True
-
-
- False
-
-
- False
-
-
- False
-
-
- {
- "[BR] Cosmetics": {
- "Path": "/FortniteGame/Content/Athena/Items/Cosmetics/",
- "isChecked": "True"
- },
- "[BR] Cosmetics Variants": {
- "Path": "/FortniteGame/Content/Athena/Items/CosmeticVariantTokens/",
- "isChecked": "True"
- },
- "[BR] Banners": {
- "Path": "/FortniteGame/Content/Athena/Items/BannerToken/",
- "isChecked": "True"
- },
- "[BR] Challenges": {
- "Path": "/FortniteGame/Content/Athena/Items/ChallengeBundles/",
- "isChecked": "True"
- },
- "[BR] Consumables": {
- "Path": "/FortniteGame/Content/Athena/Items/Consumables/",
- "isChecked": "False"
- },
- "[BR] Traps": {
- "Path": "/FortniteGame/Content/Athena/Items/Traps/",
- "isChecked": "False"
- },
- "[BR] Weapons": {
- "Path": "/FortniteGame/Content/Athena/Items/Weapons/",
- "isChecked": "True"
- },
- "[BR] 2D Assets": {
- "Path": "/FortniteGame/Content/2dAssets/",
- "isChecked": "True"
- },
- "[BR] Featured Images": {
- "Path": "/FortniteGame/Content/UI/Foundation/Textures/BattleRoyale/FeaturedItems/",
- "isChecked": "False"
- },
- "[STW] Heroes": {
- "Path": "/FortniteGame/Content/Heroes/",
- "isChecked": "False"
- },
- "[STW] Defenders": {
- "Path": "/FortniteGame/Content/Items/Defenders/",
- "isChecked": "False"
- },
- "[STW] Schematics": {
- "Path": "/FortniteGame/Content/Items/Schematics/",
- "isChecked": "False"
- },
- "[STW] Traps": {
- "Path": "/FortniteGame/Content/Items/Traps/",
- "isChecked": "False"
- },
- "[STW] Weapons": {
- "Path": "/FortniteGame/Content/Items/Weapons/",
- "isChecked": "False"
- },
- "[STW] Ingredients": {
- "Path": "/FortniteGame/Content/Items/Ingredients/",
- "isChecked": "False"
- },
- "[STW] Persistent Resources": {
- "Path": "/FortniteGame/Content/Items/PersistentResources/",
- "isChecked": "False"
- },
- "[STW] CardPacks": {
- "Path": "/FortniteGame/Content/Items/CardPacks/",
- "isChecked": "False"
- },
- "Tokens": {
- "Path": "/FortniteGame/Content/Items/Tokens/",
- "isChecked": "False"
- },
- "Icons": {
- "Path": "/FortniteGame/Content/UI/Foundation/Textures/Icons/",
- "isChecked": "False"
- },
- "Additional Banners": {
- "Path": "/FortniteGame/Content/UI/Foundation/Textures/Banner/",
- "isChecked": "False"
- },
- "Additional Loading Screens": {
- "Path": "/FortniteGame/Content/UI/Foundation/Textures/LoadingScreens/",
- "isChecked": "False"
- }
-}
-
-
-
-
-
- 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/FModel/App.xaml b/FModel/App.xaml
index f780ac10..a43c45c3 100644
--- a/FModel/App.xaml
+++ b/FModel/App.xaml
@@ -1,17 +1,13 @@
-
-
-
-
+
+
diff --git a/FModel/App.xaml.cs b/FModel/App.xaml.cs
index b13666a4..81295a88 100644
--- a/FModel/App.xaml.cs
+++ b/FModel/App.xaml.cs
@@ -1,20 +1,80 @@
-using FModel.Methods.MessageBox;
-using FModel.Methods.Utilities;
+using FModel.Discord;
+using FModel.Logger;
+using FModel.Utils;
+using FModel.ViewModels.StatusBar;
+using FModel.Windows.DarkMessageBox;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Threading;
using System.Windows;
+using System.Windows.Threading;
namespace FModel
{
///
- /// Logique d'interaction pour App.xaml
+ /// Interaction logic for App.xaml
///
public partial class App : Application
{
- void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
+ internal static Stopwatch StartTimer { get; private set; }
+ static bool framerateSet = false;
+
+ protected override void OnStartup(StartupEventArgs e)
{
- string errorMessage = string.Format("An unhandled exception occurred: {0}", e.Exception.Message);
+ StartTimer = Stopwatch.StartNew();
+
+ //Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
+
+ DebugHelper.Init(LogsFilePath);
+ DebugHelper.WriteLine("{0} {1}", "[FModel]", "––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––");
+ DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Version]", Assembly.GetExecutingAssembly().GetName().Version.ToString());
+ DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Build]", Globals.Build);
+ DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[OS]", Logger.Logger.GetOperatingSystemProductName(true));
+ DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Runtime]", RuntimeInformation.FrameworkDescription);
+ DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Culture]", Thread.CurrentThread.CurrentUICulture);
+
+ StatusBarVm.statusBarViewModel.Set(FModel.Properties.Resources.Initializing, FModel.Properties.Resources.Loading);
+ DiscordIntegration.StartClient();
+
+ base.OnStartup(e);
+ }
+
+ public static string LogsFilePath
+ {
+ get
+ {
+ string filename = string.Format("FModel-Log-{0:yyyy-MM-dd}.txt", DateTime.Now);
+
+ // Copy user settings from previous application version if necessary
+ if (FModel.Properties.Settings.Default.UpdateSettings)
+ FModel.Properties.Settings.Default.Upgrade();
+
+ Folders.LoadFolders();
+
+ return Path.Combine(FModel.Properties.Settings.Default.OutputPath + "\\Logs", filename);
+ }
+ }
+
+ private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
+ {
+ string errorMessage = string.Format(FModel.Properties.Resources.UnhandledExceptionOccured, e.Exception.Message);
DebugHelper.WriteException(e.Exception, "thrown in App.xaml.cs by OnDispatcherUnhandledException");
- DarkMessageBox.Show(errorMessage, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ DarkMessageBoxHelper.Show(errorMessage, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
e.Handled = true;
}
+
+ internal static void SetFramerate()
+ {
+ if (!framerateSet)
+ {
+ System.Windows.Media.Animation.Timeline.DesiredFrameRateProperty.OverrideMetadata(
+ typeof(System.Windows.Media.Animation.Timeline),
+ new FrameworkPropertyMetadata { DefaultValue = 10 });
+ framerateSet = true;
+ }
+ }
}
}
diff --git a/FModel/AssemblyInfo.cs b/FModel/AssemblyInfo.cs
new file mode 100644
index 00000000..8b5504ec
--- /dev/null
+++ b/FModel/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/FModel/Commands/FModel_Commands.cs b/FModel/Commands/FModel_Commands.cs
deleted file mode 100644
index 17503699..00000000
--- a/FModel/Commands/FModel_Commands.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Windows.Input;
-
-namespace FModel.Commands
-{
- static class FModel_Commands
- {
- public static readonly RoutedUICommand OpenSettings = new RoutedUICommand("Open Settings Window", "OpenSettings", typeof(MainWindow));
- public static readonly RoutedUICommand OpenSearch = new RoutedUICommand("Open Search Window", "OpenSearch", typeof(MainWindow));
- public static readonly RoutedUICommand OpenOutput = new RoutedUICommand("Open Output Folder", "OpenOutput", typeof(MainWindow));
- public static readonly RoutedUICommand MergeImages = new RoutedUICommand("Merge Images", "MergeImages", typeof(MainWindow));
- }
-}
diff --git a/FModel/Creator/BaseBundle.cs b/FModel/Creator/BaseBundle.cs
new file mode 100644
index 00000000..648f278e
--- /dev/null
+++ b/FModel/Creator/BaseBundle.cs
@@ -0,0 +1,102 @@
+using FModel.Creator.Bundles;
+using FModel.Creator.Texts;
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.PropertyTagData;
+using System.Collections.Generic;
+
+namespace FModel.Creator
+{
+ public class BaseBundle
+ {
+ public Header DisplayStyle;
+ public string DisplayName;
+ public string FolderName;
+ public string Watermark;
+ public int Width = 1024;
+ public int HeaderHeight = 261; // height is the header basically
+ public int AdditionalSize = 50; // must be increased depending on the number of quests to draw
+ public bool IsDisplayNameShifted;
+ public List Quests;
+ public List CompletionRewards;
+
+ public BaseBundle()
+ {
+ DisplayStyle = new Header();
+ DisplayName = "";
+ FolderName = "";
+ Watermark = Properties.Settings.Default.ChallengeBannerWatermark;
+ Quests = new List();
+ CompletionRewards = new List();
+ }
+
+ ///
+ /// used for the settings
+ ///
+ public BaseBundle(string watermark) : this()
+ {
+ DisplayName = "{DisplayName}";
+ FolderName = "{FolderName}";
+ Watermark = watermark;
+ Quests.Add(new Quest { Description = "", Count = 999, Reward = null });
+ AdditionalSize += 89;
+ }
+
+ public BaseBundle(IUExport export, string assetFolder) : this()
+ {
+ if (export.GetExport("DisplayStyle") is StructProperty displayStyle)
+ DisplayStyle = new Header(displayStyle, assetFolder);
+ if (export.GetExport("DisplayName") is TextProperty displayName)
+ DisplayName = Text.GetTextPropertyBase(displayName);
+
+ if (export.GetExport("CareerQuestBitShifts") is ArrayProperty careerQuestBitShifts)
+ {
+ foreach (SoftObjectProperty questPath in careerQuestBitShifts.Value)
+ {
+ PakPackage p = Utils.GetPropertyPakPackage(questPath.Value.AssetPathName.String);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var obj = p.GetExport();
+ if (obj != null)
+ Quests.Add(new Quest(obj));
+ }
+ }
+ }
+
+ if (export.GetExport("BundleCompletionRewards") is ArrayProperty bundleCompletionRewards)
+ {
+ foreach (StructProperty completionReward in bundleCompletionRewards.Value)
+ {
+ if (completionReward.Value is UObject reward &&
+ reward.TryGetValue("CompletionCount", out var c) && c is IntProperty completionCount &&
+ reward.TryGetValue("Rewards", out var r) && r is ArrayProperty rewards)
+ {
+ foreach (StructProperty rew in rewards.Value)
+ {
+ if (rew.Value is UObject re &&
+ re.TryGetValue("Quantity", out var q) && q is IntProperty quantity &&
+ re.TryGetValue("TemplateId", out var t) && t is StrProperty templateId &&
+ re.TryGetValue("ItemDefinition", out var d) && d is SoftObjectProperty itemDefinition)
+ {
+ if (!itemDefinition.Value.AssetPathName.IsNone &&
+ !itemDefinition.Value.AssetPathName.String.StartsWith("/Game/Items/Tokens/") &&
+ !itemDefinition.Value.AssetPathName.String.StartsWith("/Game/Athena/Items/Quests"))
+ {
+ CompletionRewards.Add(new CompletionReward(completionCount, quantity, itemDefinition));
+ }
+ else if (!string.IsNullOrEmpty(templateId.Value))
+ {
+ CompletionRewards.Add(new CompletionReward(completionCount, quantity, templateId.Value));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ FolderName = assetFolder;
+ AdditionalSize += 95 * Quests.Count;
+ if (CompletionRewards.Count > 0) AdditionalSize += 50 + (95 * CompletionRewards.Count);
+ }
+ }
+}
diff --git a/FModel/Creator/BaseIcon.cs b/FModel/Creator/BaseIcon.cs
new file mode 100644
index 00000000..5c5a6bfb
--- /dev/null
+++ b/FModel/Creator/BaseIcon.cs
@@ -0,0 +1,126 @@
+using FModel.Creator.Icons;
+using FModel.Creator.Rarities;
+using FModel.Creator.Stats;
+using FModel.Creator.Texts;
+using FModel.Utils;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.PropertyTagData;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Windows;
+
+namespace FModel.Creator
+{
+ public class BaseIcon
+ {
+ public SKBitmap FallbackImage;
+ public SKBitmap IconImage;
+ public SKBitmap RarityBackgroundImage;
+ public SKBitmap[] UserFacingFlags;
+ public SKColor[] RarityBackgroundColors;
+ public SKColor[] RarityBorderColor;
+ public string DisplayName;
+ public string Description;
+ public string ShortDescription;
+ public string CosmeticSource;
+ public int Size = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons
+ public int AdditionalSize = 0; // must be increased if there are weapon stats, hero abilities or more to draw/show
+ public int Margin = 2;
+ public List Stats;
+
+ public BaseIcon()
+ {
+ FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream);
+ IconImage = FallbackImage;
+ RarityBackgroundImage = null;
+ UserFacingFlags = null;
+ RarityBackgroundColors = new SKColor[2] { SKColor.Parse("5EBC36"), SKColor.Parse("305C15") };
+ RarityBorderColor = new SKColor[2] { SKColor.Parse("74EF52"), SKColor.Parse("74EF52") };
+ DisplayName = "";
+ Description = "";
+ ShortDescription = "";
+ CosmeticSource = "";
+ Stats = new List();
+ }
+
+ ///
+ /// used to get low res icons ONLY
+ ///
+ ///
+ ///
+ public BaseIcon(IUExport export, string assetName) : this()
+ {
+ if (export.GetExport("HeroDefinition", "WeaponDefinition") is ObjectProperty itemDef)
+ LargeSmallImage.GetPreviewImage(this, itemDef, assetName, false);
+ else if (export.GetExport("SmallPreviewImage", "SmallImage") is SoftObjectProperty previewImage)
+ LargeSmallImage.GetPreviewImage(this, previewImage);
+ }
+
+ ///
+ /// Order:
+ /// 1. Rarity
+ /// 2. Image
+ /// 3. Text
+ /// 1. DisplayName
+ /// 2. Description
+ /// 3. Misc
+ /// 4. GameplayTags
+ /// 1. order doesn't matter
+ /// 2. the importance here is to get the description before gameplay tags
+ ///
+ public BaseIcon(IUExport export, string exportType, string assetName) : this()
+ {
+ // rarity
+ if (export.GetExport("Series") is ObjectProperty series)
+ Serie.GetRarity(this, series);
+ else if (Properties.Settings.Default.UseGameColors) // override default green
+ Rarity.GetInGameRarity(this, export.GetExport("Rarity")); // uncommon will be triggered by Rarity being null
+ else if (export.GetExport("Rarity") is EnumProperty rarity)
+ Rarity.GetHardCodedRarity(this, rarity);
+
+ // image
+ if (Properties.Settings.Default.UseItemShopIcon &&
+ DisplayAssetImage.GetDisplayAssetImage(this, export.GetExport("DisplayAssetPath"), assetName))
+ { } // ^^^^ will return false if image not found, if so, we try to get the normal icon
+ else if (export.GetExport("HeroDefinition", "WeaponDefinition") is ObjectProperty itemDef)
+ LargeSmallImage.GetPreviewImage(this, itemDef, assetName);
+ else if (export.GetExport("LargePreviewImage", "SmallPreviewImage", "ItemDisplayAsset") is SoftObjectProperty previewImage)
+ LargeSmallImage.GetPreviewImage(this, previewImage);
+ else if (export.GetExport("IconBrush") is StructProperty iconBrush) // abilities
+ LargeSmallImage.GetPreviewImage(this, iconBrush);
+
+ // text
+ if (export.GetExport("DisplayName", "DefaultHeaderText", "UIDisplayName") is TextProperty displayName)
+ DisplayName = Text.GetTextPropertyBase(displayName);
+ if (export.GetExport("Description", "DefaultBodyText") is TextProperty description)
+ Description = Text.GetTextPropertyBase(description);
+ else if (export.GetExport("Description") is ArrayProperty arrayDescription) // abilities
+ Description = Text.GetTextPropertyBase(arrayDescription);
+ if (export.GetExport("MaxStackSize") is StructProperty maxStackSize)
+ ShortDescription = Text.GetMaxStackSize(maxStackSize);
+ else if (export.GetExport("ShortDescription") is TextProperty shortDescription)
+ ShortDescription = Text.GetTextPropertyBase(shortDescription);
+ else if (exportType.Equals("AthenaItemWrapDefinition")) // if no ShortDescription it's most likely a wrap
+ ShortDescription = Localizations.GetLocalization("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
+
+ // gameplaytags
+ if (export.GetExport("GameplayTags") is StructProperty gameplayTags)
+ GameplayTag.GetGameplayTags(this, gameplayTags, exportType);
+ else if (export.GetExport("cosmetic_item") is ObjectProperty cosmeticItem) // variants
+ CosmeticSource = cosmeticItem.Value.Resource.ObjectName.String;
+
+ if (export.GetExport("AmmoData") is SoftObjectProperty ammoData)
+ Statistics.GetAmmoData(this, ammoData);
+ if (export.GetExport("WeaponStatHandle") is StructProperty weaponStatHandle)
+ Statistics.GetWeaponStats(this, weaponStatHandle);
+ if (export.GetExport("HeroGameplayDefinition") is ObjectProperty heroGameplayDefinition)
+ Statistics.GetHeroStats(this, heroGameplayDefinition);
+
+ /* Please do not add Schematics support because it takes way too much memory */
+ /* Thank the STW Dev Team for using a 5,69Mb file to get... Oh nvm, they all left */
+
+ AdditionalSize = 48 * Stats.Count;
+ }
+ }
+}
diff --git a/FModel/Creator/BaseUserOption.cs b/FModel/Creator/BaseUserOption.cs
new file mode 100644
index 00000000..80eab7bc
--- /dev/null
+++ b/FModel/Creator/BaseUserOption.cs
@@ -0,0 +1,166 @@
+using FModel.Creator.Texts;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.Objects;
+using PakReader.Parsers.PropertyTagData;
+using SkiaSharp;
+using System.Collections.Generic;
+
+namespace FModel.Creator
+{
+ public class Options
+ {
+ public string Option;
+ public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150);
+ }
+
+ public class BaseUserOption
+ {
+ private readonly SKPaint descriptionPaint = new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Typeface = Text.TypeFaces.DisplayNameTypeface,
+ TextSize = 25,
+ Color = SKColor.Parse("88DBFF"),
+ };
+
+ public string OptionDisplayName;
+ public string OptionDescription;
+ public List OptionValues = new List();
+ public int Width = 512;
+ public int Height = 128;
+ public int Margin = 32;
+
+ public BaseUserOption(IUExport export)
+ {
+ if (export.GetExport("OptionDisplayName") is TextProperty optionDisplayName)
+ OptionDisplayName = Text.GetTextPropertyBase(optionDisplayName).ToUpperInvariant();
+ if (export.GetExport("OptionDescription") is TextProperty optionDescription)
+ {
+ OptionDescription = Text.GetTextPropertyBase(optionDescription);
+ Height += (int)descriptionPaint.TextSize * Helper.SplitLines(OptionDescription, descriptionPaint, Width - Margin).Length;
+ Height += (int)descriptionPaint.TextSize;
+ }
+
+ if (export.GetExport("OptionValues") is ArrayProperty optionValues)
+ {
+ OptionValues = new List(optionValues.Value.Length);
+ for (int i = 0; i < OptionValues.Capacity; i++)
+ {
+ if (optionValues.Value[i] is StructProperty s && s.Value is UObject option)
+ {
+ if (option.TryGetValue("DisplayName", out var v1) && v1 is TextProperty displayName)
+ {
+ var opt = new Options { Option = Text.GetTextPropertyBase(displayName).ToUpperInvariant() };
+ if (option.TryGetValue("Value", out var v) && v is StructProperty value && value.Value is FLinearColor color)
+ opt.Color = SKColor.Parse(color.Hex).WithAlpha(150);
+ OptionValues.Add(opt);
+ }
+ else if (option.TryGetValue("PrimaryAssetName", out var v2) && v2 is NameProperty primaryAssetName)
+ OptionValues.Add(new Options { Option = primaryAssetName.Value.String });
+ }
+ }
+ }
+
+ if (export.GetExport("OptionOnText") is TextProperty optionOnText)
+ OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOnText).ToUpperInvariant() });
+ if (export.GetExport("OptionOffText") is TextProperty optionOffText)
+ OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOffText).ToUpperInvariant() });
+
+ if (export.GetExport("Min", "DefaultValue") is IntProperty iMin &&
+ export.GetExport("Max") is IntProperty iMax)
+ {
+ int increment = iMin.Value;
+ if (export.GetExport("IncrementValue") is IntProperty incrementValue)
+ increment = incrementValue.Value;
+
+ for (int i = iMin.Value; i <= iMax.Value; i += increment)
+ {
+ OptionValues.Add(new Options { Option = i.ToString() });
+ }
+ }
+
+ if (export.GetExport("Min") is FloatProperty fMin &&
+ export.GetExport("Max") is FloatProperty fMax)
+ {
+ float increment = fMin.Value;
+ if (export.GetExport("IncrementValue") is FloatProperty incrementValue)
+ increment = incrementValue.Value;
+
+ for (float i = fMin.Value; i <= fMax.Value; i += increment)
+ {
+ OptionValues.Add(new Options { Option = i.ToString() });
+ }
+ }
+
+ Height += Margin;
+ Height += 35 * OptionValues.Count;
+ }
+
+ public void Draw(SKCanvas c)
+ {
+ c.DrawRect(new SKRect(0, 0, Width, Height),
+ new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Shader = SKShader.CreateLinearGradient(
+ new SKPoint(Width / 2, Height),
+ new SKPoint(Width, Height / 4),
+ new SKColor[2] { SKColor.Parse("01369C"), SKColor.Parse("1273C8") },
+ SKShaderTileMode.Clamp)
+ });
+
+ int textSize = 45;
+ SKPaint namePaint = new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Typeface = Text.TypeFaces.DisplayNameTypeface,
+ TextSize = textSize,
+ Color = SKColors.White,
+ TextAlign = SKTextAlign.Left
+ };
+
+ // resize if too long
+ while (namePaint.MeasureText(OptionDisplayName) > (Width - (Margin * 2)))
+ {
+ namePaint.TextSize = textSize -= 2;
+ }
+ int y = Margin + textSize;
+ c.DrawText(OptionDisplayName, Margin, y, namePaint);
+ y += (int)descriptionPaint.TextSize + (Margin / 2);
+
+ // wrap if too long
+ Helper.DrawMultilineText(c, OptionDescription, Width, Margin, ETextSide.Left,
+ new SKRect(Margin, y, Width - Margin, 256), descriptionPaint, out int top);
+
+ int height = 30;
+ int space = 5;
+ foreach (Options option in OptionValues)
+ {
+ c.DrawRect(new SKRect(Margin, top, Width - Margin, top + height),
+ new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Color = option.Color
+ });
+
+ int ts = 20;
+ c.DrawText(option.Option, Margin + (space * 2), top + (ts * 1.1f),
+ new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Typeface = Text.TypeFaces.DisplayNameTypeface,
+ TextSize = ts,
+ Color = SKColor.Parse("EEFFFF"),
+ TextAlign = SKTextAlign.Left
+ });
+
+ top += height + space;
+ }
+ }
+ }
+}
diff --git a/FModel/Creator/Bundles/CompletionReward.cs b/FModel/Creator/Bundles/CompletionReward.cs
new file mode 100644
index 00000000..3f92b714
--- /dev/null
+++ b/FModel/Creator/Bundles/CompletionReward.cs
@@ -0,0 +1,60 @@
+using FModel.Utils;
+using PakReader.Parsers.PropertyTagData;
+using System;
+
+namespace FModel.Creator.Bundles
+{
+ public class CompletionReward
+ {
+ private const string _TRIGGER1 = "";
+ private const string _TRIGGER2 = ">";
+ public string CompletionText;
+ public Reward Reward;
+
+ public CompletionReward(IntProperty completionCount)
+ {
+ string all = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete all {0} challenges> to earn the reward item");
+ string allFormated = ReformatString(all, completionCount.Value.ToString(), true);
+ string any = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete any {0} challenges> to earn the reward item");
+ string anyFormated = ReformatString(any, completionCount.Value.ToString(), false);
+ CompletionText = completionCount.Value >= 0 ? anyFormated : allFormated;
+
+ Reward = null;
+ }
+
+ public CompletionReward(IntProperty completionCount, IntProperty quantity, SoftObjectProperty itemDefinition) : this(completionCount)
+ {
+ Reward = new Reward(quantity, itemDefinition);
+ }
+
+ public CompletionReward(IntProperty completionCount, IntProperty quantity, string reward) : this(completionCount)
+ {
+ Reward = new Reward(quantity, reward);
+ }
+
+ private string ReformatString(string s, string completionCount, bool isAll)
+ {
+ s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "{0}>");
+
+ int index = s.IndexOf("|plural(", StringComparison.CurrentCultureIgnoreCase);
+ if (index > -1)
+ {
+ int i = s.Substring(index).IndexOf(')', StringComparison.CurrentCultureIgnoreCase);
+ s = s.Replace(s.Substring(index, i + 1), string.Empty).Replace("{0} {0}", "{0}");
+ }
+
+ int index1 = s.IndexOf(_TRIGGER1, StringComparison.CurrentCultureIgnoreCase);
+ if (index1 < 0) index1 = 0;
+ string partOne = s.Substring(0, index1);
+
+ string partTemp = s.Substring(index1 + _TRIGGER1.Length);
+ int index2 = partTemp.IndexOf(_TRIGGER2, StringComparison.CurrentCultureIgnoreCase);
+ if (index2 < 0) index2 = 0;
+ string partUpper = partTemp.Substring(0, index2).ToUpper().Replace("{0}", isAll ? string.Empty : completionCount);
+
+ string partTwo = partTemp.Substring(index2 + _TRIGGER2.Length);
+
+ return string.Format("{0}{1}{2}", partOne, partUpper, partTwo).Replace(" ", " ").Replace(" ,", ",");
+ }
+ }
+}
diff --git a/FModel/Creator/Bundles/Header.cs b/FModel/Creator/Bundles/Header.cs
new file mode 100644
index 00000000..66efc995
--- /dev/null
+++ b/FModel/Creator/Bundles/Header.cs
@@ -0,0 +1,106 @@
+using PakReader.Parsers.Class;
+using PakReader.Parsers.Objects;
+using PakReader.Parsers.PropertyTagData;
+using SkiaSharp;
+using System;
+using System.IO;
+
+namespace FModel.Creator.Bundles
+{
+ public class Header
+ {
+ public SKColor PrimaryColor;
+ public SKColor SecondaryColor;
+ public SKColor AccentColor;
+ public SKBitmap DisplayImage; // 256x256
+ public SKBitmap CustomBackground; // 1024x256
+
+ private readonly Random _random = new Random(Environment.TickCount);
+ private readonly string[] _randomColors = new string[255]
+ {
+ "F44336", "FFEBEE", "FFCDD2", "EF9A9A", "E57373", "EF5350", "E53935", "D32F2F", "C62828", "B71C1C",
+ "FF8A80", "FF5252", "FF1744", "D50000", "FCE4EC", "F8BBD0", "F48FB1", "F06292", "EC407A", "E91E63",
+ "D81B60", "C2185B", "AD1457", "880E4F", "FF80AB", "FF4081", "F50057", "C51162", "F3E5F5", "E1BEE7",
+ "CE93D8", "BA68C8", "AB47BC", "9C27B0", "8E24AA", "7B1FA2", "6A1B9A", "4A148C", "EA80FC", "E040FB",
+ "D500F9", "AA00FF", "EDE7F6", "D1C4E9", "B39DDB", "9575CD", "7E57C2", "673AB7", "5E35B1", "512DA8",
+ "4527A0", "311B92", "B388FF", "7C4DFF", "651FFF", "6200EA", "E8EAF6", "C5CAE9", "9FA8DA", "7986CB",
+ "5C6BC0", "3F51B5", "3949AB", "303F9F", "283593", "1A237E", "8C9EFF", "536DFE", "3D5AFE", "304FFE",
+ "E3F2FD", "BBDEFB", "90CAF9", "64B5F6", "42A5F5", "2196F3", "1E88E5", "1976D2", "1565C0", "0D47A1",
+ "82B1FF", "448AFF", "2979FF", "2962FF", "E1F5FE", "B3E5FC", "81D4FA", "4FC3F7", "29B6F6", "03A9F4",
+ "039BE5", "0288D1", "0277BD", "01579B", "80D8FF", "40C4FF", "00B0FF", "0091EA", "E0F7FA", "B2EBF2",
+ "80DEEA", "4DD0E1", "26C6DA", "00BCD4", "00ACC1", "0097A7", "00838F", "006064", "84FFFF", "18FFFF",
+ "00E5FF", "00B8D4", "E0F2F1", "B2DFDB", "80CBC4", "4DB6AC", "26A69A", "009688", "00897B", "00796B",
+ "00695C", "004D40", "A7FFEB", "64FFDA", "1DE9B6", "00BFA5", "E8F5E9", "C8E6C9", "A5D6A7", "81C784",
+ "66BB6A", "4CAF50", "43A047", "388E3C", "2E7D32", "1B5E20", "B9F6CA", "69F0AE", "00E676", "00C853",
+ "F1F8E9", "DCEDC8", "C5E1A5", "AED581", "9CCC65", "8BC34A", "7CB342", "689F38", "558B2F", "33691E",
+ "CCFF90", "B2FF59", "76FF03", "64DD17", "F9FBE7", "F0F4C3", "E6EE9C", "DCE775", "D4E157", "CDDC39",
+ "C0CA33", "AFB42B", "9E9D24", "827717", "F4FF81", "EEFF41", "C6FF00", "AEEA00", "FFFDE7", "FFF9C4",
+ "FFF59D", "FFF176", "FFEE58", "FFEB3B", "FDD835", "FBC02D", "F9A825", "F57F17", "FFFF8D", "FFFF00",
+ "FFEA00", "FFD600", "FFF8E1", "FFECB3", "FFE082", "FFD54F", "FFCA28", "FFC107", "FFB300", "FFA000",
+ "FF8F00", "FF6F00", "FFE57F", "FFD740", "FFC400", "FFAB00", "FFF3E0", "FFE0B2", "FFCC80", "FFB74D",
+ "FFA726", "FF9800", "FB8C00", "F57C00", "EF6C00", "E65100", "FFD180", "FFAB40", "FF9100", "FF6D00",
+ "FBE9E7", "FFCCBC", "FFAB91", "FF8A65", "FF7043", "FF5722", "F4511E", "E64A19", "D84315", "BF360C",
+ "FF9E80", "FF6E40", "FF3D00", "DD2C00", "EFEBE9", "D7CCC8", "BCAAA4", "A1887F", "8D6E63", "795548",
+ "6D4C41", "5D4037", "4E342E", "3E2723", "FAFAFA", "F5F5F5", "EEEEEE", "E0E0E0", "BDBDBD", "9E9E9E",
+ "757575", "616161", "424242", "212121", "ECEFF1", "CFD8DC", "B0BEC5", "90A4AE", "78909C", "607D8B",
+ "546E7A", "455A64", "37474F", "263238", "000000",
+ };
+
+ public Header()
+ {
+ if (Properties.Settings.Default.UseChallengeBanner)
+ {
+ SKColor mainColor = SKColor.Parse(Properties.Settings.Default.ChallengeBannerPrimaryColor);
+ mainColor.ToHsl(out float h, out float s, out float l);
+ float i = l + 20.0F > 100.0F ? 100.0F - l : 20.0F;
+
+ PrimaryColor = mainColor;
+ SecondaryColor = SKColor.Parse(Properties.Settings.Default.ChallengeBannerSecondaryColor);
+ AccentColor = SKColor.FromHsl(h += i, s, l);
+ DisplayImage = null;
+ if (!string.IsNullOrEmpty(Properties.Settings.Default.ChallengeBannerPath))
+ CustomBackground = SKBitmap.Decode(new FileInfo(Properties.Settings.Default.ChallengeBannerPath).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
+ else CustomBackground = null;
+ }
+ else
+ {
+ SKColor mainColor = SKColor.Parse(_randomColors[_random.Next(0, 255)]);
+ mainColor.ToHsl(out float h, out float s, out float l);
+ while (l > 75 || l < 10)
+ {
+ mainColor = SKColor.Parse(_randomColors[_random.Next(0, 255)]);
+ mainColor.ToHsl(out float _, out float _, out l);
+ }
+ float i = l + 20.0F > 100.0F ? 100.0F - l : 20.0F;
+
+ PrimaryColor = mainColor;
+ SecondaryColor = SKColor.FromHsl(h, s, l += i);
+ AccentColor = SKColor.FromHsl(h += i, s, l);
+ DisplayImage = null;
+ CustomBackground = null;
+ }
+ }
+
+ public Header(StructProperty displayStyle, string assetFolder) : this()
+ {
+ if (displayStyle.Value is UObject o)
+ {
+ if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c1, "PrimaryColor", "Context_LimitedTimeColor") && c1 is StructProperty s1 && s1.Value is FLinearColor primaryColor)
+ PrimaryColor = SKColor.Parse(primaryColor.Hex);
+ if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c2, "SecondaryColor", "Context_BaseColor") && c2 is StructProperty s2 && s2.Value is FLinearColor secondaryColor)
+ SecondaryColor = SKColor.Parse(secondaryColor.Hex);
+ if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue("AccentColor", out var c3) && c3 is StructProperty s3 && s3.Value is FLinearColor accentColor)
+ {
+ AccentColor = SKColor.Parse(accentColor.Hex);
+ if (SecondaryColor.Red + SecondaryColor.Green + SecondaryColor.Blue <= 75 || assetFolder.Equals("LTM", StringComparison.CurrentCultureIgnoreCase)) // if secondary is too dark
+ SecondaryColor = AccentColor; // use accent and pray for accent to be ligher
+ }
+
+ if (o.TryGetValue("DisplayImage", out var i) && i is SoftObjectProperty displayImage)
+ DisplayImage = Utils.GetSoftObjectTexture(displayImage);
+ if (CustomBackground == null && o.TryGetValue("CustomBackground", out var b) && b is SoftObjectProperty customBackground)
+ CustomBackground = Utils.GetSoftObjectTexture(customBackground);
+ }
+ }
+ }
+}
diff --git a/FModel/Creator/Bundles/HeaderStyle.cs b/FModel/Creator/Bundles/HeaderStyle.cs
new file mode 100644
index 00000000..bd0534b3
--- /dev/null
+++ b/FModel/Creator/Bundles/HeaderStyle.cs
@@ -0,0 +1,98 @@
+using FModel.Creator.Texts;
+using SkiaSharp;
+using System;
+
+namespace FModel.Creator.Bundles
+{
+ static class HeaderStyle
+ {
+ public static void DrawHeaderPaint(SKCanvas c, BaseBundle icon)
+ {
+ c.DrawRect(new SKRect(0, 0, icon.Width, icon.HeaderHeight), new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Color = icon.DisplayStyle.PrimaryColor
+ });
+
+ if (icon.DisplayStyle.CustomBackground != null && icon.DisplayStyle.CustomBackground.Height != icon.DisplayStyle.CustomBackground.Width)
+ {
+ icon.IsDisplayNameShifted = false;
+ var bgPaint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen };
+ if (Properties.Settings.Default.UseChallengeBanner) bgPaint.Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.ChallengeBannerOpacity);
+ c.DrawBitmap(icon.DisplayStyle.CustomBackground, new SKRect(0, 0, 1024, 256), bgPaint);
+ }
+ else if (icon.DisplayStyle.DisplayImage != null)
+ {
+ icon.IsDisplayNameShifted = true;
+ if (icon.DisplayStyle.CustomBackground != null && icon.DisplayStyle.CustomBackground.Height == icon.DisplayStyle.CustomBackground.Width)
+ c.DrawBitmap(icon.DisplayStyle.CustomBackground, new SKRect(0, 0, icon.HeaderHeight, icon.HeaderHeight),
+ new SKPaint {
+ IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen,
+ ImageFilter = SKImageFilter.CreateDropShadow(2.5F, 0, 20, 0, icon.DisplayStyle.SecondaryColor.WithAlpha(25), SKDropShadowImageFilterShadowMode.DrawShadowAndForeground)
+ });
+
+ c.DrawBitmap(icon.DisplayStyle.DisplayImage, new SKRect(0, 0, icon.HeaderHeight, icon.HeaderHeight),
+ new SKPaint {
+ IsAntialias = true, FilterQuality = SKFilterQuality.High,
+ ImageFilter = SKImageFilter.CreateDropShadow(-2.5F, 0, 20, 0, icon.DisplayStyle.SecondaryColor.WithAlpha(50), SKDropShadowImageFilterShadowMode.DrawShadowAndForeground)
+ });
+ }
+
+ SKPath pathTop = new SKPath { FillType = SKPathFillType.EvenOdd };
+ pathTop.MoveTo(0, icon.HeaderHeight);
+ pathTop.LineTo(icon.Width, icon.HeaderHeight);
+ pathTop.LineTo(icon.Width, icon.HeaderHeight - 19);
+ pathTop.LineTo(icon.Width / 2 + 7, icon.HeaderHeight - 23);
+ pathTop.LineTo(icon.Width / 2 + 13, icon.HeaderHeight - 7);
+ pathTop.LineTo(0, icon.HeaderHeight - 19);
+ pathTop.Close();
+ c.DrawPath(pathTop, new SKPaint {
+ IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = icon.DisplayStyle.SecondaryColor,
+ ImageFilter = SKImageFilter.CreateDropShadow(-5, -5, 0, 0, icon.DisplayStyle.AccentColor.WithAlpha(75), SKDropShadowImageFilterShadowMode.DrawShadowAndForeground)
+ });
+
+ c.DrawRect(new SKRect(0, icon.HeaderHeight, icon.Width, icon.HeaderHeight + icon.AdditionalSize), new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Color = icon.DisplayStyle.PrimaryColor.WithAlpha(200) // default background is black, so i'm kinda lowering the brightness here and that's what i want
+ });
+ }
+
+ public static void DrawHeaderText(SKCanvas c, BaseBundle icon)
+ {
+ using SKPaint paint = new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Typeface = Text.TypeFaces.BundleDisplayNameTypeface,
+ TextSize = 50,
+ Color = SKColors.White,
+ TextAlign = SKTextAlign.Left,
+ };
+
+ string text = icon.DisplayName.ToUpper();
+ int x = icon.IsDisplayNameShifted ? 300 : 50;
+ while (paint.MeasureText(text) > (icon.Width - x))
+ {
+ paint.TextSize -= 2;
+ }
+ c.DrawText(text, x, 155, paint);
+
+ paint.Color = SKColors.White.WithAlpha(150);
+ paint.TextAlign = SKTextAlign.Right;
+ paint.TextSize = 23;
+ c.DrawText(icon.Watermark
+ .Replace("{BundleName}", text)
+ .Replace("{Date}", DateTime.Now.ToString("dd/MM/yyyy")),
+ icon.Width - 25, icon.HeaderHeight - 40, paint);
+
+ paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
+ paint.Color = icon.DisplayStyle.SecondaryColor;
+ paint.TextAlign = SKTextAlign.Left;
+ paint.TextSize = 30;
+ c.DrawText(icon.FolderName.ToUpper(), x, 95, paint);
+ }
+ }
+}
diff --git a/FModel/Creator/Bundles/Quest.cs b/FModel/Creator/Bundles/Quest.cs
new file mode 100644
index 00000000..ee0847b0
--- /dev/null
+++ b/FModel/Creator/Bundles/Quest.cs
@@ -0,0 +1,94 @@
+using FModel.Creator.Texts;
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.PropertyTagData;
+
+namespace FModel.Creator.Bundles
+{
+ public class Quest
+ {
+ public string Description;
+ public int Count;
+ public Reward Reward;
+
+ public Quest()
+ {
+ Description = "";
+ Count = 0;
+ Reward = null;
+ }
+
+ public Quest(UObject obj) : this()
+ {
+ if (obj.TryGetValue("Description", out var d) && d is TextProperty description)
+ Description = Text.GetTextPropertyBase(description);
+ if (obj.TryGetValue("ObjectiveCompletionCount", out var o) && o is IntProperty objectiveCompletionCount)
+ Count = objectiveCompletionCount.Value;
+
+ if (obj.TryGetValue("Objectives", out var v1) && v1 is ArrayProperty a1 &&
+ a1.Value.Length > 0 && a1.Value[0] is StructProperty s && s.Value is UObject objectives)
+ {
+ if (string.IsNullOrEmpty(Description) && objectives.TryGetValue("Description", out var od) && od is TextProperty objectivesDescription)
+ Description = Text.GetTextPropertyBase(objectivesDescription);
+
+ if (Count == 0 && objectives.TryGetValue("Count", out var c) && c is IntProperty count)
+ Count = count.Value;
+ }
+
+ if (obj.TryGetValue("RewardsTable", out var v4) && v4 is ObjectProperty rewardsTable)
+ {
+ PakPackage p = Utils.GetPropertyPakPackage(rewardsTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var u = p.GetExport();
+ if (u != null && u.TryGetValue("Default", out var i) && i is UObject r &&
+ r.TryGetValue("TemplateId", out var i1) && i1 is NameProperty templateId &&
+ r.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity)
+ {
+ Reward = new Reward(quantity, templateId);
+ }
+ }
+ }
+
+ if (Reward == null && obj.TryGetValue("Rewards", out var v2) && v2 is ArrayProperty rewards)
+ {
+ foreach (StructProperty reward in rewards.Value)
+ {
+ if (reward.Value is UObject r1 &&
+ r1.TryGetValue("ItemPrimaryAssetId", out var i1) && i1 is StructProperty itemPrimaryAssetId &&
+ r1.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity)
+ {
+ if (itemPrimaryAssetId.Value is UObject r2 &&
+ r2.TryGetValue("PrimaryAssetType", out var t1) && t1 is StructProperty primaryAssetType &&
+ r2.TryGetValue("PrimaryAssetName", out var t2) && t2 is NameProperty primaryAssetName)
+ {
+ if (primaryAssetType.Value is UObject r3 && r3.TryGetValue("Name", out var k) && k is NameProperty name)
+ {
+ if (!name.Value.String.Equals("Quest") && !name.Value.String.Equals("Token") &&
+ !name.Value.String.Equals("ChallengeBundle") && !name.Value.String.Equals("GiftBox"))
+ {
+ Reward = new Reward(quantity, primaryAssetName);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (Reward == null && obj.TryGetValue("HiddenRewards", out var v3) && v3 is ArrayProperty hiddenRewards)
+ {
+ foreach (StructProperty reward in hiddenRewards.Value)
+ {
+ if (reward.Value is UObject r1 &&
+ r1.TryGetValue("TemplateId", out var i1) && i1 is NameProperty templateId &&
+ r1.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity)
+ {
+ Reward = new Reward(quantity, templateId);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/FModel/Creator/Bundles/QuestStyle.cs b/FModel/Creator/Bundles/QuestStyle.cs
new file mode 100644
index 00000000..70d3af47
--- /dev/null
+++ b/FModel/Creator/Bundles/QuestStyle.cs
@@ -0,0 +1,174 @@
+using FModel.Creator.Texts;
+using SkiaSharp;
+
+namespace FModel.Creator.Bundles
+{
+ static class QuestStyle
+ {
+ public static void DrawQuests(SKCanvas c, BaseBundle icon)
+ {
+ using SKPaint paint = new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ TextSize = 27,
+ Color = SKColors.White,
+ TextAlign = SKTextAlign.Left,
+ Typeface = Text.TypeFaces.BundleDisplayNameTypeface
+ };
+
+ int y = icon.HeaderHeight + 50;
+ foreach (Quest q in icon.Quests)
+ {
+ DrawQuestBackground(c, icon, y, true);
+
+ paint.TextSize = 27;
+ paint.ImageFilter = null;
+ paint.Color = SKColors.White;
+ paint.TextAlign = SKTextAlign.Left;
+ paint.Typeface = Text.TypeFaces.BundleDisplayNameTypeface;
+ while (paint.MeasureText(q.Description) > icon.Width - 65 - 165)
+ {
+ paint.TextSize -= 1;
+ }
+ c.DrawText(q.Description, new SKPoint(65, y + paint.TextSize + 11), paint);
+
+ paint.TextSize = 16;
+ paint.Color = SKColors.White.WithAlpha(200);
+ paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
+ c.DrawText(q.Count.ToString(), new SKPoint(93 + icon.Width - (175 * 3), y + 60), paint);
+
+ if (q.Reward?.RewardIcon != null)
+ {
+ if (q.Reward.IsCountShifted)
+ {
+ int l = q.Reward.RewardQuantity.ToString().Length;
+ paint.TextSize = l >= 5 ? 30 : 35;
+ paint.TextAlign = SKTextAlign.Right;
+ paint.Color = SKColor.Parse(q.Reward.RewardFillColor);
+ paint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColor.Parse(q.Reward.RewardBorderColor).WithAlpha(200), SKDropShadowImageFilterShadowMode.DrawShadowAndForeground);
+ c.DrawText(q.Reward.RewardQuantity.ToString(), new SKPoint(icon.Width - 85, y + 47.5F), paint);
+ c.DrawBitmap(q.Reward.RewardIcon, new SKPoint(icon.Width - 30 - q.Reward.RewardIcon.Width, y + 12.5F),
+ new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
+ }
+ else
+ c.DrawBitmap(q.Reward.RewardIcon, new SKPoint(icon.Width - 125, y + 5),
+ new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
+ }
+
+ y += 95;
+ }
+ }
+
+ public static void DrawCompletionRewards(SKCanvas c, BaseBundle icon)
+ {
+ using SKPaint paint = new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ TextSize = 35,
+ Color = SKColors.White,
+ TextAlign = SKTextAlign.Left,
+ Typeface = Text.TypeFaces.BundleDisplayNameTypeface
+ };
+
+ int y = icon.HeaderHeight + (50 * 2) + (95 * icon.Quests.Count);
+ foreach (CompletionReward r in icon.CompletionRewards)
+ {
+ DrawQuestBackground(c, icon, y, false);
+
+ paint.TextSize = 35;
+ paint.ImageFilter = null;
+ paint.Color = SKColors.White;
+ paint.TextAlign = SKTextAlign.Left;
+ paint.Typeface = Text.TypeFaces.BundleDisplayNameTypeface;
+ while (paint.MeasureText(r.CompletionText) > icon.Width - 65 - 165)
+ {
+ paint.TextSize -= 1;
+ }
+ c.DrawText(r.CompletionText, new SKPoint(65, y + paint.TextSize + 15), paint);
+
+ if (r.Reward?.RewardIcon != null)
+ {
+ if (r.Reward.IsCountShifted)
+ {
+ int l = r.Reward.RewardQuantity.ToString().Length;
+ paint.TextSize = l >= 5 ? 30 : 35;
+ paint.TextAlign = SKTextAlign.Right;
+ paint.Color = SKColor.Parse(r.Reward.RewardFillColor);
+ paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
+ paint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColor.Parse(r.Reward.RewardBorderColor).WithAlpha(200), SKDropShadowImageFilterShadowMode.DrawShadowAndForeground);
+ c.DrawText(r.Reward.RewardQuantity.ToString(), new SKPoint(icon.Width - 85, y + 47.5F), paint);
+ c.DrawBitmap(r.Reward.RewardIcon, new SKPoint(icon.Width - 30 - r.Reward.RewardIcon.Width, y + 12.5F),
+ new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
+ }
+ else
+ c.DrawBitmap(r.Reward.RewardIcon, new SKPoint(icon.Width - 125, y + 5),
+ new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
+ }
+
+ y += 95;
+ }
+ }
+
+ private static void DrawQuestBackground(SKCanvas c, BaseBundle icon, int y, bool hasSlider)
+ {
+ SKColor baseColor = icon.DisplayStyle.PrimaryColor;
+ using SKPaint paint = new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Color = baseColor
+ };
+ using SKPath secondaryRect = new SKPath
+ {
+ FillType = SKPathFillType.EvenOdd
+ };
+ using SKPath selector = new SKPath
+ {
+ FillType = SKPathFillType.EvenOdd
+ };
+ using SKPath slider = new SKPath
+ {
+ FillType = SKPathFillType.EvenOdd
+ };
+
+ c.DrawRect(new SKRect(25, y, icon.Width - 25, y + 75), paint);
+
+ baseColor.ToHsl(out float h, out float s, out float l);
+ baseColor = SKColor.FromHsl(h, s, l + 5);
+ paint.Color = baseColor;
+
+ secondaryRect.MoveTo(32, y + 5);
+ secondaryRect.LineTo(icon.Width - 155, y + 4);
+ secondaryRect.LineTo(icon.Width - 175, y + 68);
+ secondaryRect.LineTo(29, y + 71);
+ secondaryRect.Close();
+ c.DrawPath(secondaryRect, paint);
+
+ paint.Color = icon.DisplayStyle.SecondaryColor;
+ selector.MoveTo(41, y + 38);
+ selector.LineTo(48, y + 34);
+ selector.LineTo(52, y + 39);
+ selector.LineTo(46, y + 44);
+ selector.Close();
+ c.DrawPath(selector, paint);
+
+ if (hasSlider)
+ {
+ slider.MoveTo(65, y + 53);
+ slider.LineTo(65 + icon.Width - (175 * 3), y + 53);
+ slider.LineTo(65 + icon.Width - (175 * 3), y + 58);
+ slider.LineTo(65, y + 58);
+ slider.Close();
+ c.DrawPath(slider, paint);
+
+ paint.TextSize = 14;
+ paint.Color = SKColors.White;
+ paint.TextAlign = SKTextAlign.Left;
+ paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
+ c.DrawText("0 / ", new SKPoint(75 + icon.Width - (175 * 3), y + 59), paint);
+ }
+ }
+ }
+}
diff --git a/FModel/Creator/Bundles/Reward.cs b/FModel/Creator/Bundles/Reward.cs
new file mode 100644
index 00000000..fd38be35
--- /dev/null
+++ b/FModel/Creator/Bundles/Reward.cs
@@ -0,0 +1,134 @@
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.PropertyTagData;
+using SkiaSharp;
+using System;
+
+namespace FModel.Creator.Bundles
+{
+ public class Reward
+ {
+ public int RewardQuantity;
+ public SKBitmap RewardIcon;
+ public string RewardFillColor;
+ public string RewardBorderColor;
+ public bool IsCountShifted;
+
+ public Reward()
+ {
+ RewardQuantity = 0;
+ RewardIcon = null;
+ RewardFillColor = "";
+ RewardBorderColor = "";
+ IsCountShifted = false;
+ }
+
+ public Reward(IntProperty quantity, NameProperty primaryAssetName) : this(quantity, primaryAssetName.Value.String) { }
+ public Reward(IntProperty quantity, string assetName) : this()
+ {
+ RewardQuantity = quantity.Value;
+
+ if (assetName.Contains(':'))
+ {
+ string[] parts = assetName.Split(':');
+ if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
+ {
+ PakPackage p = Utils.GetPropertyPakPackage("/Game/Banners/BannerIcons");
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var c = p.GetExport();
+ if (c != null && c.TryGetCaseInsensitiveValue(parts[1], out var s) && s is UObject banner)
+ {
+ RewardIcon = new BaseIcon(banner, "BannerIcons.uasset").IconImage.Resize(64, 64);
+ }
+ }
+ }
+ else GetReward(parts[1]);
+ }
+ else GetReward(assetName);
+ }
+
+ public Reward(IntProperty quantity, SoftObjectProperty itemDefinition) : this()
+ {
+ RewardQuantity = quantity.Value;
+
+ PakPackage p = Utils.GetPropertyPakPackage(itemDefinition.Value.AssetPathName.String);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var d = p.GetExport();
+ if (d != null)
+ {
+ int s1 = itemDefinition.Value.AssetPathName.String.LastIndexOf('/');
+ if (s1 < 0) s1 = 0;
+ int s2 = itemDefinition.Value.AssetPathName.String.LastIndexOf('.') - s1;
+ switch (itemDefinition.Value.AssetPathName.String)
+ {
+ case "/Game/Items/PersistentResources/AthenaBattleStar.AthenaBattleStar":
+ IsCountShifted = true;
+ RewardFillColor = "FFDB67";
+ RewardBorderColor = "8F4A20";
+ RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints").Resize(48, 48);
+ break;
+ case "/Game/Items/PersistentResources/AthenaSeasonalXP.AthenaSeasonalXP":
+ IsCountShifted = true;
+ RewardFillColor = "E6FDB1";
+ RewardBorderColor = "51830F";
+ RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium").Resize(48, 48);
+ break;
+ case "/Game/Items/Currency/MtxGiveaway.MtxGiveaway":
+ IsCountShifted = true;
+ RewardFillColor = "DCE6FF";
+ RewardBorderColor = "64A0AF";
+ RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-Items-MTX").Resize(48, 48);
+ break;
+ default:
+ IsCountShifted = false;
+ RewardIcon = new BaseIcon(d, itemDefinition.Value.AssetPathName.String.Substring(s1, s2) + ".uasset").IconImage.Resize(64, 64);
+ break;
+ }
+ }
+ }
+ }
+
+ private void GetReward(string trigger)
+ {
+ switch (trigger.ToLower())
+ {
+ case "athenabattlestar":
+ IsCountShifted = true;
+ RewardFillColor = "FFDB67";
+ RewardBorderColor = "8F4A20";
+ RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints").Resize(48, 48);
+ break;
+ case "athenaseasonalxp":
+ IsCountShifted = true;
+ RewardFillColor = "E6FDB1";
+ RewardBorderColor = "51830F";
+ RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium").Resize(48, 48);
+ break;
+ case "mtxgiveaway":
+ IsCountShifted = true;
+ RewardFillColor = "DCE6FF";
+ RewardBorderColor = "64A0AF";
+ RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-Items-MTX").Resize(48, 48);
+ break;
+ default:
+ {
+ string path = Utils.GetFullPath($"/FortniteGame/Content/Athena/.*?/{trigger}.*").Replace("FortniteGame/Content", "Game");
+ PakPackage p = Utils.GetPropertyPakPackage(path);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var d = p.GetExport();
+ if (d != null)
+ {
+ int i = path.LastIndexOf('/');
+ IsCountShifted = false;
+ RewardIcon = new BaseIcon(d, path.Substring(i > 0 ? i : 0) + ".uasset").IconImage.Resize(64, 64);
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/FModel/Creator/Creator.cs b/FModel/Creator/Creator.cs
new file mode 100644
index 00000000..adf3f13a
--- /dev/null
+++ b/FModel/Creator/Creator.cs
@@ -0,0 +1,180 @@
+using FModel.Creator.Bundles;
+using FModel.Creator.Icons;
+using FModel.Creator.Rarities;
+using FModel.Creator.Stats;
+using FModel.Creator.Texts;
+using FModel.ViewModels.ImageBox;
+using PakReader.Parsers.Class;
+using SkiaSharp;
+using System.IO;
+
+namespace FModel.Creator
+{
+ static class Creator
+ {
+ ///
+ /// we draw based on the fist export type of the asset, no need to check others it's a waste of time
+ /// i don't cache images because i don't wanna store a lot of SKCanvas in the memory
+ ///
+ /// true if an icon has been drawn
+ public static bool TryDrawIcon(string assetPath, string exportType, IUExport export)
+ {
+ var d = new DirectoryInfo(assetPath);
+ string assetName = d.Name;
+ string assetFolder = d.Parent.Name;
+ if (Text.TypeFaces.NeedReload(false))
+ Text.TypeFaces = new Typefaces(); // when opening bundle creator settings without loading paks first
+
+ // please respect my wave if you wanna add a new exportType
+ // Athena first, then Fort, thank you
+ switch (exportType)
+ {
+ case "AthenaConsumableEmoteItemDefinition":
+ case "AthenaSkyDiveContrailItemDefinition":
+ case "AthenaLoadingScreenItemDefinition":
+ case "AthenaVictoryPoseItemDefinition":
+ case "AthenaPetCarrierItemDefinition":
+ case "AthenaMusicPackItemDefinition":
+ case "AthenaBattleBusItemDefinition":
+ case "AthenaCharacterItemDefinition":
+ case "AthenaBackpackItemDefinition":
+ case "AthenaPickaxeItemDefinition":
+ case "AthenaGadgetItemDefinition":
+ case "AthenaGliderItemDefinition":
+ case "AthenaDailyQuestDefinition":
+ case "AthenaSprayItemDefinition":
+ case "AthenaDanceItemDefinition":
+ case "AthenaEmojiItemDefinition":
+ case "AthenaItemWrapDefinition":
+ case "AthenaToyItemDefinition":
+ case "FortHeroType":
+ case "FortTokenType":
+ case "FortAbilityKit":
+ case "FortWorkerType":
+ case "FortBannerTokenType":
+ case "FortVariantTokenType":
+ case "FortFeatItemDefinition":
+ case "FortStatItemDefinition":
+ case "FortTrapItemDefinition":
+ case "FortAmmoItemDefinition":
+ case "FortQuestItemDefinition":
+ case "FortBadgeItemDefinition":
+ case "FortAwardItemDefinition":
+ case "FortGadgetItemDefinition":
+ case "FortPlaysetItemDefinition":
+ case "FortGiftBoxItemDefinition":
+ case "FortSpyTechItemDefinition":
+ case "FortAccoladeItemDefinition":
+ case "FortCardPackItemDefinition":
+ case "FortDefenderItemDefinition":
+ case "FortCurrencyItemDefinition":
+ case "FortResourceItemDefinition":
+ case "FortSchematicItemDefinition":
+ case "FortIngredientItemDefinition":
+ case "FortWeaponMeleeItemDefinition":
+ case "FortContextTrapItemDefinition":
+ case "FortPlayerPerksItemDefinition":
+ case "FortPlaysetPropItemDefinition":
+ case "FortHomebaseNodeItemDefinition":
+ case "FortWeaponRangedItemDefinition":
+ case "FortNeverPersistItemDefinition":
+ case "FortPlaysetGrenadeItemDefinition":
+ case "FortPersonalVehicleItemDefinition":
+ case "FortHardcoreModifierItemDefinition":
+ case "FortConsumableAccountItemDefinition":
+ case "FortConversionControlItemDefinition":
+ case "FortPersistentResourceItemDefinition":
+ case "FortCampaignHeroLoadoutItemDefinition":
+ case "FortConditionalResourceItemDefinition":
+ case "FortChallengeBundleScheduleDefinition":
+ case "FortWeaponMeleeDualWieldItemDefinition":
+ case "FortDailyRewardScheduleTokenDefinition":
+ {
+ BaseIcon icon = new BaseIcon(export, exportType, assetName);
+ int height = icon.Size + icon.AdditionalSize;
+ using (var ret = new SKBitmap(icon.Size, height, SKColorType.Rgba8888, SKAlphaType.Opaque))
+ using (var c = new SKCanvas(ret))
+ {
+ Rarity.DrawRarity(c, icon);
+ LargeSmallImage.DrawPreviewImage(c, icon);
+ if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText)
+ {
+ Text.DrawBackground(c, icon);
+ Text.DrawDisplayName(c, icon);
+ Text.DrawDescription(c, icon);
+ if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.Mini)
+ {
+ if (!icon.ShortDescription.Equals(icon.DisplayName) && !icon.ShortDescription.Equals(icon.Description))
+ Text.DrawToBottom(c, icon, ETextSide.Left, icon.ShortDescription);
+ Text.DrawToBottom(c, icon, ETextSide.Right, icon.CosmeticSource);
+ }
+ }
+ UserFacingFlag.DrawUserFacingFlags(c, icon);
+
+ // has more things to show
+ if (height > icon.Size)
+ {
+ Statistics.DrawStats(c, icon);
+ }
+
+ Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
+ ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
+ }
+ return true;
+ }
+ case "FortItemSeriesDefinition":
+ {
+ BaseIcon icon = new BaseIcon();
+ using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Opaque))
+ using (var c = new SKCanvas(ret))
+ {
+ Serie.GetRarity(icon, export);
+ Rarity.DrawRarity(c, icon);
+
+ Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
+ ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
+ }
+ return true;
+ }
+ case "PlaylistUserOptionEnum":
+ case "PlaylistUserOptionBool":
+ case "PlaylistUserOptionString":
+ case "PlaylistUserOptionIntEnum":
+ case "PlaylistUserOptionIntRange":
+ case "PlaylistUserOptionColorEnum":
+ case "PlaylistUserOptionFloatEnum":
+ case "PlaylistUserOptionFloatRange":
+ case "PlaylistUserOptionPrimaryAsset":
+ case "PlaylistUserOptionCollisionProfileEnum":
+ {
+ BaseUserOption icon = new BaseUserOption(export);
+ using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Opaque))
+ using (var c = new SKCanvas(ret))
+ {
+ icon.Draw(c);
+
+ Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
+ ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
+ }
+ return true;
+ }
+ case "FortChallengeBundleItemDefinition":
+ {
+ BaseBundle icon = new BaseBundle(export, assetFolder);
+ using (var ret = new SKBitmap(icon.Width, icon.HeaderHeight + icon.AdditionalSize, SKColorType.Rgba8888, SKAlphaType.Opaque))
+ using (var c = new SKCanvas(ret))
+ {
+ HeaderStyle.DrawHeaderPaint(c, icon);
+ HeaderStyle.DrawHeaderText(c, icon);
+ QuestStyle.DrawQuests(c, icon);
+ QuestStyle.DrawCompletionRewards(c, icon);
+
+ ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/FModel/Creator/Icons/DisplayAssetImage.cs b/FModel/Creator/Icons/DisplayAssetImage.cs
new file mode 100644
index 00000000..df232b55
--- /dev/null
+++ b/FModel/Creator/Icons/DisplayAssetImage.cs
@@ -0,0 +1,55 @@
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.PropertyTagData;
+
+namespace FModel.Creator.Icons
+{
+ static class DisplayAssetImage
+ {
+ public static bool GetDisplayAssetImage(BaseIcon icon, SoftObjectProperty o, string assetName)
+ {
+ string imageType = "DetailsImage";
+ switch ("DA_Featured_" + assetName)
+ {
+ case "DA_Featured_Glider_ID_141_AshtonBoardwalk.uasset":
+ case "DA_Featured_Glider_ID_150_TechOpsBlue.uasset":
+ case "DA_Featured_Glider_ID_131_SpeedyMidnight.uasset":
+ case "DA_Featured_Pickaxe_ID_178_SpeedyMidnight.uasset":
+ case "DA_Featured_Glider_ID_015_Brite.uasset":
+ case "DA_Featured_Glider_ID_016_Tactical.uasset":
+ case "DA_Featured_Glider_ID_017_Assassin.uasset":
+ case "DA_Featured_Pickaxe_ID_027_Scavenger.uasset":
+ case "DA_Featured_Pickaxe_ID_028_Space.uasset":
+ case "DA_Featured_Pickaxe_ID_029_Assassin.uasset":
+ return false;
+ case "DA_Featured_Glider_ID_070_DarkViking.uasset":
+ case "DA_Featured_CID_319_Athena_Commando_F_Nautilus.uasset":
+ imageType = "TileImage";
+ break;
+ }
+
+ string path = o?.Value.AssetPathName.String;
+ if (string.IsNullOrEmpty(path))
+ path = "/Game/Catalog/DisplayAssets/DA_Featured_" + assetName.Substring(0, assetName.LastIndexOf("."));
+
+ PakPackage p = Utils.GetPropertyPakPackage(path);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var obj = p.GetExport();
+ if (obj != null)
+ {
+ if (obj.TryGetValue(imageType, out var v1) && v1 is StructProperty s && s.Value is UObject type &&
+ type.TryGetValue("ResourceObject", out var v2) && v2 is ObjectProperty resourceObject)
+ {
+ if (!resourceObject.Value.Resource.OuterIndex.Resource.ObjectName.String.Contains("/Game/Athena/Prototype/Textures/"))
+ {
+ icon.IconImage = Utils.GetObjectTexture(resourceObject);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/FModel/Creator/Icons/LargeSmallImage.cs b/FModel/Creator/Icons/LargeSmallImage.cs
new file mode 100644
index 00000000..3bd18e77
--- /dev/null
+++ b/FModel/Creator/Icons/LargeSmallImage.cs
@@ -0,0 +1,41 @@
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.PropertyTagData;
+using SkiaSharp;
+
+namespace FModel.Creator.Icons
+{
+ static class LargeSmallImage
+ {
+ public static void GetPreviewImage(BaseIcon icon, StructProperty u)
+ {
+ if (u.Value is UObject o && o.TryGetValue("ResourceObject", out var v) && v is ObjectProperty resourceObject)
+ icon.IconImage = Utils.GetObjectTexture(resourceObject);
+ }
+ public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName) => GetPreviewImage(icon, o, assetName, true);
+ public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName, bool hightRes)
+ {
+ string path = o.Value.Resource.OuterIndex.Resource.ObjectName.String;
+ if (path.Equals("/Game/Athena/Items/Weapons/WID_Harvest_Pickaxe_STWCosmetic_Tier"))
+ path += "_" + assetName.Substring(assetName.LastIndexOf(".") - 1, 1);
+
+ PakPackage p = Utils.GetPropertyPakPackage(path);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var obj = p.GetExport();
+ if (obj != null)
+ {
+ if (hightRes && obj.TryGetValue("LargePreviewImage", out var sLarge) && sLarge is SoftObjectProperty largePreviewImage)
+ GetPreviewImage(icon, largePreviewImage);
+ else if (obj.TryGetValue("SmallPreviewImage", out var sSmall) && sSmall is SoftObjectProperty smallPreviewImage)
+ GetPreviewImage(icon, smallPreviewImage);
+ }
+ }
+ }
+ public static void GetPreviewImage(BaseIcon icon, SoftObjectProperty s) => icon.IconImage = Utils.GetSoftObjectTexture(s);
+
+ public static void DrawPreviewImage(SKCanvas c, BaseIcon icon) =>
+ c.DrawBitmap(icon.IconImage ?? icon.FallbackImage, new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin),
+ new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
+ }
+}
diff --git a/FModel/Creator/Icons/UserFacingFlag.cs b/FModel/Creator/Icons/UserFacingFlag.cs
new file mode 100644
index 00000000..a8ac086c
--- /dev/null
+++ b/FModel/Creator/Icons/UserFacingFlag.cs
@@ -0,0 +1,72 @@
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.Objects;
+using PakReader.Parsers.PropertyTagData;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Windows;
+
+namespace FModel.Creator.Icons
+{
+ static class UserFacingFlag
+ {
+ public static void GetUserFacingFlags(List uffs, BaseIcon icon, string exportType)
+ {
+ if (uffs.Count > 0)
+ {
+ PakPackage p = Utils.GetPropertyPakPackage("/Game/Items/ItemCategories"); //PrimaryCategories - SecondaryCategories - TertiaryCategories
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var o = p.GetExport();
+ if (o != null && o.TryGetValue("TertiaryCategories", out var tertiaryCategories) && tertiaryCategories is ArrayProperty tertiaryArray)
+ {
+ icon.UserFacingFlags = new SKBitmap[uffs.Count];
+ for (int i = 0; i < uffs.Count; i++)
+ {
+ if (uffs[i].Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests"))
+ {
+ if (exportType.Equals("AthenaPetCarrierItemDefinition"))
+ icon.UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png")).Stream);
+ else
+ icon.UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png")).Stream);
+ }
+ else
+ {
+ foreach (StructProperty structProp in tertiaryArray.Value)
+ {
+ if (structProp.Value is UObject mainUObject &&
+ mainUObject.TryGetValue("TagContainer", out var struc1) && struc1 is StructProperty tagContainer && tagContainer.Value is FGameplayTagContainer f && f.GameplayTags.TryGetGameplayTag(uffs[i], out var _) &&
+ mainUObject.TryGetValue("CategoryBrush", out var struc2) && struc2 is StructProperty categoryBrush && categoryBrush.Value is UObject categoryUObject &&
+ categoryUObject.TryGetValue("Brush_XXS", out var struc3) && struc3 is StructProperty brushXXS && brushXXS.Value is UObject brushUObject &&
+ brushUObject.TryGetValue("ResourceObject", out var object1) && object1 is ObjectProperty resourceObject)
+ {
+ icon.UserFacingFlags[i] = Utils.GetObjectTexture(resourceObject);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static void DrawUserFacingFlags(SKCanvas c, BaseIcon icon)
+ {
+ if (icon.UserFacingFlags != null)
+ {
+ int size = 25;
+ int x = icon.Margin * (int)2.5;
+ foreach (SKBitmap b in icon.UserFacingFlags)
+ {
+ if (b == null)
+ continue;
+
+ c.DrawBitmap(b.Resize(size, size), new SKPoint(x, icon.Margin * (int)2.5), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
+ x += size;
+ }
+ }
+ }
+ }
+}
diff --git a/FModel/Creator/Icons/Watermark.cs b/FModel/Creator/Icons/Watermark.cs
new file mode 100644
index 00000000..c6b952c3
--- /dev/null
+++ b/FModel/Creator/Icons/Watermark.cs
@@ -0,0 +1,31 @@
+using SkiaSharp;
+using System.IO;
+
+namespace FModel.Creator.Icons
+{
+ static class Watermark
+ {
+ public static void DrawWatermark(SKCanvas c)
+ {
+ if (Properties.Settings.Default.UseIconWatermark && !string.IsNullOrWhiteSpace(Properties.Settings.Default.IconWatermarkPath))
+ {
+ using SKBitmap watermarkBase = SKBitmap.Decode(new FileInfo(Properties.Settings.Default.IconWatermarkPath).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
+ int sizeX = watermarkBase.Width * (int)Properties.Settings.Default.IconWatermarkScale / 512;
+ int sizeY = watermarkBase.Height * (int)Properties.Settings.Default.IconWatermarkScale / 512;
+ SKBitmap watermark = watermarkBase.Resize(sizeX, sizeY);
+
+ float left = Properties.Settings.Default.IconWatermarkX;
+ float top = Properties.Settings.Default.IconWatermarkY;
+ float right = left + watermark.Width;
+ float bottom = top + watermark.Height;
+ c.DrawBitmap(watermark, new SKRect(left, top, right, bottom),
+ new SKPaint
+ {
+ FilterQuality = SKFilterQuality.High,
+ IsAntialias = true,
+ Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.IconWatermarkOpacity)
+ });
+ }
+ }
+ }
+}
diff --git a/FModel/Creator/Rarities/EFortRarity.cs b/FModel/Creator/Rarities/EFortRarity.cs
new file mode 100644
index 00000000..412e7988
--- /dev/null
+++ b/FModel/Creator/Rarities/EFortRarity.cs
@@ -0,0 +1,15 @@
+namespace FModel.Creator.Rarities
+{
+ public enum EFortRarity : int
+ {
+ Common,
+ Uncommon,
+ Rare,
+ Epic,
+ Legendary,
+ Mythic,
+ Transcendent,
+ Unattainable,
+ //Impossible
+ }
+}
diff --git a/FModel/Creator/Rarities/Rarity.cs b/FModel/Creator/Rarities/Rarity.cs
new file mode 100644
index 00000000..069a9897
--- /dev/null
+++ b/FModel/Creator/Rarities/Rarity.cs
@@ -0,0 +1,171 @@
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.Objects;
+using PakReader.Parsers.PropertyTagData;
+using SkiaSharp;
+using System.Linq;
+
+namespace FModel.Creator.Rarities
+{
+ static class Rarity
+ {
+ public static void GetInGameRarity(BaseIcon icon, EnumProperty e)
+ {
+ PakPackage p = Utils.GetPropertyPakPackage("/Game/Balance/RarityData");
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var d = p.GetExport();
+ if (d != null)
+ {
+ EFortRarity rarity = EFortRarity.Uncommon;
+ switch (e?.Value.String)
+ {
+ case "EFortRarity::Common":
+ rarity = EFortRarity.Common;
+ break;
+ case "EFortRarity::Rare":
+ rarity = EFortRarity.Rare;
+ break;
+ case "EFortRarity::Epic":
+ case "EFortRarity::Quality":
+ rarity = EFortRarity.Epic;
+ break;
+ case "EFortRarity::Legendary":
+ rarity = EFortRarity.Legendary;
+ break;
+ case "EFortRarity::Mythic":
+ rarity = EFortRarity.Mythic;
+ break;
+ case "EFortRarity::Transcendent":
+ rarity = EFortRarity.Transcendent;
+ break;
+ case "EFortRarity::Unattainable":
+ rarity = EFortRarity.Unattainable;
+ break;
+ }
+
+ if (d.Values.ElementAt((int)rarity) is StructProperty s && s.Value is UObject colors)
+ {
+ if (colors.TryGetValue("Color1", out var c1) && c1 is StructProperty s1 && s1.Value is FLinearColor color1 &&
+ colors.TryGetValue("Color2", out var c2) && c2 is StructProperty s2 && s2.Value is FLinearColor color2 &&
+ colors.TryGetValue("Color3", out var c3) && c3 is StructProperty s3 && s3.Value is FLinearColor color3)
+ {
+ icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
+ icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
+ }
+ }
+ }
+ }
+ else GetHardCodedRarity(icon, e);
+ }
+
+ public static void GetHardCodedRarity(BaseIcon icon, EnumProperty e)
+ {
+ switch (e?.Value.String)
+ {
+ case "EFortRarity::Common":
+ icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("6D6D6D"), SKColor.Parse("333333") };
+ icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("9E9E9E"), SKColor.Parse("9E9E9E") };
+ break;
+ case "EFortRarity::Rare":
+ icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("3669BB"), SKColor.Parse("133254") };
+ icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("5180EE"), SKColor.Parse("5180EE") };
+ break;
+ case "EFortRarity::Epic":
+ case "EFortRarity::Quality":
+ icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("8138C2"), SKColor.Parse("35155C") };
+ icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("B251ED"), SKColor.Parse("B251ED") };
+ break;
+ case "EFortRarity::Legendary":
+ icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("C06A38"), SKColor.Parse("5C2814") };
+ icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("EC9650"), SKColor.Parse("EC9650") };
+ break;
+ case "EFortRarity::Mythic":
+ icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("BA9C36"), SKColor.Parse("594415") };
+ icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("EED951"), SKColor.Parse("EED951") };
+ break;
+ case "EFortRarity::Transcendent":
+ icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("D51944"), SKColor.Parse("660522") };
+ icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("FF3F58"), SKColor.Parse("FF3F58") };
+ break;
+ }
+ }
+
+ public static void DrawRarity(SKCanvas c, BaseIcon icon)
+ {
+ // border
+ c.DrawRect(new SKRect(0, 0, icon.Size, icon.Size),
+ new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Shader = SKShader.CreateLinearGradient(
+ new SKPoint(icon.Size / 2, icon.Size),
+ new SKPoint(icon.Size, icon.Size / 4),
+ icon.RarityBorderColor,
+ SKShaderTileMode.Clamp)
+ });
+
+ switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
+ {
+ case EIconDesign.Flat:
+ {
+ if (icon.RarityBackgroundImage != null)
+ c.DrawBitmap(icon.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin),
+ new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
+ else
+ {
+ c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin),
+ new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Color = icon.RarityBackgroundColors[0]
+ });
+
+ var paint = new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Color = icon.RarityBackgroundColors[1].WithAlpha(75)
+ };
+ var pathTop = new SKPath { FillType = SKPathFillType.EvenOdd };
+ pathTop.MoveTo(icon.Margin, icon.Margin);
+ pathTop.LineTo(icon.Margin + (icon.Size / 17 * 10), icon.Margin);
+ pathTop.LineTo(icon.Margin, icon.Margin + (icon.Size / 17));
+ pathTop.Close();
+ c.DrawPath(pathTop, paint);
+
+ var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
+ pathBottom.MoveTo(icon.Margin, icon.Size - icon.Margin);
+ pathBottom.LineTo(icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 2.5f));
+ pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 4.5f));
+ pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin);
+ pathBottom.Close();
+ c.DrawPath(pathBottom, paint);
+ }
+ break;
+ }
+ default:
+ {
+ if (icon.RarityBackgroundImage != null)
+ c.DrawBitmap(icon.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin),
+ new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
+ else
+ c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin),
+ new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Shader = SKShader.CreateRadialGradient(
+ new SKPoint(icon.Size / 2, icon.Size / 2),
+ icon.Size / 5 * 4,
+ icon.RarityBackgroundColors,
+ SKShaderTileMode.Clamp)
+ });
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/FModel/Creator/Rarities/Serie.cs b/FModel/Creator/Rarities/Serie.cs
new file mode 100644
index 00000000..fd037e89
--- /dev/null
+++ b/FModel/Creator/Rarities/Serie.cs
@@ -0,0 +1,40 @@
+using FModel.Creator.Texts;
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.Objects;
+using PakReader.Parsers.PropertyTagData;
+using SkiaSharp;
+
+namespace FModel.Creator.Rarities
+{
+ static class Serie
+ {
+ public static void GetRarity(BaseIcon icon, ObjectProperty o)
+ {
+ PakPackage p = Utils.GetPropertyPakPackage(o.Value.Resource.OuterIndex.Resource.ObjectName.String);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var obj = p.GetExport();
+ if (obj != null)
+ GetRarity(icon, obj);
+ }
+ }
+
+ public static void GetRarity(BaseIcon icon, IUExport export)
+ {
+ if (export.TryGetValue("BackgroundTexture", out var t) && t is SoftObjectProperty sop)
+ icon.RarityBackgroundImage = Utils.GetSoftObjectTexture(sop);
+
+ if (export.TryGetValue("Colors", out var v) && v is StructProperty s && s.Value is UObject colors)
+ {
+ if (colors.TryGetValue("Color1", out var c1) && c1 is StructProperty s1 && s1.Value is FLinearColor color1 &&
+ colors.TryGetValue("Color2", out var c2) && c2 is StructProperty s2 && s2.Value is FLinearColor color2 &&
+ colors.TryGetValue("Color4", out var c4) && c4 is StructProperty s4 && s4.Value is FLinearColor color4)
+ {
+ icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color4.Hex) };
+ icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
+ }
+ }
+ }
+ }
+}
diff --git a/FModel/Creator/Stats/Statistic.cs b/FModel/Creator/Stats/Statistic.cs
new file mode 100644
index 00000000..0730064e
--- /dev/null
+++ b/FModel/Creator/Stats/Statistic.cs
@@ -0,0 +1,10 @@
+using SkiaSharp;
+
+namespace FModel.Creator.Stats
+{
+ public class Statistic
+ {
+ public SKBitmap Icon;
+ public string Description;
+ }
+}
diff --git a/FModel/Creator/Stats/Statistics.cs b/FModel/Creator/Stats/Statistics.cs
new file mode 100644
index 00000000..3b687c35
--- /dev/null
+++ b/FModel/Creator/Stats/Statistics.cs
@@ -0,0 +1,203 @@
+using FModel.Creator.Texts;
+using FModel.Utils;
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.PropertyTagData;
+using SkiaSharp;
+using System;
+using System.Windows;
+
+namespace FModel.Creator.Stats
+{
+ static class Statistics
+ {
+ public static void GetAmmoData(BaseIcon icon, SoftObjectProperty ammoData)
+ {
+ PakPackage p = Utils.GetPropertyPakPackage(ammoData.Value.AssetPathName.String);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var obj = p.GetExport();
+ if (obj != null)
+ {
+ if (obj.TryGetValue("DisplayName", out var v1) && v1 is TextProperty displayName &&
+ obj.TryGetValue("SmallPreviewImage", out var v2) && v2 is SoftObjectProperty smallPreviewImage)
+ {
+ icon.Stats.Add(new Statistic
+ {
+ Icon = Utils.GetSoftObjectTexture(smallPreviewImage),
+ Description = Text.GetTextPropertyBase(displayName).ToUpper()
+ });
+ }
+ }
+ }
+ }
+
+ public static void GetWeaponStats(BaseIcon icon, StructProperty weaponStatHandle)
+ {
+ if (weaponStatHandle.Value is UObject o1 &&
+ o1.TryGetValue("DataTable", out var c1) && c1 is ObjectProperty dataTable &&
+ o1.TryGetValue("RowName", out var c2) && c2 is NameProperty rowName)
+ {
+ PakPackage p = Utils.GetPropertyPakPackage(dataTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var table = p.GetExport();
+ if (table != null)
+ {
+ if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject stats)
+ {
+ if (stats.TryGetValue("ReloadTime", out var s1) && s1 is FloatProperty reloadTime && reloadTime.Value != 0)
+ icon.Stats.Add(new Statistic
+ {
+ Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_ReloadTime_Weapon_Stats.png")).Stream),
+ Description = $"{Localizations.GetLocalization(string.Empty, "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time")} ({Localizations.GetLocalization(string.Empty, "6BA53D764BA5CC13E821D2A807A72365", "seconds")}) : {reloadTime.Value:0.0}".ToUpper()
+ });
+
+ if (stats.TryGetValue("ClipSize", out var s2) && s2 is IntProperty clipSize && clipSize.Value != 0)
+ icon.Stats.Add(new Statistic
+ {
+ Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_ClipSize_Weapon_Stats.png")).Stream),
+ Description = $"{Localizations.GetLocalization(string.Empty, "068239DD4327B36124498C9C5F61C038", "Magazine Size")} : {clipSize.Value}".ToUpper()
+ });
+
+ if (stats.TryGetValue("DmgPB", out var s3) && s3 is FloatProperty dmgPB && dmgPB.Value != 0)
+ icon.Stats.Add(new Statistic
+ {
+ Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_DamagePerBullet_Weapon_Stats.png")).Stream),
+ Description = $"{Localizations.GetLocalization(string.Empty, "BF7E3CF34A9ACFF52E95EAAD4F09F133", "Damage to Player")} : {dmgPB.Value}".ToUpper()
+ });
+ }
+ }
+ }
+ }
+ }
+
+ public static void GetHeroStats(BaseIcon icon, ObjectProperty heroGameplayDefinition)
+ {
+ PakPackage p = Utils.GetPropertyPakPackage(heroGameplayDefinition.Value.Resource.OuterIndex.Resource.ObjectName.String);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var obj = p.GetExport();
+ if (obj != null)
+ {
+ if (obj.TryGetValue("HeroPerk", out var v1) && v1 is StructProperty s1 && s1.Value is UObject heroPerk)
+ {
+ GetAbilityKit(icon, heroPerk);
+ }
+
+ if (obj.TryGetValue("TierAbilityKits", out var v2) && v2 is ArrayProperty tierAbilityKits)
+ {
+ foreach (StructProperty abilityKit in tierAbilityKits.Value)
+ {
+ if (abilityKit.Value is UObject kit)
+ {
+ GetAbilityKit(icon, kit);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void GetAbilityKit(BaseIcon icon, UObject parent)
+ {
+ if (parent.TryGetValue("GrantedAbilityKit", out var v) && v is SoftObjectProperty grantedAbilityKit)
+ {
+ PakPackage k = Utils.GetPropertyPakPackage(grantedAbilityKit.Value.AssetPathName.String);
+ if (k.HasExport() && !k.Equals(default))
+ {
+ var kit = k.GetExport();
+ if (kit != null &&
+ kit.GetExport("DisplayName") is TextProperty displayName &&
+ kit.GetExport("IconBrush") is StructProperty brush && brush.Value is UObject iconBrush &&
+ iconBrush.TryGetValue("ResourceObject", out var s) && s is ObjectProperty resourceObject)
+ {
+ icon.Stats.Add(new Statistic
+ {
+ Icon = Utils.GetObjectTexture(resourceObject),
+ Description = Text.GetTextPropertyBase(displayName).ToUpper()
+ });
+ }
+ }
+ }
+ }
+
+ public static void DrawStats(SKCanvas c, BaseIcon icon)
+ {
+ int size = 48;
+ int iconSize = 40;
+ int textSize = 25;
+ int y = icon.Size;
+ foreach (Statistic stat in icon.Stats)
+ {
+ c.DrawRect(new SKRect(0, y, icon.Size, y + size),
+ new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Shader = SKShader.CreateLinearGradient(
+ new SKPoint(icon.Size / 2, icon.Size),
+ new SKPoint(icon.Size, icon.Size / 4),
+ icon.RarityBorderColor,
+ SKShaderTileMode.Clamp)
+ });
+
+
+ if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign == EIconDesign.Flat)
+ {
+ c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin),
+ new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Color = icon.RarityBackgroundColors[0]
+ });
+ }
+ else
+ {
+ c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin),
+ new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Shader = SKShader.CreateRadialGradient(
+ new SKPoint(icon.Size / 2, icon.Size / 2),
+ icon.Size / 5 * 4,
+ icon.RarityBackgroundColors,
+ SKShaderTileMode.Clamp)
+ });
+ }
+
+ c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin),
+ new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Color = new SKColor(0, 0, 50, 75)
+ });
+
+ c.DrawBitmap(stat.Icon.Resize(iconSize, iconSize), new SKPoint(icon.Margin * (int)2.5, y + 4), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
+
+ var statPaint = new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Typeface = Text.TypeFaces.DisplayNameTypeface,
+ TextSize = textSize,
+ Color = SKColors.White,
+ TextAlign = SKTextAlign.Center,
+ };
+
+ // resize if too long
+ while (statPaint.MeasureText(stat.Description) > (icon.Size - (icon.Margin * 2) - iconSize))
+ {
+ statPaint.TextSize = textSize -= 2;
+ }
+
+ c.DrawText(stat.Description, icon.Size / 2, y + 32, statPaint);
+
+ y += size;
+ }
+ }
+ }
+}
diff --git a/FModel/Creator/Texts/ETextSide.cs b/FModel/Creator/Texts/ETextSide.cs
new file mode 100644
index 00000000..2d4d9aba
--- /dev/null
+++ b/FModel/Creator/Texts/ETextSide.cs
@@ -0,0 +1,9 @@
+namespace FModel.Creator.Texts
+{
+ public enum ETextSide
+ {
+ Center,
+ Right,
+ Left
+ }
+}
diff --git a/FModel/Creator/Texts/GameplayTag.cs b/FModel/Creator/Texts/GameplayTag.cs
new file mode 100644
index 00000000..24dbd4ed
--- /dev/null
+++ b/FModel/Creator/Texts/GameplayTag.cs
@@ -0,0 +1,72 @@
+using FModel.Creator.Icons;
+using FModel.Utils;
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.Objects;
+using PakReader.Parsers.PropertyTagData;
+
+namespace FModel.Creator.Texts
+{
+ static class GameplayTag
+ {
+ public static void GetGameplayTags(BaseIcon icon, StructProperty s, string exportType)
+ {
+ if (s.Value is FGameplayTagContainer g)
+ {
+ if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
+ icon.CosmeticSource = source.String.Substring("Cosmetics.Source.".Length);
+ else if(g.GameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
+ icon.CosmeticSource = action.String.Substring("Athena.ItemAction.".Length);
+
+ if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
+ icon.Description += GetCosmeticSet(set.String);
+ if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
+ icon.Description += GetCosmeticSeason(season.String);
+
+ UserFacingFlag.GetUserFacingFlags(
+ g.GameplayTags.GetAllGameplayTag("Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."),
+ icon, exportType);
+ }
+ }
+
+ private static string GetCosmeticSet(string setName)
+ {
+ PakPackage p = Utils.GetPropertyPakPackage("/Game/Athena/Items/Cosmetics/Metadata/CosmeticSets");
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var d = p.GetExport();
+ if (d != null && d.TryGetValue(setName, out var obj) && obj is UObject o)
+ {
+ if (o.TryGetValue("DisplayName", out var displayName) && displayName is TextProperty t)
+ {
+ (string n, string k, string s) = Text.GetTextPropertyBases(t);
+ string set = Localizations.GetLocalization(n, k, s);
+ string format = Localizations.GetLocalization("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
+ return string.Format(format, set);
+ }
+ }
+ }
+ return string.Empty;
+ }
+
+ private static string GetCosmeticSeason(string seasonNumber)
+ {
+ string s = seasonNumber.Substring("Cosmetics.Filter.Season.".Length);
+ int number = int.Parse(s);
+ if (number == 10)
+ s = "X";
+
+ string season = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
+ string introduced = Localizations.GetLocalization("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in {0}>.")
+ .Replace("", string.Empty).Replace(">", string.Empty);
+ if (number > 10)
+ {
+ string chapter = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
+ string chapterFormat = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
+ string d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..]));
+ return string.Format(introduced, d);
+ }
+ else return string.Format(introduced, string.Format(season, s));
+ }
+ }
+}
diff --git a/FModel/Creator/Texts/Helper.cs b/FModel/Creator/Texts/Helper.cs
new file mode 100644
index 00000000..933dc50a
--- /dev/null
+++ b/FModel/Creator/Texts/Helper.cs
@@ -0,0 +1,110 @@
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace FModel.Creator.Texts
+{
+ static class Helper
+ {
+ public class Line
+ {
+ public string Value { get; set; }
+ public float Width { get; set; }
+ }
+
+ public static void DrawCenteredMultilineText(SKCanvas canvas, string text, int maxLineCount, BaseIcon icon, ETextSide side, SKRect area, SKPaint paint)
+ => DrawCenteredMultilineText(canvas, text, maxLineCount, icon.Size, icon.Margin, side, area, paint);
+ public static void DrawCenteredMultilineText(SKCanvas canvas, string text, int maxLineCount, int size, int margin, ETextSide side, SKRect area, SKPaint paint)
+ {
+ float lineHeight = paint.TextSize * 1.2f;
+ Line[] lines = SplitLines(text, paint, area.Width - margin);
+
+ if (lines == null)
+ return;
+ if (lines.Length <= maxLineCount)
+ maxLineCount = lines.Length;
+
+ float height = maxLineCount * lineHeight;
+ float y = area.MidY - height / 2;
+ for (int i = 0; i < maxLineCount; i++)
+ {
+ y += lineHeight;
+ float x = side switch
+ {
+ ETextSide.Center => area.MidX - lines[i].Width / 2,
+ ETextSide.Right => size - margin - lines[i].Width,
+ ETextSide.Left => margin,
+ _ => area.MidX - lines[i].Width / 2
+ };
+ canvas.DrawText(lines[i].Value.TrimEnd(), x, y, paint);
+ }
+ }
+
+ public static void DrawMultilineText(SKCanvas canvas, string text, int size, int margin, ETextSide side, SKRect area, SKPaint paint, out int yPos)
+ {
+ float lineHeight = paint.TextSize * 1.2f;
+ Line[] lines = SplitLines(text, paint, area.Width - margin);
+ if (lines == null)
+ {
+ yPos = (int)area.Top;
+ return;
+ }
+
+ float y = area.Top;
+ for (int i = 0; i < lines.Length; i++)
+ {
+ float x = side switch
+ {
+ ETextSide.Center => area.MidX - lines[i].Width / 2,
+ ETextSide.Right => size - margin - lines[i].Width,
+ ETextSide.Left => margin,
+ _ => area.MidX - lines[i].Width / 2
+ };
+ canvas.DrawText(lines[i].Value.TrimEnd(), x, y, paint);
+ y += lineHeight;
+ }
+ yPos = (int)area.Top + ((int)lineHeight * lines.Length);
+ }
+
+ public static Line[] SplitLines(string text, SKPaint paint, float maxWidth)
+ {
+ if (string.IsNullOrEmpty(text))
+ return null;
+
+ float spaceWidth = paint.MeasureText(" ");
+ string[] lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries);
+
+ List ret = new List(lines.Length);
+ for (int i = 0; i < lines.Length; i++)
+ {
+ if (string.IsNullOrWhiteSpace(lines[i]))
+ continue;
+
+ float width = 0;
+ var lineResult = new StringBuilder();
+ string[] words = lines[i].Split(' ', StringSplitOptions.None);
+ foreach (var word in words)
+ {
+ float wordWidth = paint.MeasureText(word);
+ float wordWithSpaceWidth = wordWidth + spaceWidth;
+ string wordWithSpace = word + " ";
+
+ if (width + wordWidth > maxWidth)
+ {
+ ret.Add(new Line { Value = lineResult.ToString(), Width = width });
+ lineResult = new StringBuilder(wordWithSpace);
+ width = wordWithSpaceWidth;
+ }
+ else
+ {
+ lineResult.Append(wordWithSpace);
+ width += wordWithSpaceWidth;
+ }
+ }
+ ret.Add(new Line { Value = lineResult.ToString(), Width = width });
+ }
+ return ret.ToArray();
+ }
+ }
+}
diff --git a/FModel/Creator/Texts/Text.cs b/FModel/Creator/Texts/Text.cs
new file mode 100644
index 00000000..b2cb71b4
--- /dev/null
+++ b/FModel/Creator/Texts/Text.cs
@@ -0,0 +1,217 @@
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.Objects;
+using PakReader.Parsers.PropertyTagData;
+using SkiaSharp;
+using System.Collections.Generic;
+
+namespace FModel.Creator.Texts
+{
+ static class Text
+ {
+ public static Typefaces TypeFaces = new Typefaces();
+ private const int _STARTER_TEXT_POSITION = 380;
+ private static int _BOTTOM_TEXT_SIZE = 15;
+ private static int _NAME_TEXT_SIZE = 45;
+
+ public static string GetTextPropertyBase(TextProperty t)
+ {
+ if (t.Value is FText text)
+ if (text.Text is FTextHistory.Base b)
+ return b.SourceString.Replace("", string.Empty).Replace(">", string.Empty);
+ else if (text.Text is FTextHistory.StringTableEntry s)
+ {
+ PakPackage p = Utils.GetPropertyPakPackage(s.TableId.String);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var table = p.GetExport();
+ if (table != null)
+ {
+ if (table.TryGetValue("StringTable", out var v1) && v1 is FStringTable stringTable &&
+ stringTable.KeysToMetadata.TryGetValue(stringTable.TableNamespace, out var v2) && v2 is Dictionary dico &&
+ dico.TryGetValue(s.Key, out var ret))
+ {
+ return ret;
+ }
+ }
+ }
+ }
+
+ return string.Empty;
+ }
+ public static string GetTextPropertyBase(ArrayProperty a)
+ {
+ if (a.Value.Length > 0 && a.Value[0] is TextProperty t)
+ return GetTextPropertyBase(t);
+ return string.Empty;
+ }
+
+ public static (string, string, string) GetTextPropertyBases(TextProperty t)
+ {
+ if (t.Value is FText text && text.Text is FTextHistory.Base b)
+ return (b.Namespace, b.Key, b.SourceString);
+ return (string.Empty, string.Empty, string.Empty);
+ }
+
+ public static string GetMaxStackSize(StructProperty maxStackSize)
+ {
+ if (maxStackSize.Value is UObject o1)
+ {
+ if (o1.TryGetValue("Value", out var c) && c is FloatProperty value && value.Value != -1) // old way
+ return $"MaxStackSize : {value.Value}";
+ else if (
+ o1.TryGetValue("Curve", out var c1) && c1 is StructProperty curve && curve.Value is UObject o2 &&
+ o2.TryGetValue("CurveTable", out var c2) && c2 is ObjectProperty curveTable &&
+ o2.TryGetValue("RowName", out var c3) && c3 is NameProperty rowName) // new way
+ {
+ PakPackage p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var table = p.GetExport();
+ if (table != null)
+ {
+ if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject maxStackAmount &&
+ maxStackAmount.TryGetValue("Keys", out var v2) && v2 is ArrayProperty keys &&
+ keys.Value.Length > 0 && (keys.Value[0] as StructProperty).Value is FSimpleCurveKey amount &&
+ amount.KeyValue != -1)
+ {
+ return $"MaxStackSize : {amount.KeyValue}";
+ }
+ }
+ }
+ }
+ }
+ return string.Empty;
+ }
+
+ public static void DrawBackground(SKCanvas c, BaseIcon icon)
+ {
+ switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
+ {
+ case EIconDesign.Flat:
+ {
+ var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
+ pathBottom.MoveTo(icon.Margin, icon.Size - icon.Margin);
+ pathBottom.LineTo(icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 2.5f));
+ pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 4.5f));
+ pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin);
+ pathBottom.Close();
+ c.DrawPath(pathBottom, new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Color = new SKColor(0, 0, 50, 75),
+ });
+ break;
+ }
+ default:
+ {
+ c.DrawRect(
+ new SKRect(icon.Margin, _STARTER_TEXT_POSITION, icon.Size - icon.Margin, icon.Size - icon.Margin),
+ new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Color = new SKColor(0, 0, 50, 75),
+ });
+ break;
+ }
+ }
+ }
+
+ public static void DrawDisplayName(SKCanvas c, BaseIcon icon)
+ {
+ _NAME_TEXT_SIZE = 45;
+ string text = icon.DisplayName;
+ SKTextAlign side = SKTextAlign.Center;
+ int x = icon.Size / 2;
+ int y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
+ switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
+ {
+ case EIconDesign.Mini:
+ {
+ _NAME_TEXT_SIZE = 47;
+ text = text.ToUpper();
+ break;
+ }
+ case EIconDesign.Flat:
+ {
+ _NAME_TEXT_SIZE = 47;
+ side = SKTextAlign.Right;
+ x = icon.Size - icon.Margin * 2;
+ break;
+ }
+ }
+
+ SKPaint namePaint = new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Typeface = TypeFaces.DisplayNameTypeface,
+ TextSize = _NAME_TEXT_SIZE,
+ Color = SKColors.White,
+ TextAlign = side,
+ };
+
+ // resize if too long
+ while (namePaint.MeasureText(text) > (icon.Size - (icon.Margin * 2)))
+ {
+ namePaint.TextSize = _NAME_TEXT_SIZE -= 2;
+ }
+
+ c.DrawText(text, x, y, namePaint);
+ }
+
+ public static void DrawDescription(SKCanvas c, BaseIcon icon)
+ {
+ int maxLine = 4;
+ _BOTTOM_TEXT_SIZE = 15;
+ string text = icon.Description;
+ ETextSide side = ETextSide.Center;
+ switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
+ {
+ case EIconDesign.Mini:
+ {
+ maxLine = 5;
+ _BOTTOM_TEXT_SIZE = icon.Margin;
+ text = text.ToUpper();
+ break;
+ }
+ case EIconDesign.Flat:
+ {
+ side = ETextSide.Right;
+ break;
+ }
+ }
+
+ SKPaint descriptionPaint = new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Typeface = TypeFaces.DescriptionTypeface,
+ TextSize = 13,
+ Color = SKColors.White,
+ };
+
+ // wrap if too long
+ Helper.DrawCenteredMultilineText(c, text, maxLine, icon, side,
+ new SKRect(icon.Margin, _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, icon.Size - icon.Margin, icon.Size - _BOTTOM_TEXT_SIZE),
+ descriptionPaint);
+ }
+
+ public static void DrawToBottom(SKCanvas c, BaseIcon icon, ETextSide side, string text)
+ {
+ SKPaint shortDescriptionPaint = new SKPaint
+ {
+ IsAntialias = true,
+ FilterQuality = SKFilterQuality.High,
+ Typeface = side == ETextSide.Left ? TypeFaces.DisplayNameTypeface : TypeFaces.DefaultTypeface,
+ TextSize = 15,
+ Color = SKColors.White,
+ TextAlign = side == ETextSide.Left ? SKTextAlign.Left : SKTextAlign.Right,
+ };
+
+ c.DrawText(text, side == ETextSide.Left ? icon.Margin * 2.5f : icon.Size - (icon.Margin * 2.5f), icon.Size - (icon.Margin * 2.5f), shortDescriptionPaint);
+ }
+ }
+}
diff --git a/FModel/Creator/Texts/Typefaces.cs b/FModel/Creator/Texts/Typefaces.cs
new file mode 100644
index 00000000..05d7444f
--- /dev/null
+++ b/FModel/Creator/Texts/Typefaces.cs
@@ -0,0 +1,105 @@
+using FModel.Utils;
+using FModel.ViewModels.DataGrid;
+using SkiaSharp;
+using System;
+using System.Windows;
+
+namespace FModel.Creator.Texts
+{
+ public class Typefaces
+ {
+#pragma warning disable IDE0051
+ private const string _BASE_PATH = "/Game/UI/Foundation/Fonts/";
+ private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
+ private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black";
+ private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new Uri("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf"); // other languages fortnite unofficial
+ private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black";
+ private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite but it's too big so i use it for russian only
+ private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
+ private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset";
+ private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite
+ private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji";
+ private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
+ private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
+ private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic";
+ private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular";
+ private const string _NOTO_SANS_ITALIC = "NotoSans-Italic";
+ private const string _NOTO_SANS_REGULAR = "NotoSans-Regular";
+ private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite
+ private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold";
+ private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
+ private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold";
+ private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
+ private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // traditional chinese fortnite
+ private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
+ private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // simplified chinese fortnite
+ private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular";
+ private const string _BURBANK_SMALL_BLACK = "burbanksmall-black";
+ private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold";
+#pragma warning restore IDE0051
+
+ public SKTypeface DefaultTypeface; // used as default font for all untranslated strings (item source, ...)
+ public SKTypeface BundleDefaultTypeface; // used for the last folder string
+ public SKTypeface DisplayNameTypeface;
+ public SKTypeface DescriptionTypeface;
+ public SKTypeface BundleDisplayNameTypeface;
+
+ public Typefaces()
+ {
+ DefaultTypeface = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD).Stream);
+
+ ArraySegment[] t = Utils.GetPropertyArraySegmentByte(_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK);
+ if (t != null && t.Length == 3)
+ BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
+ else BundleDefaultTypeface = DefaultTypeface;
+
+ string namePath = _BASE_PATH + (
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_REGULAR_BOLD :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_BLACK :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_SC_BLACK :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_TC_BLACK :
+ string.Empty);
+ if (!namePath.Equals(_BASE_PATH))
+ {
+ t = Utils.GetPropertyArraySegmentByte(namePath);
+ if (t != null && t.Length == 3)
+ DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
+ }
+ else DisplayNameTypeface = DefaultTypeface;
+
+ string descriptionPath = _BASE_PATH + (
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_BOLD :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_REGULAR :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_SC_REGULAR :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_TC_REGULAR :
+ _NOTO_SANS_REGULAR);
+ t = Utils.GetPropertyArraySegmentByte(descriptionPath);
+ if (t != null && t.Length == 3)
+ DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
+ else DescriptionTypeface = DefaultTypeface;
+
+ string bundleNamePath = _BASE_PATH + (
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_REGULAR_BOLD :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_BLACK :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_SC_BLACK :
+ Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_TC_BLACK :
+ string.Empty);
+ if (!bundleNamePath.Equals(_BASE_PATH))
+ {
+ t = Utils.GetPropertyArraySegmentByte(bundleNamePath);
+ if (t != null && t.Length == 3)
+ BundleDisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
+ }
+ else BundleDisplayNameTypeface = BundleDefaultTypeface;
+ }
+
+ public bool NeedReload(bool forceReload) => forceReload ?
+ DataGridVm.dataGridViewModel.Count > 0 : //reload only if at least one pak is loaded
+ DataGridVm.dataGridViewModel.Count > 0 && (BundleDefaultTypeface == DefaultTypeface && DisplayNameTypeface == DefaultTypeface && DescriptionTypeface == DefaultTypeface && BundleDisplayNameTypeface == BundleDefaultTypeface);
+ }
+}
diff --git a/FModel/Creator/Utils.cs b/FModel/Creator/Utils.cs
new file mode 100644
index 00000000..5b18e873
--- /dev/null
+++ b/FModel/Creator/Utils.cs
@@ -0,0 +1,78 @@
+using FModel.Utils;
+using PakReader.Pak;
+using PakReader.Parsers.Class;
+using PakReader.Parsers.PropertyTagData;
+using SkiaSharp;
+using System;
+
+namespace FModel.Creator
+{
+ static class Utils
+ {
+ public static string GetFullPath(string partialPath)
+ {
+ foreach (var fileReader in Globals.CachedPakFiles.Values)
+ if (fileReader.TryGetPartialKey(partialPath, out var fullPath))
+ {
+ return fullPath;
+ }
+ return string.Empty;
+ }
+
+ public static PakPackage GetPropertyPakPackage(string value)
+ {
+ string path = Strings.FixPath(value);
+ foreach (var fileReader in Globals.CachedPakFiles.Values)
+ if (fileReader.TryGetValue(path, out var entry))
+ {
+ // kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯
+ string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf(".")).Length);
+ return Assets.GetPakPackage(entry, mount);
+ }
+ return default;
+ }
+
+ public static ArraySegment[] GetPropertyArraySegmentByte(string value)
+ {
+ string path = Strings.FixPath(value);
+ foreach (var fileReader in Globals.CachedPakFiles.Values)
+ if (fileReader.TryGetValue(path, out var entry))
+ {
+ // kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯
+ string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf(".")).Length);
+ return Assets.GetArraySegmentByte(entry, mount);
+ }
+ return default;
+ }
+
+ public static SKBitmap NewZeroedBitmap(int width, int height) => new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels);
+ public static SKBitmap Resize(this SKBitmap me, int width, int height)
+ {
+ var bmp = NewZeroedBitmap(width, height);
+ using var pixmap = bmp.PeekPixels();
+ me.ScalePixels(pixmap, SKFilterQuality.Medium);
+ return bmp;
+ }
+
+ public static SKBitmap GetObjectTexture(ObjectProperty o) => GetTexture(o.Value.Resource.OuterIndex.Resource.ObjectName.String);
+ public static SKBitmap GetSoftObjectTexture(SoftObjectProperty s) => GetTexture(s.Value.AssetPathName.String);
+ public static SKBitmap GetTexture(string s)
+ {
+ PakPackage p = GetPropertyPakPackage(s);
+ if (p.HasExport() && !p.Equals(default))
+ {
+ var i = p.GetExport();
+ if (i != null)
+ return SKBitmap.Decode(i.Image.Encode());
+
+ var u = p.GetExport();
+ if (u != null)
+ if (u.TryGetValue("TextureParameterValues", out var v) && v is ArrayProperty a)
+ if (a.Value.Length > 0 && a.Value[0] is StructProperty str && str.Value is UObject o)
+ if (o.TryGetValue("ParameterValue", out var obj) && obj is ObjectProperty parameterValue)
+ return GetObjectTexture(parameterValue);
+ }
+ return null;
+ }
+ }
+}
diff --git a/FModel/Discord/DiscordIntegration.cs b/FModel/Discord/DiscordIntegration.cs
new file mode 100644
index 00000000..3e8ed8ab
--- /dev/null
+++ b/FModel/Discord/DiscordIntegration.cs
@@ -0,0 +1,66 @@
+using DiscordRPC;
+using DiscordRPC.Logging;
+using FModel.Logger;
+using System;
+using System.Reflection;
+
+namespace FModel.Discord
+{
+ static class DiscordIntegration
+ {
+ private const string _DISCORD_APP_ID = "684489366189768767";
+
+ private static readonly Timestamps _baseTimestamp = new Timestamps { Start = DateTime.UtcNow };
+ private static readonly Assets _assets = new Assets
+ {
+ LargeImageKey = "official_logo",
+ SmallImageKey = "verified",
+ SmallImageText = $"v{Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, 5)}"
+ };
+ private static readonly DiscordRpcClient _client = new DiscordRpcClient(_DISCORD_APP_ID);
+ private static RichPresence _presence;
+
+ public static void Dispose() => _client.Dispose();
+ private static void Initialize()
+ {
+ _client.Logger = new ConsoleLogger() { Level = LogLevel.Warning };
+ _client.OnReady += (sender, e) =>
+ {
+ DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Discord RPC]", $"Ready for {e.User.Username}#{e.User.Discriminator} ({e.User.ID})");
+ };
+ _client.Initialize();
+ }
+
+ public static void StartClient()
+ {
+ _client.SetPresence(new RichPresence
+ {
+ Assets = _assets,
+ Timestamps = _baseTimestamp,
+ State = Properties.Resources.Idling
+ });
+ Initialize();
+ SaveCurrentPresence();
+ }
+
+ public static void Update(string detail = null, string state = null)
+ {
+ _client.SetPresence(new RichPresence
+ {
+ Assets = _assets,
+ Timestamps = _baseTimestamp,
+ Details = string.IsNullOrEmpty(detail) ? _presence.Details : detail,
+ State = string.IsNullOrEmpty(state) ? _presence.State : state
+ });
+ _client.Invoke();
+ }
+
+ public static void Restore()
+ {
+ _client.SetPresence(_presence);
+ _client.Invoke();
+ }
+
+ public static void SaveCurrentPresence() => _presence = _client.CurrentPresence;
+ }
+}
diff --git a/FModel/Enums.cs b/FModel/Enums.cs
new file mode 100644
index 00000000..2e924931
--- /dev/null
+++ b/FModel/Enums.cs
@@ -0,0 +1,60 @@
+namespace FModel
+{
+ public enum EGame
+ {
+ Unknown,
+ Fortnite,
+ Valorant
+ }
+
+ public enum EFModel
+ {
+ Debug,
+ Release,
+ Unknown
+ }
+
+ public enum EPakLoader
+ {
+ Single,
+ All,
+ New,
+ Modified,
+ NewModified
+ }
+
+ public enum ECopy
+ {
+ Path,
+ PathNoExt,
+ File,
+ FileNoExt
+ }
+
+ public enum ELanguage : long
+ {
+ English,
+ French,
+ German,
+ Italian,
+ Spanish,
+ SpanishLatin,
+ Arabic,
+ Japanese,
+ Korean,
+ Polish,
+ PortugueseBrazil,
+ Russian,
+ Turkish,
+ Chinese,
+ TraditionalChinese
+ }
+
+ public enum EIconDesign : long
+ {
+ Default,
+ NoText,
+ Mini,
+ Flat
+ }
+}
diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj
index 00de34d5..90127e46 100644
--- a/FModel/FModel.csproj
+++ b/FModel/FModel.csproj
@@ -1,634 +1,236 @@
-
-
-
+
+
- Debug
- AnyCPU
- {8AAB27BD-18D7-4164-8BBC-AB534D55D30F}
WinExe
- FModel
- FModel
- v4.7.2
- 512
- {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- 4
- true
- true
-
-
-
- false
- publish\
- true
- Disk
- false
- Foreground
- 7
- Days
- false
- false
- true
- 0
- 1.0.0.%2a
- false
- true
+ netcoreapp3.1
+ true
+ FModel.ico
+ FModel.App
+ Asval
+
+ 3.1.0.0
+ 3.1.0.0
+ FModel.ico
+
+ https://github.com/iAmAsval/FModel
+
+ 3.1.0
+ x64
-
+
+
x64
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
true
- 7.1
+ 1701;1702;NU1701
-
+
+
x64
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
true
- false
- 7.1
-
-
- Logo.ico
-
-
- FModel.Program
+ 1701;1702;NU1701
+
-
- ..\packages\Autoupdater.NET.Official.1.5.8\lib\net40\AutoUpdater.NET.dll
-
-
- ..\packages\HtmlAgilityPack.1.11.17\lib\Net45\HtmlAgilityPack.dll
-
-
- ..\packages\AvalonEdit.6.0.0\lib\net45\ICSharpCode.AvalonEdit.dll
-
-
- ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll
-
-
- ..\packages\Ookii.Dialogs.Wpf.1.1.0\lib\net45\Ookii.Dialogs.Wpf.dll
-
-
- ..\packages\RestSharp.106.6.10\lib\net452\RestSharp.dll
-
-
- ..\packages\SkiaSharp.1.68.1\lib\net45\SkiaSharp.dll
-
-
-
- ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll
-
-
-
-
-
- ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll
-
-
-
- ..\packages\System.Numerics.Vectors.4.6.0-preview5.19224.8\lib\net46\System.Numerics.Vectors.dll
-
-
- ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll
-
-
-
-
-
-
-
-
-
-
-
- 4.0
-
-
-
-
-
- ..\packages\WriteableBitmapEx.1.6.3\lib\net40\WriteableBitmapEx.Wpf.dll
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+
+
+
-
- MSBuild:Compile
- Designer
-
-
-
- AESManager.xaml
-
-
-
-
-
-
- ColorPickerControl.xaml
-
-
-
- ColorPickerSwatch.xaml
-
-
- ColorPickerWindow.xaml
-
-
- ColorPickRow.xaml
-
-
- SliderRow.xaml
-
-
- FModel_About.xaml
-
-
- FModel_CustomMB.xaml
-
-
- FModel_ImagesMerger.xaml
-
-
- FModel_SearchFiles.xaml
-
-
- FModel_Settings.xaml
-
-
- FModel_UpdateMode.xaml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- FindReplaceWindow.xaml
-
-
- FindWindow.xaml
-
-
- GiveByteWindow.xaml
-
-
- HexBox.xaml
-
-
-
- HexEditor.xaml
-
-
- HexViewer.xaml
-
-
- ReplaceByteWindow.xaml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- FindReplaceDialog.xaml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
True
+ True
Resources.resx
-
-
-
- MSBuild:Compile
- Designer
-
-
- App.xaml
- Code
-
-
- FModel_Main.xaml
- Code
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- MSBuild:Compile
- Designer
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- Designer
- MSBuild:Compile
-
-
- MSBuild:Compile
- Designer
-
-
- Designer
- MSBuild:Compile
-
-
- MSBuild:Compile
- Designer
-
-
- Designer
- MSBuild:Compile
-
-
- MSBuild:Compile
- Designer
-
-
-
-
- Code
-
-
+
+ True
True
Settings.settings
- True
-
+
+
+
+
PublicResXFileCodeGenerator
Resources.Designer.cs
-
-
-
-
-
-
- SettingsSingleFileGenerator
+
+
+
+
+ PublicSettingsSingleFileGenerator
Settings.Designer.cs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- False
- Microsoft .NET Framework 4.7.2 %28x86 et x64%29
- true
-
-
- False
- .NET Framework 3.5 SP1
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- %(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)
-
-
-
-
-
-
- Ce projet fait référence à des packages NuGet qui sont manquants sur cet ordinateur. Utilisez l'option de restauration des packages NuGet pour les télécharger. Pour plus d'informations, consultez http://go.microsoft.com/fwlink/?LinkID=322105. Le fichier manquant est : {0}.
-
-
-
\ No newline at end of file
diff --git a/FModel/FModel.ico b/FModel/FModel.ico
new file mode 100644
index 00000000..20fb9834
Binary files /dev/null and b/FModel/FModel.ico differ
diff --git a/FModel/FModel_Main.xaml b/FModel/FModel_Main.xaml
deleted file mode 100644
index 33ae36c4..00000000
--- a/FModel/FModel_Main.xaml
+++ /dev/null
@@ -1,196 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/FModel/FModel_Main.xaml.cs b/FModel/FModel_Main.xaml.cs
deleted file mode 100644
index dd3963ff..00000000
--- a/FModel/FModel_Main.xaml.cs
+++ /dev/null
@@ -1,385 +0,0 @@
-using AutoUpdaterDotNET;
-using FModel.Forms;
-using FModel.Forms.HexViewer;
-using FModel.Methods;
-using FModel.Methods.AESManager;
-using FModel.Methods.Assets;
-using FModel.Methods.BackupsManager;
-using FModel.Methods.PAKs;
-using FModel.Methods.TreeViewModel;
-using FModel.Methods.Utilities;
-using System.Reflection;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Media;
-
-namespace FModel
-{
- ///
- /// Logique d'interaction pour MainWindow.xaml
- ///
- public partial class MainWindow : Window
- {
- public SortedTreeViewWindowViewModel ViewModel { get { return DataContext as SortedTreeViewWindowViewModel; } set { DataContext = value; } }
-
- public MainWindow()
- {
- InitializeComponent();
- this.SetValue(TextOptions.TextFormattingModeProperty, TextFormattingMode.Display);
- FWindow.FMain = this;
- }
-
- private async void Window_Loaded(object sender, RoutedEventArgs e)
- {
- FModelVersionLabel.Text += Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, 5);
-
- DebugHelper.WriteLine("AutoUpdater: Checking for updates");
- AutoUpdater.CheckForUpdateEvent += UIHelper.AutoUpdaterOnCheckForUpdateEvent;
- AutoUpdater.Start("https://cdn.asval.tk/d/FModel/FModel.xml");
-
- DebugHelper.WriteUserSettings();
-
- await Task.Run(() =>
- {
- FoldersUtility.CheckWatermark();
- RegisterFromPath.CheckFortniteVersion();
- RegisterFromPath.FilterPAKs();
- DynamicKeysChecker.SetDynamicKeys();
- RegisterDownloadedBackups.LoadBackupFiles();
- }).ContinueWith(TheTask =>
- {
- TasksUtility.TaskCompleted(TheTask.Exception);
- Dispatcher.InvokeAsync(() => AvalonEdit.SetAEConfig());
- Program.StartTimer.Stop();
- DebugHelper.WriteLine("Startup time: {0} ms", Program.StartTimer.ElapsedMilliseconds);
- });
- }
-
- #region BUTTON EVENTS
- private void Button_AESManager_Click(object sender, RoutedEventArgs e)
- {
- DebugHelper.WriteLine("FWindow: AES Manager");
- if (!FormsUtility.IsWindowOpen("AES Manager"))
- {
- new AESManager().Show();
- }
- else { FormsUtility.GetOpenedWindow("AES Manager").Focus(); }
- }
- private void Button_OpenImage_Click(object sender, RoutedEventArgs e)
- {
- if (ImageBox_Main.Source != null)
- {
- DebugHelper.WriteLine("FWindow: Opening image of " + FWindow.FCurrentAsset);
- if (!FormsUtility.IsWindowOpen(FWindow.FCurrentAsset))
- {
- Window win = new Window();
- win.Title = FWindow.FCurrentAsset;
- win.SetValue(TextOptions.TextFormattingModeProperty, TextFormattingMode.Display);
- win.WindowStartupLocation = WindowStartupLocation.CenterScreen;
- win.Width = ImageBox_Main.Source.Width;
- win.Height = ImageBox_Main.Source.Height;
- if (ImageBox_Main.Source.Height > 1000)
- {
- win.WindowState = WindowState.Maximized;
- }
-
- DockPanel dockPanel = new DockPanel
- {
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center,
- };
-
- Image img = new Image();
- img.UseLayoutRounding = true;
- img.Source = ImageBox_Main.Source;
- dockPanel.Children.Add(img);
-
- win.Content = dockPanel;
- win.Show();
- }
- else { FormsUtility.GetOpenedWindow(FWindow.FCurrentAsset).Focus(); }
- }
- }
- private void Button_Stop_Click(object sender, RoutedEventArgs e)
- {
- if (TasksUtility.CancellableTaskTokenSource != null)
- {
- DebugHelper.WriteLine("Thread canceled by user");
- TasksUtility.CancellableTaskTokenSource.Cancel();
- if (TasksUtility.CancellableTaskTokenSource.IsCancellationRequested)
- {
- new UpdateMyProcessEvents("Canceled!", "Yikes").Update();
- }
- else { new UpdateMyProcessEvents("This is odd!\tCanceled but not requested. You should never see this tbh", "Yikes").Update(); }
- }
- }
- private async void Button_Extract_Click(object sender, RoutedEventArgs e)
- {
- if (ListBox_Main.SelectedIndex >= 0)
- {
- await AssetsLoader.LoadSelectedAsset();
- }
- }
- #endregion
-
- #region MENU ITEM EVENTS
- public async void MI_Pak_Click(object sender, RoutedEventArgs e)
- {
- FWindow.FCurrentPAK = (sender as MenuItem).Header.ToString();
- await PAKsLoader.LoadOnePAK();
- }
- private async void MI_LoadAllPAKs_Click(object sender, RoutedEventArgs e)
- {
- FWindow.FCurrentPAK = string.Empty;
-
- //LOAD ALL
- if (!MI_DifferenceMode.IsChecked && !MI_UpdateMode.IsChecked)
- {
- await PAKsLoader.LoadAllPAKs();
- }
-
- //LOAD DIFF
- if (MI_DifferenceMode.IsChecked && !MI_UpdateMode.IsChecked)
- {
- await PAKsLoader.LoadDifference();
- }
-
- //LOAD AND EXTRACT DIFF
- if (MI_DifferenceMode.IsChecked && MI_UpdateMode.IsChecked)
- {
- await PAKsLoader.LoadDifference(true);
- if (PAKsLoader.umIsOk)
- await AssetsLoader.ExtractUpdateMode();
- }
- }
- private void MI_ReloadAESs_Click(object sender, RoutedEventArgs e)
- {
- DynamicKeysChecker.SetDynamicKeys(true);
- }
- private async void MI_BackupPAKs_Click(object sender, RoutedEventArgs e)
- {
- await BackupPAKs.CreateBackupFile();
- }
- private void MI_Settings_Click(object sender, RoutedEventArgs e)
- {
- DebugHelper.WriteLine("FWindow: Settings");
- if (!FormsUtility.IsWindowOpen("Settings"))
- {
- new FModel_Settings().Show();
- }
- else { FormsUtility.GetOpenedWindow("Settings").Focus(); }
- }
- private void MI_Search_Click(object sender, RoutedEventArgs e)
- {
- DebugHelper.WriteLine("FWindow: Search Files");
- if (!FormsUtility.IsWindowOpen("Search"))
- {
- new FModel_SearchFiles().Show();
- }
- else { FormsUtility.GetOpenedWindow("Search").Focus(); }
- }
- private void MI_HexViewer_Click(object sender, RoutedEventArgs e)
- {
- DebugHelper.WriteLine("FWindow: Hex Viewer");
- if (!FormsUtility.IsWindowOpen("Hex Viewer"))
- {
- new HexViewer().Show();
- }
- else { FormsUtility.GetOpenedWindow("Hex Viewer").Focus(); }
- }
- private void MI_ExportRaw_Click(object sender, RoutedEventArgs e)
- {
- if (ListBox_Main.SelectedIndex >= 0)
- {
- FWindow.FCurrentAsset = ListBox_Main.SelectedItem.ToString();
- AssetsUtility.ExportAssetData();
- }
- }
- private void MI_SaveJson_Click(object sender, RoutedEventArgs e)
- {
- AssetsUtility.SaveAssetProperties();
- }
- private void MI_OpenOutputFolder_Click(object sender, RoutedEventArgs e)
- {
- FoldersUtility.OpenOutputFolder();
- }
- private void MI_Save_Image_Click(object sender, RoutedEventArgs e)
- {
- if (ImageBox_Main.Source != null)
- {
- ImagesUtility.SaveImageDialog();
- }
- }
- private void MI_MergeImages_Click(object sender, RoutedEventArgs e)
- {
- DebugHelper.WriteLine("FWindow: Images Merger");
- if (!FormsUtility.IsWindowOpen("Images Merger"))
- {
- new FModel_ImagesMerger().Show();
- }
- else { FormsUtility.GetOpenedWindow("Images Merger").Focus(); }
- }
- private void MI_Changelog_Click(object sender, RoutedEventArgs e)
- {
- System.Diagnostics.Process.Start("https://github.com/iAmAsval/FModel/releases/latest");
- }
- private void MI_BugReports_Click(object sender, RoutedEventArgs e)
- {
- System.Diagnostics.Process.Start("https://github.com/iAmAsval/FModel/issues/new");
- }
- private void MI_About_Click(object sender, RoutedEventArgs e)
- {
- DebugHelper.WriteLine("FWindow: About");
- if (!FormsUtility.IsWindowOpen("About"))
- {
- new FModel_About().Show();
- }
- else { FormsUtility.GetOpenedWindow("About").Focus(); }
- }
- private void MI_Change_Header(object sender, RoutedEventArgs e)
- {
- //DIFFERENCE MODE
- if (MI_DifferenceMode.IsChecked)
- {
- MI_LoadOnePAK.IsEnabled = false;
- MI_LoadAllPAKs.Header = "Load Difference";
- MI_UpdateMode.IsEnabled = true;
- }
- if (!MI_DifferenceMode.IsChecked)
- {
- MI_LoadOnePAK.IsEnabled = true;
- MI_UpdateMode.IsEnabled = false;
- MI_UpdateMode.IsChecked = false;
- }
-
- //UPDATE MODE
- if (MI_UpdateMode.IsChecked)
- {
- MI_LoadAllPAKs.Header = "Load And Extract Difference";
- MI_UpdateMode.IsEnabled = true;
- MI_Auto_Save_Images.IsChecked = true; //auto save images
-
- if (MI_DifferenceMode.IsChecked && MI_UpdateMode.IsChecked)
- {
- if (!FormsUtility.IsWindowOpen("Update Mode"))
- {
- new FModel_UpdateMode().Show();
- }
- else { FormsUtility.GetOpenedWindow("Update Mode").Focus(); }
- }
- }
- if (!MI_UpdateMode.IsChecked)
- {
- MI_LoadAllPAKs.Header = "Load Difference";
- MI_Auto_Save_Images.IsChecked = false;
- }
-
- //BOTH
- if (!MI_DifferenceMode.IsChecked && !MI_UpdateMode.IsChecked)
- {
- MI_LoadAllPAKs.Header = "Load All PAKs";
- }
- }
- #endregion
-
- #region TREEVIEW EVENTS
- private async void NodeSelected(object sender, RoutedEventArgs e)
- {
- TreeViewItem currContainer = e.OriginalSource as TreeViewItem;
- if (currContainer != null)
- {
- FWindow.TVItem = currContainer;
- await ListBoxUtility.PopulateListBox(currContainer);
- }
-
- }
- private async void RC_ExtractFolders_Click(object sender, RoutedEventArgs e)
- {
- if (TreeView_Main.SelectedItem != null)
- {
- string path = TreeViewUtility.GetFullPath(FWindow.TVItem);
- await AssetsLoader.ExtractFoldersAndSub(path);
- }
- }
- #endregion
-
- #region LISTBOX EVENTS
- private void ListBox_Main_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- if (e.AddedItems.Count > 0) { ((ListBox)sender).ScrollIntoView(e.AddedItems[0]); }
- if (!AssetsLoader.isRunning) { Button_Extract.IsEnabled = ListBox_Main.SelectedIndex >= 0; }
- }
- private async void ListBox_Main_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- if (!AssetsLoader.isRunning && ListBox_Main.SelectedIndex >= 0)
- {
- await AssetsLoader.LoadSelectedAsset();
- }
- }
- private async void FilterTextBox_Main_TextChanged(object sender, TextChangedEventArgs e)
- {
- await ListBoxUtility.FilterListBox();
- }
- private async void RC_Extract_Click(object sender, RoutedEventArgs e)
- {
- if (ListBox_Main.SelectedIndex >= 0)
- {
- await AssetsLoader.LoadSelectedAsset();
- }
- }
- private void RC_ExportData_Click(object sender, RoutedEventArgs e)
- {
- if (ListBox_Main.SelectedIndex >= 0)
- {
- FWindow.FCurrentAsset = ListBox_Main.SelectedItem.ToString();
- AssetsUtility.ExportAssetData();
- }
- }
- private void RC_SaveData_Click(object sender, RoutedEventArgs e)
- {
- AssetsUtility.SaveAssetProperties();
- }
- private void RC_Copy_FPath_Click(object sender, RoutedEventArgs e)
- {
- if (ListBox_Main.SelectedIndex >= 0)
- {
- FWindow.FCurrentAsset = ListBox_Main.SelectedItem.ToString();
- Clipboard.SetText(AssetsUtility.GetAssetPathToCopy());
- }
- }
- private void RC_Copy_FName_Click(object sender, RoutedEventArgs e)
- {
- if (ListBox_Main.SelectedIndex >= 0)
- {
- FWindow.FCurrentAsset = ListBox_Main.SelectedItem.ToString();
- Clipboard.SetText(AssetsUtility.GetAssetPathToCopy(true));
- }
- }
- private void RC_Copy_FPath_NoExt_Click(object sender, RoutedEventArgs e)
- {
- if (ListBox_Main.SelectedIndex >= 0)
- {
- FWindow.FCurrentAsset = ListBox_Main.SelectedItem.ToString();
- Clipboard.SetText(AssetsUtility.GetAssetPathToCopy(false, false));
- }
- }
- private void RC_Copy_FName_NoExt_Click(object sender, RoutedEventArgs e)
- {
- if (ListBox_Main.SelectedIndex >= 0)
- {
- FWindow.FCurrentAsset = ListBox_Main.SelectedItem.ToString();
- Clipboard.SetText(AssetsUtility.GetAssetPathToCopy(true, false));
- }
- }
- private void RC_Properties_Click(object sender, RoutedEventArgs e)
- {
- if (ListBox_Main.SelectedIndex >= 0)
- {
- FWindow.FCurrentAsset = ListBox_Main.SelectedItem.ToString();
- AssetInformations.OpenAssetInfos();
- }
- }
- #endregion
- }
-}
diff --git a/FModel/Forms/AESManager.xaml b/FModel/Forms/AESManager.xaml
deleted file mode 100644
index 345f609a..00000000
--- a/FModel/Forms/AESManager.xaml
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/FModel/Forms/AESManager.xaml.cs b/FModel/Forms/AESManager.xaml.cs
deleted file mode 100644
index ae3d9732..00000000
--- a/FModel/Forms/AESManager.xaml.cs
+++ /dev/null
@@ -1,145 +0,0 @@
-using FModel.Methods;
-using FModel.Methods.AESManager;
-using FModel.Methods.Utilities;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Media;
-using System.Windows.Navigation;
-using FProp = FModel.Properties.Settings;
-
-namespace FModel.Forms
-{
- ///
- /// Logique d'interaction pour AESManager.xaml
- ///
- public partial class AESManager : Window
- {
- private static readonly string AESManager_PATH = FProp.Default.FOutput_Path + "\\FAESManager.xml";
-
- public AESManager()
- {
- InitializeComponent();
- this.SetValue(TextOptions.TextFormattingModeProperty, TextFormattingMode.Display);
- }
-
- private void Window_Loaded(object sender, RoutedEventArgs e)
- {
- AddLblTxtForDynamicPAKs();
- GetUserSettings();
- }
-
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- SetUserSettings();
- PAKsUtility.DisableNonKeyedPAKs();
- Close();
- }
-
- private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
- {
- System.Diagnostics.Process.Start(e.Uri.AbsoluteUri);
- }
-
- private void AddLblTxtForDynamicPAKs()
- {
- if (PAKEntries.PAKEntriesList != null && PAKEntries.PAKEntriesList.Any())
- {
- if (AESEntries.AESEntriesList == null) { KeysManager.Deserialize(); }
- int yPos = 4;
-
- foreach (PAKInfosEntry Pak in PAKEntries.PAKEntriesList.Where(x => x.bTheDynamicPAK))
- {
- Label PakLabel = new Label
- {
- Content = Path.GetFileNameWithoutExtension(Pak.ThePAKPath),
- HorizontalAlignment = HorizontalAlignment.Left,
- Margin = new Thickness(2, yPos - 2, 0, 0),
- VerticalAlignment = VerticalAlignment.Top,
- Foreground = new SolidColorBrush(Color.FromRgb(239, 239, 239))
- };
-
- TextBox PakTextBox = new TextBox
- {
- Height = 19,
- TextWrapping = TextWrapping.NoWrap,
- AcceptsReturn = false,
- Margin = new Thickness(160, yPos, 5, 0),
- VerticalAlignment = VerticalAlignment.Top,
- Foreground = new SolidColorBrush(Color.FromRgb(239, 239, 239)),
- Name = $"TxtBox_{Regex.Match(Path.GetFileNameWithoutExtension(Pak.ThePAKPath), @"\d+").Value}"
- };
-
- string PAKKeyFromXML = string.Empty;
- if (AESEntries.AESEntriesList != null && AESEntries.AESEntriesList.Any())
- {
- PAKKeyFromXML = AESEntries.AESEntriesList.Where(x => string.Equals(x.ThePAKName, Path.GetFileNameWithoutExtension(Pak.ThePAKPath))).Select(x => x.ThePAKKey).FirstOrDefault();
- PakTextBox.Text = $"0x{PAKKeyFromXML}";
- }
-
- yPos += 28;
- Grid_DynamicKeys.Children.Add(PakLabel);
- Grid_DynamicKeys.Children.Add(PakTextBox);
-
- DebugHelper.WriteLine($"AESManager GET: {Pak.ThePAKPath} with key: {PAKKeyFromXML}");
- }
- }
- }
-
- private void GetUserSettings()
- {
- MAesTextBox.Text = $"0x{FProp.Default.FPak_MainAES}";
- DebugHelper.WriteLine($"AESManager GET: Main PAKs with key: {FProp.Default.FPak_MainAES}");
- }
-
- private void SetUserSettings()
- {
- //MAIN AES
- if (!string.IsNullOrEmpty(MAesTextBox.Text))
- {
- if (MAesTextBox.Text.StartsWith("0x"))
- {
- FProp.Default.FPak_MainAES = Regex.Replace(MAesTextBox.Text.Substring(2).ToUpper(), @"\s+", string.Empty);
- }
- else { FProp.Default.FPak_MainAES = Regex.Replace(MAesTextBox.Text.ToUpper(), @"\s+", string.Empty); }
- }
- else { FProp.Default.FPak_MainAES = string.Empty; }
- DebugHelper.WriteLine($"AESManager SET: Main PAKs with key: {MAesTextBox.Text}");
-
- //DYNAMIC AESs
- AESEntries.AESEntriesList = new List();
- if (PAKEntries.PAKEntriesList != null && PAKEntries.PAKEntriesList.Any())
- {
- foreach (PAKInfosEntry Pak in PAKEntries.PAKEntriesList.Where(x => x.bTheDynamicPAK))
- {
- TextBox PakTextBox = UIHelper.FindChild(this, $"TxtBox_{Regex.Match(Path.GetFileNameWithoutExtension(Pak.ThePAKPath), @"\d+").Value}");
- if (!string.IsNullOrEmpty(PakTextBox.Text))
- {
- if (PakTextBox.Text.StartsWith("0x"))
- {
- KeysManager.Serialize(Path.GetFileNameWithoutExtension(Pak.ThePAKPath), Regex.Replace(PakTextBox.Text.Substring(2).ToUpper(), @"\s+", string.Empty));
- }
- else { KeysManager.Serialize(Path.GetFileNameWithoutExtension(Pak.ThePAKPath), Regex.Replace(PakTextBox.Text.ToUpper(), @"\s+", string.Empty)); }
- }
- else { KeysManager.Serialize(Path.GetFileNameWithoutExtension(Pak.ThePAKPath), string.Empty); }
- DebugHelper.WriteLine($"AESManager SET: {Pak.ThePAKPath} with key: {PakTextBox.Text}");
- }
-
- Directory.CreateDirectory(Path.GetDirectoryName(AESManager_PATH));
- using (var fileStream = new FileStream(AESManager_PATH, FileMode.Create))
- {
- KeysManager.serializer.Serialize(fileStream, AESEntries.AESEntriesList);
- }
- }
-
- //SAVE
- FProp.Default.Save();
- DebugHelper.WriteLine($"AESManager: Saved");
- }
-
-
- }
-}
diff --git a/FModel/Forms/ColorPicker/Code/ColorSwatchItem.cs b/FModel/Forms/ColorPicker/Code/ColorSwatchItem.cs
deleted file mode 100644
index 0ff39a3f..00000000
--- a/FModel/Forms/ColorPicker/Code/ColorSwatchItem.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Media;
-
-namespace ColorPickerWPF.Code
-{
- public class ColorSwatchItem
- {
- public Color Color { get; set; }
- public string HexString { get; set; }
- }
-}
diff --git a/FModel/Forms/ColorPicker/ColorPickRow.xaml b/FModel/Forms/ColorPicker/ColorPickRow.xaml
deleted file mode 100644
index c7a8f3b4..00000000
--- a/FModel/Forms/ColorPicker/ColorPickRow.xaml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/FModel/Forms/ColorPicker/ColorPickerControl.xaml b/FModel/Forms/ColorPicker/ColorPickerControl.xaml
deleted file mode 100644
index 3561dd6d..00000000
--- a/FModel/Forms/ColorPicker/ColorPickerControl.xaml
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/FModel/Forms/ColorPicker/ColorPickerSwatch.xaml b/FModel/Forms/ColorPicker/ColorPickerSwatch.xaml
deleted file mode 100644
index d9b2b15c..00000000
--- a/FModel/Forms/ColorPicker/ColorPickerSwatch.xaml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/FModel/Forms/ColorPicker/ColorPickerWindow.xaml b/FModel/Forms/ColorPicker/ColorPickerWindow.xaml
deleted file mode 100644
index 11f11e2b..00000000
--- a/FModel/Forms/ColorPicker/ColorPickerWindow.xaml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/FModel/Forms/FModel_About.xaml.cs b/FModel/Forms/FModel_About.xaml.cs
deleted file mode 100644
index 28c9c623..00000000
--- a/FModel/Forms/FModel_About.xaml.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System.Windows;
-
-namespace FModel.Forms
-{
- ///
- /// Logique d'interaction pour FModel_About.xaml
- ///
- public partial class FModel_About : Window
- {
- public FModel_About()
- {
- InitializeComponent();
- }
- }
-}
diff --git a/FModel/Forms/FModel_ImagesMerger.xaml b/FModel/Forms/FModel_ImagesMerger.xaml
deleted file mode 100644
index 24d3b04d..00000000
--- a/FModel/Forms/FModel_ImagesMerger.xaml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/FModel/Forms/FModel_ImagesMerger.xaml.cs b/FModel/Forms/FModel_ImagesMerger.xaml.cs
deleted file mode 100644
index 3d7b1b3a..00000000
--- a/FModel/Forms/FModel_ImagesMerger.xaml.cs
+++ /dev/null
@@ -1,308 +0,0 @@
-using FModel.Methods.Utilities;
-using Microsoft.Win32;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using FProp = FModel.Properties.Settings;
-
-namespace FModel.Forms
-{
- ///
- /// Logique d'interaction pour FModel_ImagesMerger.xaml
- ///
- public partial class FModel_ImagesMerger : Window
- {
- private static List _imagePath { get; set; }
-
- public FModel_ImagesMerger()
- {
- InitializeComponent();
- }
-
- private void Window_Loaded(object sender, RoutedEventArgs e)
- {
- ImagesListBox.Items.Clear();
- }
-
- private async void UpdatePreview(object sender, RoutedEventArgs e)
- {
- if (MergerPreview_Image.Source != null)
- {
- await UpdateMergerPreview();
- }
- }
-
- private async Task UpdateMergerPreview()
- {
- DebugHelper.WriteLine("Merger: Merging images of the listbox");
-
- AddImages_Button.IsEnabled = false;
- RemoveImage_Button.IsEnabled = false;
- ClearImages_Button.IsEnabled = false;
- ImagesPerRow_Slider.IsEnabled = false;
- OpenImage_Button.IsEnabled = false;
- SaveImage_Button.IsEnabled = false;
-
- if ((_imagePath != null && _imagePath.Count > 0) || ImagesListBox.Items.Count > 0)
- {
- _imagePath = new List();
- for (int i = 0; i < ImagesListBox.Items.Count; ++i)
- {
- _imagePath.Add(((ListBoxItem)ImagesListBox.Items[i]).ContentStringFormat);
- }
- }
- int imageCount = _imagePath.Count;
- int numperrow = Convert.ToInt32(ImagesPerRow_Slider.Value);
-
- await Task.Run(() =>
- {
- DrawingVisual drawingVisual = new DrawingVisual();
- using (DrawingContext drawingContext = drawingVisual.RenderOpen())
- {
- //INITIALIZATION
- drawingContext.DrawRectangle(Brushes.Transparent, null, new Rect(new Point(0, 0), new Size(515, 515)));
-
- int num = 1;
- int curW = 0;
- int curH = 0;
- int maxHeight = 0;
-
- for (int i = 0; i < imageCount; i++)
- {
- BitmapImage source = new BitmapImage(new Uri(_imagePath[i]));
- source.DecodePixelWidth = 515;
-
- double width = source.Width;
- double height = source.Height;
- if (height > maxHeight) { maxHeight = Convert.ToInt32(height); }
-
- drawingContext.DrawImage(source, new Rect(new Point(curW, curH), new Size(width, height)));
- if (num % numperrow == 0)
- {
- curW = 0;
- curH += maxHeight + 5;
- num += 1;
-
- maxHeight = 0; //reset max height for each new row
- }
- else
- {
- curW += Convert.ToInt32(width) + 5;
- num += 1;
- }
- }
- }
-
- RenderTargetBitmap RTB = new RenderTargetBitmap((int)Math.Floor(drawingVisual.DescendantBounds.Width), (int)Math.Floor(drawingVisual.DescendantBounds.Height), 96, 96, PixelFormats.Pbgra32);
- RTB.Render(drawingVisual);
- RTB.Freeze(); //We freeze to apply the RTB to our imagesource from the UI Thread
-
- this.Dispatcher.InvokeAsync(() =>
- {
- MergerPreview_Image.Source = BitmapFrame.Create(RTB); //thread safe and fast af
- });
-
- }).ContinueWith(TheTask =>
- {
- TasksUtility.TaskCompleted(TheTask.Exception);
- });
-
- GC.Collect();
- GC.WaitForPendingFinalizers();
-
- AddImages_Button.IsEnabled = true;
- RemoveImage_Button.IsEnabled = true;
- ClearImages_Button.IsEnabled = true;
- ImagesPerRow_Slider.IsEnabled = true;
- OpenImage_Button.IsEnabled = true;
- SaveImage_Button.IsEnabled = true;
-
- DebugHelper.WriteLine("Merger: Images of the listbox merged successfully");
- }
-
- private async void AddImages_Button_Click(object sender, RoutedEventArgs e)
- {
- OpenFileDialog openFiledialog = new OpenFileDialog();
- openFiledialog.Title = "Choose your images";
- openFiledialog.InitialDirectory = FProp.Default.FOutput_Path + "\\Icons\\";
- openFiledialog.Multiselect = true;
- openFiledialog.Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*";
- if (openFiledialog.ShowDialog() == true)
- {
- AddFiles(openFiledialog.FileNames);
- await UpdateMergerPreview();
- }
- }
-
- private void AddFiles(string[] files)
- {
- if (files.Length > 0)
- {
- foreach (string file in files)
- {
- ListBoxItem itm = new ListBoxItem();
- itm.ContentStringFormat = file;
- itm.Content = Path.GetFileNameWithoutExtension(file);
-
- ImagesListBox.Items.Add(itm);
- DebugHelper.WriteLine($"Merger: {file} added to the listbox");
- }
- }
- }
-
- private async void RemoveImage_Button_Click(object sender, RoutedEventArgs e)
- {
- if (ImagesListBox.Items.Count > 0 && ImagesListBox.SelectedItems.Count > 0)
- {
- for (int i = ImagesListBox.SelectedItems.Count - 1; i >= 0; --i)
- {
- ImagesListBox.Items.Remove(ImagesListBox.SelectedItems[i]);
- }
-
- await UpdateMergerPreview();
- }
- }
-
- private void ClearImages_Button_Click(object sender, RoutedEventArgs e)
- {
- ImagesListBox.Items.Clear();
- MergerPreview_Image.Source = null;
- }
-
- private void OpenImage_Button_Click(object sender, RoutedEventArgs e)
- {
- if (MergerPreview_Image.Source != null)
- {
- DebugHelper.WriteLine("Merger: Opening preview of the merged image");
- if (!FormsUtility.IsWindowOpen("Merged Image"))
- {
- Window win = new Window();
- win.Title = "Merged Image";
- win.SetValue(TextOptions.TextFormattingModeProperty, TextFormattingMode.Display);
- win.WindowStartupLocation = WindowStartupLocation.CenterScreen;
- win.Width = MergerPreview_Image.Source.Width;
- win.Height = MergerPreview_Image.Source.Height;
- if (MergerPreview_Image.Source.Height > 1000)
- {
- win.WindowState = WindowState.Maximized;
- }
-
- DockPanel dockPanel = new DockPanel
- {
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center,
- };
-
- Image img = new Image();
- img.UseLayoutRounding = true;
- img.Source = MergerPreview_Image.Source;
- dockPanel.Children.Add(img);
-
- win.Content = dockPanel;
- win.Show();
- }
- else { FormsUtility.GetOpenedWindow("Merged Image").Focus(); }
- }
- }
-
- private void SaveImage_Button_Click(object sender, RoutedEventArgs e)
- {
- if (MergerPreview_Image.Source != null)
- {
- DebugHelper.WriteLine("Merger: Saving image...");
-
- SaveFileDialog saveFileDialog = new SaveFileDialog();
- saveFileDialog.Title = "Save Image";
- saveFileDialog.FileName = "Merger";
- saveFileDialog.InitialDirectory = FProp.Default.FOutput_Path;
- saveFileDialog.Filter = "PNG Files (*.png)|*.png";
- if (saveFileDialog.ShowDialog() == true)
- {
- string path = saveFileDialog.FileName;
- using (var fileStream = new FileStream(path, FileMode.Create))
- {
- PngBitmapEncoder encoder = new PngBitmapEncoder();
- encoder.Frames.Add(BitmapFrame.Create((BitmapSource)MergerPreview_Image.Source));
- encoder.Save(fileStream);
-
- if (File.Exists(path))
- {
- DebugHelper.WriteLine("Merger: Image saved at " + path);
-
- new UpdateMyConsole(Path.GetFileNameWithoutExtension(path), CColors.Blue).Append();
- new UpdateMyConsole(" successfully saved", CColors.White, true).Append();
- }
- else //just in case
- {
- DebugHelper.WriteLine("Merger: Image couldn't be saved at " + path);
-
- new UpdateMyConsole("Bruh moment\nCouldn't save ", CColors.White).Append();
- new UpdateMyConsole(Path.GetFileNameWithoutExtension(path), CColors.Blue, true).Append();
- }
- }
- }
- }
- }
-
- private async void Up_Button_Click(object sender, RoutedEventArgs e)
- {
- if (ImagesListBox.Items.Count > 0 && ImagesListBox.SelectedItems.Count > 0)
- {
- bool reloadImage = false;
-
- int[] indices = ImagesListBox.SelectedItems.Cast