FModel v4.0

This commit is contained in:
Valentin 2021-05-22 22:10:08 +02:00
parent d7751d6f95
commit c403453cc3
702 changed files with 17887 additions and 52663 deletions

View File

@ -1,21 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
1. Describe the bug
*A clear and concise description of what the bug is.*
2. Steps to reproduce the behavior
3. Send logs
*The latest log file is ABSOLUTELY MANDATORY*
4. Add screenshots
**DO NOT** report issues to ask where is `<insert your favorite asset here>`.
**DO NOT** report issues if you have AES errors.
**DO NOT** report issues if your game is not up to date.
Without following the rules above, your issue will be closed without explanation.

View File

@ -1,17 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement, suggestion
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

32
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: Artifact Generator
on:
workflow_dispatch:
jobs:
build:
runs-on: windows-latest
steps:
- name: GIT Checkout
uses: actions/checkout@v2
with:
submodules: 'true'
token: ${{ secrets.PAT_TOKEN }}
- name: .NET 5 Setup
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
- name: .NET Restore
run: dotnet restore FModel
- name: .NET Publish
run: dotnet publish FModel -c Release -f net5.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false --no-self-contained -r win-x64
- name: EXE Upload
uses: actions/upload-artifact@v2
with:
name: FModel
path: D:\a\FModel4\FModel4\FModel\bin\Publish\

54
.gitignore vendored
View File

@ -4,6 +4,7 @@
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
@ -12,6 +13,9 @@
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
@ -19,13 +23,17 @@
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
.idea/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
@ -36,9 +44,10 @@ Generated\ Files/
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
@ -52,7 +61,6 @@ BenchmarkDotNet.Artifacts/
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# StyleCop
StyleCopReport.xml
@ -60,7 +68,7 @@ StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*_h.h
*.ilk
*.meta
*.obj
@ -77,6 +85,7 @@ StyleCopReport.xml
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
@ -119,9 +128,6 @@ _ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
@ -179,6 +185,8 @@ PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
@ -203,12 +211,14 @@ BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
!?*.[Cc]ache/
# Others
ClientBin/
@ -221,7 +231,7 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
@ -236,7 +246,7 @@ Generated_Code/
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
#Backup*/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
@ -252,6 +262,9 @@ ServiceFabricBackup/
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
@ -287,12 +300,8 @@ paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
@ -317,7 +326,7 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
@ -326,8 +335,17 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
.github
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "CUE4Parse"]
path = CUE4Parse
url = https://github.com/FabianFG/CUE4Parse

1
CUE4Parse Submodule

@ -0,0 +1 @@
Subproject commit 09a98cdafd273db1cdee58ded37ed4f5b3a2ff22

View File

@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel\FModel.csproj", "{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{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|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 = {688BAB53-42F7-45F2-AFDF-79A07771213F}
EndGlobalSection
EndGlobal

View File

@ -1,14 +1,19 @@
<Application x:Class="FModel.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
StartupUri="MainWindow.xaml" ShutdownMode="OnMainWindowClose"
DispatcherUnhandledException="OnDispatcherUnhandledException">
Exit="AppExit" DispatcherUnhandledException="OnUnhandledException">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Theme/Style.xaml" />
<ResourceDictionary Source="pack://application:,,,/ToastNotifications.Messages;component/Themes/Default.xaml" />
<ResourceDictionary Source="pack://application:,,,/AdonisUI;component/ColorSchemes/Dark.xaml"/>
<ResourceDictionary Source="pack://application:,,,/AdonisUI.ClassicTheme;component/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Color x:Key="{x:Static adonisUi:Colors.AccentColor}">#206BD4</Color>
<Color x:Key="{x:Static adonisUi:Colors.AlertColor}">#D49220</Color>
<Color x:Key="{x:Static adonisUi:Colors.ErrorColor}">#C22B2B</Color>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -1,80 +1,124 @@
using FModel.Logger;
using FModel.Utils;
using FModel.ViewModels.ComboBox;
using FModel.ViewModels.StatusBar;
using FModel.Windows.DarkMessageBox;
using AdonisUI.Controls;
using Microsoft.Win32;
using Serilog;
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;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using Newtonsoft.Json;
using Serilog.Sinks.SystemConsole.Themes;
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
namespace FModel
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
public partial class App
{
internal static Stopwatch StartTimer { get; private set; }
static bool framerateSet = false;
protected override void OnStartup(StartupEventArgs e)
{
StartTimer = Stopwatch.StartNew();
DebugHelper.Init(LogsFilePath); // get old settings too
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(ProgramLang.GetProgramLang());
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);
base.OnStartup(e);
}
public static string LogsFilePath
{
get
try
{
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);
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
}
catch
{
UserSettings.Default = new UserSettings();
}
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
{
UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
}
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Saves"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Textures"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Sounds"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
Log.Information("Version {Version}", Constants.APP_VERSION);
Log.Information("{OS}", GetOperatingSystemProductName());
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
Log.Information("Culture {SysLang}", Thread.CurrentThread.CurrentUICulture);
}
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
private void AppExit(object sender, ExitEventArgs e)
{
string errorMessage = string.Format(FModel.Properties.Resources.UnhandledExceptionOccured, e.Exception.Message);
DebugHelper.WriteException(e.Exception, "thrown in App.xaml.cs by OnDispatcherUnhandledException");
DarkMessageBoxHelper.Show(errorMessage, FModel.Properties.Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error, true);
Log.Information("");
Log.CloseAndFlush();
UserSettings.Save();
Environment.Exit(0);
}
private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
Log.Error("{Exception}", e.Exception);
var messageBox = new MessageBoxModel
{
Text = $"An unhandled exception occurred: {e.Exception.Message}",
Caption = "Fatal Error",
Icon = MessageBoxImage.Error,
Buttons = new[]
{
MessageBoxButtons.Custom("Restart", EErrorKind.Restart),
MessageBoxButtons.Custom("OK", EErrorKind.Ignore)
},
IsSoundEnabled = false
};
MessageBox.Show(messageBox);
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
{
ApplicationService.ApplicationView.Restart();
}
e.Handled = true;
}
internal static void SetFramerate()
private string GetOperatingSystemProductName()
{
if (!framerateSet)
var productName = string.Empty;
try
{
System.Windows.Media.Animation.Timeline.DesiredFrameRateProperty.OverrideMetadata(
typeof(System.Windows.Media.Animation.Timeline),
new FrameworkPropertyMetadata { DefaultValue = 10 });
framerateSet = true;
productName = GetRegistryValue(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", RegistryHive.LocalMachine);
}
catch
{
// ignored
}
if (string.IsNullOrEmpty(productName))
productName = Environment.OSVersion.VersionString;
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
}
private string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
{
using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path);
if (rk != null)
return rk.GetValue(name, null) as string;
return string.Empty;
}
}
}
}

63
FModel/Constants.cs Normal file
View File

@ -0,0 +1,63 @@
using System;
using System.Reflection;
using CUE4Parse.UE4.Objects.Core.Misc;
namespace FModel
{
public static class Constants
{
public static readonly string APP_VERSION = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000";
public static readonly FGuid ZERO_GUID = new(0U);
public const string WHITE = "#DAE5F2";
public const string RED = "#E06C75";
public const string GREEN = "#98C379";
public const string YELLOW = "#E5C07B";
public const string BLUE = "#528BCC";
public const string DONATE_LINK = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EP9SSWG8MW4UC&source=url";
public const string CHANGELOG_LINK = "https://github.com/iAmAsval/FModel/releases/latest";
public const string ISSUE_LINK = "https://github.com/iAmAsval/FModel/issues/new";
public const string DISCORD_LINK = "https://discord.gg/fdkNYYQ";
public const string _FN_LIVE_TRIGGER = "fortnite-live.manifest";
public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest";
public static string GetRandomColor()
{
return _randomColors[_random.Next(0, 255)];
}
private static readonly Random _random = new(Environment.TickCount);
private static readonly string[] _randomColors =
{
"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",
};
}
}

View File

@ -0,0 +1,43 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Creator.Bases.FN;
using SkiaSharp;
namespace FModel.Creator.Bases.BB
{
public class BaseBreakersIcon : BaseIcon
{
public BaseBreakersIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
SeriesBackground = Utils.GetBitmap("WorldExplorers/Content/UMG/Materials/t_TextGradient.t_TextGradient");
Background = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("636363")};
Border = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF")};
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FSoftObjectPath iconTextureAssetData, "IconTextureAssetData"))
Preview = Utils.GetBitmap(iconTextureAssetData);
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description"))
Description = description.Text;
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
return SKImage.FromBitmap(ret);
}
}
}

View File

@ -1,60 +0,0 @@
using System;
using System.Windows;
using FModel.Creator.Rarities;
using FModel.Creator.Texts;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Bases
{
public class BaseBBDefinition : IBase
{
public SKBitmap FallbackImage;
public SKBitmap IconImage;
public SKBitmap RarityBackgroundImage;
public SKColor[] RarityBackgroundColors;
public SKColor[] RarityBorderColor;
public string DisplayName;
public string Description;
public int Width = 512;
public int Height = 512;
public int Margin = 2;
public BaseBBDefinition(string exportType)
{
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream);
RarityBackgroundImage = null;
IconImage = FallbackImage;
RarityBackgroundColors = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("636363") };
RarityBorderColor = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") };
DisplayName = "";
Description = "";
}
public BaseBBDefinition(IUExport export, string exportType) : this(exportType)
{
if (export.GetExport<SoftObjectProperty>("IconTextureAssetData") is {} previewImage)
IconImage = Utils.GetSoftObjectTexture(previewImage);
else if (export.GetExport<ObjectProperty>("IconTextureAssetData") is {} iconTexture)
IconImage = Utils.GetObjectTexture(iconTexture);
if (export.GetExport<TextProperty>("DisplayName") is {} displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
if (export.GetExport<TextProperty>("Description") is {} description)
Description = Text.GetTextPropertyBase(description);
RarityBackgroundImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/battle-breakers-item-background.png"))?.Stream);
}
SKBitmap IBase.FallbackImage => FallbackImage;
SKBitmap IBase.IconImage => IconImage;
SKColor[] IBase.RarityBackgroundColors => RarityBackgroundColors;
SKColor[] IBase.RarityBorderColor => RarityBorderColor;
string IBase.DisplayName => DisplayName;
string IBase.Description => Description;
int IBase.Width => Width;
int IBase.Height => Height;
int IBase.Margin => Margin;
}
}

View File

@ -1,102 +0,0 @@
using FModel.Creator.Bundles;
using FModel.Creator.Texts;
using System.Collections.Generic;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{
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<Quest> Quests;
public List<CompletionReward> CompletionRewards;
public BaseBundle()
{
DisplayStyle = new Header();
DisplayName = "";
FolderName = "";
Watermark = Properties.Settings.Default.ChallengeBannerWatermark;
Quests = new List<Quest>();
CompletionRewards = new List<CompletionReward>();
}
/// <summary>
/// used for the settings
/// </summary>
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<StructProperty>("DisplayStyle") is StructProperty displayStyle)
DisplayStyle = new Header(displayStyle, assetFolder);
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
if (export.GetExport<ArrayProperty>("CareerQuestBitShifts") is ArrayProperty careerQuestBitShifts)
{
foreach (SoftObjectProperty questPath in careerQuestBitShifts.Value)
{
Package p = Utils.GetPropertyPakPackage(questPath.Value.AssetPathName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();
if (obj != null)
Quests.Add(new Quest(obj));
}
}
}
if (export.GetExport<ArrayProperty>("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.Contains("/Items/Tokens/") &&
!itemDefinition.Value.AssetPathName.String.Contains("/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);
}
}
}

View File

@ -1,106 +0,0 @@
using FModel.Creator.Rarities;
using FModel.Creator.Texts;
using SkiaSharp;
using System;
using System.Windows;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{
public class BaseGCosmetic : IBase
{
public SKBitmap FallbackImage;
public SKBitmap IconImage;
public SKColor[] RarityBackgroundColors;
public SKColor[] RarityBorderColor;
public SKBitmap RarityBackgroundImage1;
public SKBitmap RarityBackgroundImage2;
public string RarityDisplayName;
public string DisplayName;
public string Description;
public int Width = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons
public int Height = 512;
public int Margin = 2;
public BaseGCosmetic(string exportType)
{
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream);
IconImage = FallbackImage;
RarityBackgroundColors = new SKColor[2] { SKColor.Parse("FFFFFF"), SKColor.Parse("636363") };
RarityBorderColor = new SKColor[2] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") };
RarityBackgroundImage1 = null;
RarityBackgroundImage2 = null;
RarityDisplayName = "";
DisplayName = "";
Description = "";
Width = exportType switch
{
"GCosmeticCard" => 1536,
_ => 512
};
Height = exportType switch
{
"GCosmeticCard" => 450, // Not perfect, causes images to get stretched a bit, but actually allows text to show up so it works for now. - FireMonkey
_ => 512
};
}
public BaseGCosmetic(IUExport export, string exportType) : this(exportType)
{
// rarity
EnumProperty r = export.GetExport<EnumProperty>("Rarity");
Rarity.GetInGameRarity(this, r);
this.RarityDisplayName = r != null ? r?.Value.String["EXRarity::".Length..] : "Common";
// image
if (export.GetExport<SoftObjectProperty>("IconTexture") is SoftObjectProperty previewImage)
this.IconImage = Utils.GetSoftObjectTexture(previewImage);
else if (export.GetExport<ObjectProperty>("IconTexture") is ObjectProperty iconTexture)
this.IconImage = Utils.GetObjectTexture(iconTexture);
// text
if (export.GetExport<TextProperty>("DisplayName", "Title") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
if (export.GetExport<TextProperty>("Description") is TextProperty description)
Description = Text.GetTextPropertyBase(description);
RarityBackgroundImage1 = Utils.GetTexture("/Game/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox");
RarityBackgroundImage2 = Utils.GetTexture("/Game/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT");
}
public void Draw(SKCanvas c)
{
if (this.RarityBackgroundImage1 != null)
c.DrawBitmap(this.RarityBackgroundImage1, new SKRect(0, 0, Width, Height),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
if (this.RarityBackgroundImage2 != null)
c.DrawBitmap(this.RarityBackgroundImage2, new SKRect(0, 0, Width, Height),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true, Color = SKColors.Transparent.WithAlpha(75) });
int x = this.Margin * (int)2.5;
int radi = 15;
c.DrawCircle(x + radi, x + radi, radi, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(radi, radi),
(radi * 2) / 5 * 4,
this.RarityBackgroundColors,
SKShaderTileMode.Clamp)
});
}
SKBitmap IBase.FallbackImage => FallbackImage;
SKBitmap IBase.IconImage => IconImage;
SKColor[] IBase.RarityBackgroundColors => RarityBackgroundColors;
SKColor[] IBase.RarityBorderColor => RarityBorderColor;
string IBase.DisplayName => DisplayName;
string IBase.Description => Description;
int IBase.Width => Width;
int IBase.Height => Height;
int IBase.Margin => Margin;
}
}

View File

@ -1,164 +0,0 @@
using System;
using System.Collections.Generic;
using System.Windows;
using FModel.Creator.Icons;
using FModel.Creator.Rarities;
using FModel.Creator.Stats;
using FModel.Creator.Texts;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using FModel.Properties;
using FModel.Utils;
using SkiaSharp;
namespace FModel.Creator.Bases
{
public class BaseIcon : IBase
{
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; // must be increased if there are weapon stats, hero abilities or more to draw/show
public int Margin = 2;
public List<Statistic> 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.Parse("5EBC36"), SKColor.Parse("305C15") };
RarityBorderColor = new[] { SKColor.Parse("74EF52"), SKColor.Parse("74EF52") };
DisplayName = "";
Description = "";
ShortDescription = "";
CosmeticSource = "";
Stats = new List<Statistic>();
}
public BaseIcon(IUExport export, string assetName, bool forceHR) : this()
{
if (export.GetExport<ObjectProperty>("Series") is { } series)
Serie.GetRarity(this, series);
else if (Settings.Default.UseGameColors) // override default green
Rarity.GetInGameRarity(this, export.GetExport<EnumProperty>("Rarity")); // uncommon will be triggered by Rarity being null
else if (export.GetExport<EnumProperty>("Rarity") is { } rarity)
Rarity.GetHardCodedRarity(this, rarity);
if (export.GetExport<ObjectProperty>("HeroDefinition", "WeaponDefinition") is { } itemDef)
LargeSmallImage.GetPreviewImage(this, itemDef, assetName, forceHR);
else if (export.GetExport<SoftObjectProperty>(forceHR ? "LargePreviewImage" : "SmallPreviewImage", forceHR ? "ItemDisplayAsset" : "SmallImage", forceHR ? "SidePanelIcon" : "ToastIcon") is { } previewImage)
LargeSmallImage.GetPreviewImage(this, previewImage);
else if (export.GetExport<ObjectProperty>("access_item") is { } accessItem)
{
Package p = Utils.GetPropertyPakPackage(accessItem.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();
if (d != null)
{
IconImage = new BaseIcon(d, accessItem.Value.Resource.ObjectName.String + ".uasset", false).IconImage;
}
}
}
if (export.GetExport<TextProperty>("DisplayName", "DefaultHeaderText", "UIDisplayName") is { } displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
}
/// <summary>
/// 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
/// </summary>
public BaseIcon(IUExport export, string exportType, ref string assetName) : this()
{
// rarity
if (export.GetExport<ObjectProperty>("Series") is { } series)
Serie.GetRarity(this, series);
else if (Settings.Default.UseGameColors) // override default green
Rarity.GetInGameRarity(this, export.GetExport<EnumProperty>("Rarity")); // uncommon will be triggered by Rarity being null
else if (export.GetExport<EnumProperty>("Rarity") is { } rarity)
Rarity.GetHardCodedRarity(this, rarity);
// image
if (Settings.Default.UseItemShopIcon &&
DisplayAssetImage.GetDisplayAssetImage(this, export, ref assetName))
{ } // ^^^^ will return false if image not found, if so, we try to get the normal icon
else if (export.GetExport<ObjectProperty>("HeroDefinition", "WeaponDefinition") is { } itemDef)
LargeSmallImage.GetPreviewImage(this, itemDef, assetName);
else if (export.GetExport<SoftObjectProperty>("LargePreviewImage", "SidePanelIcon", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset") is { } previewImage)
LargeSmallImage.GetPreviewImage(this, previewImage);
else if (export.GetExport<ObjectProperty>("SmallPreviewImage", "ToastIcon") is { } smallPreviewImage)
IconImage = Utils.GetObjectTexture(smallPreviewImage);
else if (export.GetExport<StructProperty>("IconBrush") is { } iconBrush) // abilities
LargeSmallImage.GetPreviewImage(this, iconBrush);
// text
if (export.GetExport<TextProperty>("DisplayName", "DefaultHeaderText", "UIDisplayName") is { } displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
if (export.GetExport<TextProperty>("Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription") is { } description)
Description = Text.GetTextPropertyBase(description);
else if (export.GetExport<ArrayProperty>("Description") is { } arrayDescription) // abilities
Description = Text.GetTextPropertyBase(arrayDescription);
if (export.GetExport<StructProperty>("MaxStackSize") is { } maxStackSize)
ShortDescription = Text.GetMaxStackSize(maxStackSize);
else if (export.GetExport<StructProperty>("XpRewardAmount") is { } xpRewardAmount)
ShortDescription = Text.GetXpRewardAmount(xpRewardAmount);
else if (export.GetExport<TextProperty>("ShortDescription", "UIDisplaySubName") is { } 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<StructProperty>("GameplayTags") is { } gameplayTags)
GameplayTag.GetGameplayTags(this, gameplayTags, exportType);
else if (export.GetExport<ObjectProperty>("cosmetic_item") is { } cosmeticItem) // variants
CosmeticSource = cosmeticItem.Value.Resource.ObjectName.String;
if (Settings.Default.DrawStats && export.GetExport<SoftObjectProperty>("AmmoData") is { } ammoData)
Statistics.GetAmmoData(this, ammoData);
if (Settings.Default.DrawStats && export.GetExport<StructProperty>("WeaponStatHandle") is { } weaponStatHandle &&
(exportType.Equals("FortWeaponMeleeItemDefinition") ||
(export.GetExport<SoftObjectProperty>("StatList") is { } statList &&
!statList.Value.AssetPathName.String.StartsWith("/Game/UI/Tooltips/NoTooltipStats"))))
{
Statistics.GetWeaponStats(this, weaponStatHandle);
}
if (Settings.Default.DrawStats && export.GetExport<ObjectProperty>("HeroGameplayDefinition") is { } 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;
}
SKBitmap IBase.FallbackImage => FallbackImage;
SKBitmap IBase.IconImage => IconImage;
SKColor[] IBase.RarityBackgroundColors => RarityBackgroundColors;
SKColor[] IBase.RarityBorderColor => RarityBorderColor;
string IBase.DisplayName => DisplayName;
string IBase.Description => Description;
int IBase.Width => Size;
int IBase.Height => Size;
int IBase.Margin => Margin;
}
}

View File

@ -1,138 +0,0 @@
using FModel.Creator.Rarities;
using FModel.Creator.Texts;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases
{
public class BaseItemAccess
{
private readonly SKPaint descriptionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DescriptionTypeface,
TextSize = 13,
Color = SKColors.White,
};
public BaseIcon Item;
public string SItem;
public SKBitmap Lock;
public SKBitmap Unlock;
public string DisplayName;
public string Description;
public string UnlockDescription;
public int Size = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons
public BaseItemAccess()
{
Item = new BaseIcon();
SItem = "";
Lock = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128").Resize(24, 24);
Unlock = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128").Resize(24, 24);
DisplayName = "";
Description = "";
UnlockDescription = "";
}
public BaseItemAccess(IUExport export) : this()
{
if (export.GetExport<ObjectProperty>("access_item") is ObjectProperty accessItem)
{
SItem = accessItem.Value.Resource.ObjectName.String;
Package p = Utils.GetPropertyPakPackage(accessItem.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();
if (d != null)
{
Item = new BaseIcon(d, SItem + ".uasset", true);
}
}
}
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
if (export.GetExport<TextProperty>("Description") is TextProperty description)
Description = Text.GetTextPropertyBase(description);
if (export.GetExport<TextProperty>("UnlockDescription") is TextProperty unlockDescription)
UnlockDescription = Text.GetTextPropertyBase(unlockDescription);
}
public void Draw(SKCanvas c)
{
Rarity.DrawRarity(c, Item);
int size = 45;
int left = Size / 2;
SKPaint namePaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = size,
Color = SKColors.White,
TextAlign = SKTextAlign.Center,
};
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
{
SKShaper shaper = new SKShaper(namePaint.Typeface);
float shapedTextWidth;
while (true)
{
SKShaper.Result shapedText = shaper.Shape(DisplayName, namePaint);
shapedTextWidth = shapedText.Points[^1].X + namePaint.TextSize / 2f;
if (shapedTextWidth > (Size - (Item.Margin * 2)))
{
namePaint.TextSize -= 2;
}
else
{
break;
}
}
c.DrawShapedText(shaper, DisplayName, (Size - shapedTextWidth) / 2f, Item.Margin * 8 + size, namePaint);
}
else
{
while (namePaint.MeasureText(DisplayName) > (Size - (Item.Margin * 2)))
{
namePaint.TextSize = size -= 2;
}
c.DrawText(DisplayName, left, Item.Margin * 8 + size, namePaint);
}
int topBase = Item.Margin + size * 2;
c.DrawBitmap(Lock, new SKRect(50, topBase, 50 + Lock.Width, topBase + Lock.Height),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
Helper.DrawMultilineText(c, UnlockDescription, Size, Item.Margin, ETextSide.Left,
new SKRect(70 + Lock.Width, topBase + 10, Size - 50, 256), descriptionPaint, out topBase);
c.DrawBitmap(Unlock, new SKRect(50, topBase, 50 + Unlock.Width, topBase + Unlock.Height),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
Helper.DrawMultilineText(c, Description, Size, Item.Margin, ETextSide.Left,
new SKRect(70 + Unlock.Width, topBase + 10, Size - 50, 256), descriptionPaint, out topBase);
int h = Size - Item.Margin - topBase;
c.DrawBitmap(Item.IconImage ?? Item.FallbackImage, new SKRect(left - h / 2, topBase, left + h / 2, Size - Item.Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
c.DrawText(SItem, Size - (Item.Margin * 2.5f), Size - (Item.Margin * 2.5f), new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.BottomDefaultTypeface ?? Text.TypeFaces.DefaultTypeface,
TextSize = 15,
Color = SKColors.White,
TextAlign = SKTextAlign.Right,
});
}
}
}

View File

@ -1,96 +0,0 @@
using FModel.Creator.Texts;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Bases
{
public class BaseMapUIData
{
public SKBitmap Splash;
public SKBitmap VLogo;
public string DisplayName;
public string Description;
public string Coordinates;
public int Width = 1920;
public int Height = 1080;
public BaseMapUIData()
{
Splash = null;
VLogo = null;
DisplayName = "";
Description = "";
Coordinates = "";
}
public BaseMapUIData(IUExport export) : this()
{
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName) ?? "";
if (export.GetExport<TextProperty>("Description") is TextProperty description)
Description = Text.GetTextPropertyBase(description) ?? "";
if (export.GetExport<TextProperty>("Coordinates") is TextProperty coordinates)
Coordinates = Text.GetTextPropertyBase(coordinates) ?? "";
if (export.GetExport<ObjectProperty>("Splash") is ObjectProperty icon)
Splash = Utils.GetObjectTexture(icon);
VLogo = Utils.GetTexture("/Game/UI/Shared/Icons/Valorant_logo_cutout").Resize(48, 48);
if (Splash != null)
{
Width = Splash.Width;
Height = Splash.Height;
}
}
public void Draw(SKCanvas c)
{
int paddingLR = 80;
int paddingTB = 35;
int nameSize = 200;
int descriptionSize = 30;
using var namePaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = nameSize,
TextAlign = SKTextAlign.Left,
Color = SKColor.Parse("FFFBFA")
};
while (namePaint.MeasureText(DisplayName) > Width - (paddingLR * 2))
{
namePaint.TextSize = nameSize -= 2;
}
using var descriptionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DescriptionTypeface,
TextSize = descriptionSize,
TextAlign = SKTextAlign.Left,
Color = SKColor.Parse("FFFBFA")
};
while (descriptionPaint.MeasureText(Description) > Width - (paddingLR * 2))
{
descriptionPaint.TextSize = descriptionSize -= 2;
}
c.DrawBitmap(Splash, new SKRect(0, 0, Width, Height), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
c.DrawText(DisplayName.ToUpper(), paddingLR, paddingTB + namePaint.TextSize, namePaint);
c.DrawRect(new SKRect(paddingLR + 2.5f, paddingTB + 25 + namePaint.TextSize, paddingLR + 202.5f, paddingTB + 27.5f + namePaint.TextSize), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true, Color = SKColor.Parse("5AFFFBFA") });
c.DrawText(Description, paddingLR + 2.5f, paddingTB + 40 + namePaint.TextSize + descriptionPaint.TextSize, descriptionPaint);
descriptionPaint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
c.DrawText(Coordinates.ToUpper(), paddingLR, Height - paddingTB - descriptionPaint.TextSize, descriptionPaint);
if (VLogo != null)
{
c.DrawBitmap(VLogo, new SKRect(Width - VLogo.Width - paddingLR, paddingLR, Width - paddingLR, paddingLR + VLogo.Height), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
c.DrawRect(new SKRect(Width - VLogo.Width - paddingLR, paddingLR + VLogo.Height + 5, Width - paddingLR, paddingLR + VLogo.Height + 7.5f), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true, Color = SKColor.Parse("FFFBFA") });
}
}
}
}

View File

@ -1,93 +0,0 @@
using SkiaSharp;
using System;
using System.Windows;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{
public class BaseOffer
{
public SKBitmap FallbackImage;
public SKBitmap IconImage;
public SKColor[] RarityBackgroundColors;
public SKColor RarityBorderColor;
public int Size = 512;
public int Margin = 2;
public BaseOffer()
{
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream);
IconImage = FallbackImage;
RarityBackgroundColors = new SKColor[2] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
RarityBorderColor = SKColor.Parse("9092AB");
}
public BaseOffer(IUExport export) : this()
{
if (export.GetExport<StructProperty>("DetailsImage", "TileImage") is StructProperty typeImage)
{
if (typeImage.Value is UObject t && t.TryGetValue("ResourceObject", out var v) && v is ObjectProperty resourceObject)
{
IconImage = Utils.GetObjectTexture(resourceObject);
}
}
if (export.GetExport<StructProperty>("Gradient") is StructProperty gradient)
{
if (gradient.Value is UObject g &&
g.TryGetValue("Start", out var s1) && s1 is StructProperty t1 && t1.Value is FLinearColor start &&
g.TryGetValue("Stop", out var s2) && s2 is StructProperty t2 && t2.Value is FLinearColor stop)
{
RarityBackgroundColors = new SKColor[2] { SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex) };
}
}
if (export.GetExport<StructProperty>("Background") is StructProperty background)
{
if (background.Value is FLinearColor b)
{
RarityBorderColor = SKColor.Parse(b.Hex);
}
}
}
public void DrawBackground(SKCanvas c)
{
if (RarityBackgroundColors[0] == RarityBackgroundColors[1])
RarityBackgroundColors[0] = RarityBorderColor;
RarityBackgroundColors[0].ToHsl(out var _, out var _, out var l1);
RarityBackgroundColors[1].ToHsl(out var _, out var _, out var l2);
bool reverse = l1 > l2;
// border
c.DrawRect(new SKRect(0, 0, Size, Size),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = RarityBorderColor
});
c.DrawRect(new SKRect(Margin, Margin, Size - Margin, Size - Margin),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(Size / 2, Size / 2),
Size / 5 * 4,
new SKColor[2] { reverse ? RarityBackgroundColors[0] : RarityBackgroundColors[1], reverse ? RarityBackgroundColors[1] : RarityBackgroundColors[0] },
SKShaderTileMode.Clamp)
});
}
public void DrawImage(SKCanvas c)
{
c.DrawBitmap(IconImage ?? FallbackImage, new SKRect(Margin, Margin, Size - Margin, Size - Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
}
}
}

View File

@ -1,121 +0,0 @@
using SkiaSharp;
using System;
using System.Windows;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{
public class BaseOfferMaterial
{
public SKBitmap FallbackImage;
public SKBitmap IconImage;
public SKBitmap RarityBackgroundImage;
public SKColor[] RarityBackgroundColors;
public SKColor RarityBorderColor;
public int Size = 512;
public int Margin = 2;
public BaseOfferMaterial()
{
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream);
IconImage = null;
RarityBackgroundImage = null;
RarityBackgroundColors = new SKColor[2] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
RarityBorderColor = SKColor.Parse("9092AB");
}
public BaseOfferMaterial(IUExport export) : this()
{
if (export.GetExport<ArrayProperty>("VectorParameterValues") is ArrayProperty vectorParameterValues)
{
foreach (StructProperty vectorParameter in vectorParameterValues.Value)
{
if (vectorParameter.Value is UObject parameter &&
parameter.TryGetValue("ParameterValue", out var i) && i is StructProperty v && v.Value is FLinearColor value &&
parameter.TryGetValue("ParameterInfo", out var i1) && i1 is StructProperty i2 && i2.Value is UObject info &&
info.TryGetValue("Name", out var j1) && j1 is NameProperty name)
{
if (name.Value.String.Equals("Background_Color_A"))
{
RarityBackgroundColors[0] = SKColor.Parse(value.Hex);
RarityBorderColor = RarityBackgroundColors[0];
}
else if (name.Value.String.Equals("Background_Color_B"))
{
RarityBackgroundColors[1] = SKColor.Parse(value.Hex);
}
}
}
}
if (export.GetExport<ArrayProperty>("TextureParameterValues") is ArrayProperty textureParameterValues)
{
foreach (StructProperty textureParameter in textureParameterValues.Value)
{
if (textureParameter.Value is UObject parameter &&
parameter.TryGetValue("ParameterValue", out var i) && i is ObjectProperty value &&
parameter.TryGetValue("ParameterInfo", out var i1) && i1 is StructProperty i2 && i2.Value is UObject info &&
info.TryGetValue("Name", out var j1) && j1 is NameProperty name)
{
if (name.Value.String.Equals("SeriesTexture"))
{
RarityBackgroundImage = Utils.GetObjectTexture(value);
}
else if (IconImage == null && value.Value.Resource.OuterIndex.Resource != null && (name.Value.String.Equals("OfferImage") || name.Value.String.Contains("Texture")))
{
IconImage = Utils.GetObjectTexture(value);
if (IconImage == null) IconImage = Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_1");
if (IconImage == null) IconImage = Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_01");
}
}
}
}
if (IconImage == null)
IconImage = FallbackImage;
}
public void DrawBackground(SKCanvas c)
{
if (RarityBackgroundColors[0] == RarityBackgroundColors[1])
RarityBackgroundColors[0] = RarityBorderColor;
RarityBackgroundColors[0].ToHsl(out var _, out var _, out var l1);
RarityBackgroundColors[1].ToHsl(out var _, out var _, out var l2);
bool reverse = l1 > l2;
// border
c.DrawRect(new SKRect(0, 0, Size, Size),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = RarityBorderColor
});
c.DrawRect(new SKRect(Margin, Margin, Size - Margin, Size - Margin),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(Size / 2, Size / 2),
Size / 5 * 4,
new SKColor[2] { reverse ? RarityBackgroundColors[0] : RarityBackgroundColors[1], reverse ? RarityBackgroundColors[1] : RarityBackgroundColors[0] },
SKShaderTileMode.Clamp)
});
}
public void DrawImage(SKCanvas c)
{
if (RarityBackgroundImage != null)
c.DrawBitmap(RarityBackgroundImage, new SKRect(Margin, Margin, Size - Margin, Size - Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
c.DrawBitmap(IconImage ?? FallbackImage, new SKRect(Margin, Margin, Size - Margin, Size - Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
}
}
}

View File

@ -1,60 +0,0 @@
using System;
using System.Windows;
using FModel.Creator.Texts;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using FModel.Utils;
using Fortnite_API.Objects;
using Fortnite_API.Objects.V1;
using SkiaSharp;
namespace FModel.Creator.Bases
{
public class BasePlaylist : IBase
{
public SKBitmap FallbackImage { get; }
public SKBitmap IconImage { get; }
public SKColor[] RarityBackgroundColors { get; }
public SKColor[] RarityBorderColor { get; }
public string DisplayName { get; }
public string Description { get; }
public int Width { get; }
public int Height { get; }
public int Margin { get; } = 2;
public BasePlaylist(IUExport export)
{
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream);
IconImage = FallbackImage;
RarityBackgroundColors = new[] { SKColor.Parse("5EBC36"), SKColor.Parse("305C15") };
RarityBorderColor = new[] { SKColor.Parse("74EF52"), SKColor.Parse("74EF52") };
if (export.GetExport<TextProperty>("UIDisplayName", "DisplayName") is { } displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
if (export.GetExport<TextProperty>("UIDescription", "Description") is { } description)
Description = Text.GetTextPropertyBase(description);
Width = 1024;
Height = 512;
if (export.GetExport<NameProperty>("PlaylistName") is { } playlistName && !playlistName.Value.IsNone)
{
ApiResponse<PlaylistV1> playlist = Endpoints.FortniteAPIClient.V1.Playlists.Get(playlistName.Value.String);
if (playlist.IsSuccess && playlist.Data.Images.HasShowcase)
{
byte[] imageBytes = Endpoints.GetRawData(playlist.Data.Images.Showcase);
if (imageBytes != null)
{
IconImage = SKBitmap.Decode(imageBytes);
Width = IconImage.Width;
Height = IconImage.Height;
}
}
}
}
}
}

View File

@ -1,318 +0,0 @@
using FModel.Creator.Bundles;
using FModel.Creator.Texts;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
using System;
using System.Collections.Generic;
using System.Linq;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{
public class BaseSeason
{
public string DisplayName;
public string FolderName;
public string Watermark;
public Reward FirstWinReward;
public Dictionary<int, List<Reward>> BookXpSchedule;
public Header DisplayStyle;
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 BaseSeason()
{
DisplayName = "";
FolderName = "";
Watermark = Properties.Settings.Default.ChallengeBannerWatermark;
FirstWinReward = null;
BookXpSchedule = new Dictionary<int, List<Reward>>();
DisplayStyle = new Header();
}
public BaseSeason(IUExport export, string assetFolder) : this()
{
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
if (export.GetExport<StructProperty>("SeasonFirstWinRewards") is StructProperty s && s.Value is UObject seasonFirstWinRewards &&
seasonFirstWinRewards.GetExport<ArrayProperty>("Rewards") is ArrayProperty rewards)
{
foreach (StructProperty reward in rewards.Value)
{
if (reward.Value is UObject o &&
o.GetExport<SoftObjectProperty>("ItemDefinition") is SoftObjectProperty itemDefinition &&
o.GetExport<IntProperty>("Quantity") is IntProperty quantity)
{
FirstWinReward = new Reward(quantity, itemDefinition.Value);
}
}
}
if (export.GetExport<StructProperty>("BookXpScheduleFree") is StructProperty r2 && r2.Value is UObject bookXpScheduleFree &&
bookXpScheduleFree.GetExport<ArrayProperty>("Levels") is ArrayProperty levels2)
{
for (int i = 0; i < levels2.Value.Length; i++)
{
BookXpSchedule[i] = new List<Reward>(); // init list for all reward index and once
if (levels2.Value[i] is StructProperty level && level.Value is UObject l &&
l.GetExport<ArrayProperty>("Rewards") is ArrayProperty elRewards && elRewards.Value.Length > 0)
{
foreach (StructProperty reward in elRewards.Value)
{
if (reward.Value is UObject o &&
o.GetExport<SoftObjectProperty>("ItemDefinition") is SoftObjectProperty itemDefinition &&
!itemDefinition.Value.AssetPathName.String.Contains("/Items/Tokens/") &&
o.GetExport<IntProperty>("Quantity") is IntProperty quantity)
{
BookXpSchedule[i].Add(new Reward(quantity, itemDefinition.Value));
}
}
}
}
}
if (export.GetExport<StructProperty>("BookXpSchedulePaid") is StructProperty r1 && r1.Value is UObject bookXpSchedulePaid &&
bookXpSchedulePaid.GetExport<ArrayProperty>("Levels") is ArrayProperty levels1)
{
for (int i = 0; i < levels1.Value.Length; i++)
{
if (levels1.Value[i] is StructProperty level && level.Value is UObject l &&
l.GetExport<ArrayProperty>("Rewards") is ArrayProperty elRewards && elRewards.Value.Length > 0)
{
foreach (StructProperty reward in elRewards.Value)
{
if (reward.Value is UObject o &&
o.GetExport<SoftObjectProperty>("ItemDefinition") is SoftObjectProperty itemDefinition &&
o.GetExport<IntProperty>("Quantity") is IntProperty quantity)
{
BookXpSchedule[i].Add(new Reward(quantity, itemDefinition.Value));
}
}
}
}
}
FolderName = assetFolder;
AdditionalSize += 100 * (BookXpSchedule.Count / 10);
}
public void Draw(SKCanvas c)
{
DrawHeaderPaint(c);
DrawHeaderText(c);
using SKPaint paint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
TextSize = 15,
Color = SKColors.White,
TextAlign = SKTextAlign.Center,
Typeface = Text.TypeFaces.BottomDefaultTypeface ?? Text.TypeFaces.DisplayNameTypeface
};
using SKPaint bg = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = SKColor.Parse("#0F5CAF"),
};
using SKPaint rarity = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = SKColors.White,
};
using SKPaint icon = new SKPaint
{
FilterQuality = SKFilterQuality.High,
IsAntialias = true
};
int y = HeaderHeight + 50;
int defaultSize = 80;
int x = 20;
foreach (var (index, reward) in BookXpSchedule)
{
if (index == 0 || reward.Count == 0)
continue;
c.DrawText(index.ToString(), new SKPoint(x + (defaultSize / 2), y - 5), paint);
var theReward = reward[0].TheReward;
if (theReward == null)
continue;
rarity.Color = theReward.RarityBackgroundColors[0];
c.DrawRect(new SKRect(x, y, x + defaultSize, y + defaultSize), bg);
c.DrawBitmap(reward[0].RewardIcon, new SKPoint(x, y), icon);
var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
pathBottom.MoveTo(x, y + defaultSize);
pathBottom.LineTo(x, y + defaultSize - (defaultSize / 25 * 2.5f));
pathBottom.LineTo(x + defaultSize, y + defaultSize - (defaultSize / 25 * 4.5f));
pathBottom.LineTo(x + defaultSize, y + defaultSize);
pathBottom.Close();
c.DrawPath(pathBottom, rarity);
if (index != 1 && index % 10 == 0)
{
y += defaultSize + 20;
x = 20;
}
else
{
x += defaultSize + 20;
}
}
}
private void DrawHeaderText(SKCanvas c)
{
using SKPaint paint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.BundleDisplayNameTypeface,
TextSize = 50,
Color = SKColors.White,
TextAlign = SKTextAlign.Left,
};
string text = DisplayName.ToUpper();
int x = 300;
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
{
SKShaper shaper = new SKShaper(paint.Typeface);
float shapedTextWidth;
while (true)
{
SKShaper.Result shapedText = shaper.Shape(text, paint);
shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
if (shapedTextWidth > Width)
{
paint.TextSize -= 2;
}
else
{
break;
}
}
//only trigger the fix if The Last char is a digit
if (char.IsDigit(text[text.Length - 1]))
{
int s = text.Count(k => Char.IsDigit(k));
float numberwidth = paint.MeasureText(text.Substring(text.Length - s));
//Draw Number Separately
c.DrawShapedText(shaper, text.Substring(text.Length - s), x, 155, paint);
c.DrawShapedText(shaper, text.Substring(0, text.Length - s), x + numberwidth, 155, paint);
}
else
{
//feels bad man
c.DrawShapedText(shaper, text, x, 155, paint);
}
}
else
{
while (paint.MeasureText(text) > (Width - x))
{
paint.TextSize -= 2;
}
c.DrawText(text, x, 155, paint);
}
paint.Color = SKColors.White.WithAlpha(150);
paint.TextAlign = SKTextAlign.Right;
paint.TextSize = 23;
paint.Typeface = Text.TypeFaces.DefaultTypeface;
c.DrawText(Watermark
.Replace("{BundleName}", text)
.Replace("{Date}", DateTime.Now.ToString("dd/MM/yyyy")),
Width - 25, HeaderHeight - 40, paint);
paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
paint.Color = DisplayStyle.SecondaryColor;
paint.TextAlign = SKTextAlign.Left;
paint.TextSize = 30;
c.DrawText(FolderName.ToUpper(), x, 95, paint);
}
private void DrawHeaderPaint(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, HeaderHeight), new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = DisplayStyle.PrimaryColor
});
if (DisplayStyle.CustomBackground != null && DisplayStyle.CustomBackground.Height != DisplayStyle.CustomBackground.Width)
{
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(DisplayStyle.CustomBackground, new SKRect(0, 0, 1024, 256), bgPaint);
}
else if (DisplayStyle.DisplayImage != null)
{
if (DisplayStyle.CustomBackground != null && DisplayStyle.CustomBackground.Height == DisplayStyle.CustomBackground.Width)
c.DrawBitmap(DisplayStyle.CustomBackground, new SKRect(0, 0, HeaderHeight, HeaderHeight),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
BlendMode = SKBlendMode.Screen,
ImageFilter = SKImageFilter.CreateDropShadow(2.5F, 0, 20, 0, DisplayStyle.SecondaryColor.WithAlpha(25))
});
c.DrawBitmap(DisplayStyle.DisplayImage, new SKRect(0, 0, HeaderHeight, HeaderHeight),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
ImageFilter = SKImageFilter.CreateDropShadow(-2.5F, 0, 20, 0, DisplayStyle.SecondaryColor.WithAlpha(50))
});
}
if (FirstWinReward != null)
{
c.DrawBitmap(FirstWinReward.TheReward.IconImage.Resize(HeaderHeight, HeaderHeight), new SKPoint(0, 0), new SKPaint
{
FilterQuality = SKFilterQuality.High,
IsAntialias = true
});
}
SKPath pathTop = new SKPath { FillType = SKPathFillType.EvenOdd };
pathTop.MoveTo(0, HeaderHeight);
pathTop.LineTo(Width, HeaderHeight);
pathTop.LineTo(Width, HeaderHeight - 19);
pathTop.LineTo(Width / 2 + 7, HeaderHeight - 23);
pathTop.LineTo(Width / 2 + 13, HeaderHeight - 7);
pathTop.LineTo(0, HeaderHeight - 19);
pathTop.Close();
c.DrawPath(pathTop, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = DisplayStyle.SecondaryColor,
ImageFilter = SKImageFilter.CreateDropShadow(-5, -5, 0, 0, DisplayStyle.AccentColor.WithAlpha(75))
});
c.DrawRect(new SKRect(0, HeaderHeight, Width, HeaderHeight + AdditionalSize), new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = DisplayStyle.PrimaryColor.WithAlpha(200) // default background is black, so i'm kinda lowering the brightness here and that's what i want
});
}
}
}

View File

@ -1,177 +0,0 @@
using FModel.Creator.Stats;
using FModel.Creator.Texts;
using SkiaSharp;
using System.Collections.Generic;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{
public class BaseUIData
{
private readonly SKPaint descriptionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DescriptionTypeface,
TextSize = 19.5f,
Color = SKColor.Parse("939498"),
};
public SKBitmap IconImage;
public string DisplayName;
public string Description;
public List<Statistic> Abilities;
public int Width = 768; // keep it 512 (or a multiple of 512) if you don't want blurry icons
public int AdditionalWidth = 0;
public int Height = 96;
public int Margin = 3;
public BaseUIData()
{
IconImage = null;
DisplayName = "";
Description = "";
Abilities = new List<Statistic>();
}
public BaseUIData(IUExport[] exports, int baseIndex) : this()
{
if (exports[baseIndex].GetExport<TextProperty>("DisplayName") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName) ?? "";
if (exports[baseIndex].GetExport<TextProperty>("Description") is TextProperty description)
{
Description = Text.GetTextPropertyBase(description) ?? "";
if (Description.Equals(DisplayName)) Description = string.Empty;
if (!string.IsNullOrEmpty(Description))
{
Height += (int)descriptionPaint.TextSize * Helper.SplitLines(Description, descriptionPaint, Width - Margin).Count;
Height += (int)descriptionPaint.TextSize;
}
}
if (exports[baseIndex].GetExport<ObjectProperty>("StoreFeaturedImage", "FullRender", "VerticalPromoImage", "LargeIcon", "DisplayIcon2", "DisplayIcon") is ObjectProperty icon)
{
SKBitmap raw = Utils.GetObjectTexture(icon);
if (raw != null)
{
float coef = (float)Width / (float)raw.Width;
int sizeX = (int)(raw.Width * coef);
int sizeY = (int)(raw.Height * coef);
Height += sizeY;
IconImage = raw.Resize(sizeX, sizeY);
}
}
if (exports[baseIndex].GetExport<MapProperty>("Abilities") is MapProperty abilities)
{
AdditionalWidth = 768;
foreach (var (_, value) in abilities.Value)
{
if (value is ObjectProperty o && o.Value.Resource == null && o.Value.Index > 0)
{
Statistic s = new Statistic();
if (exports[o.Value.Index - 1].GetExport<TextProperty>("DisplayName") is TextProperty aDisplayName)
s.DisplayName = Text.GetTextPropertyBase(aDisplayName) ?? "";
if (exports[o.Value.Index - 1].GetExport<TextProperty>("Description") is TextProperty aDescription)
{
s.Description = Text.GetTextPropertyBase(aDescription) ?? "";
if (!string.IsNullOrEmpty(Description))
{
s.Height += (int)descriptionPaint.TextSize * Helper.SplitLines(s.Description, descriptionPaint, Width - Margin).Count;
s.Height += (int)descriptionPaint.TextSize * 3;
}
}
if (exports[o.Value.Index - 1].GetExport<ObjectProperty>("DisplayIcon") is ObjectProperty displayIcon)
{
SKBitmap raw = Utils.GetObjectTexture(displayIcon);
if (raw != null) s.Icon = raw.Resize(128, 128);
}
Abilities.Add(s);
}
}
}
}
public void Draw(SKCanvas c)
{
DrawCenteredTitle(c, DisplayName, 67.5f, out var textSize);
Helper.DrawMultilineText(c, Description, Width, Margin, ETextSide.Center,
new SKRect(Margin, textSize + 56.25f, Width - Margin, Height - 37.5f), descriptionPaint, out var yPos);
if (IconImage != null)
c.DrawBitmap(IconImage, new SKRect(0, yPos, Width, Height),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
int yaPos = 0;
foreach (Statistic ability in Abilities)
{
int xToAdd = ability.Icon != null ? ability.Icon.Width : 0;
textSize = 42.5f;
var 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(ability.DisplayName) > Width - 128)
{
namePaint.TextSize = textSize -= 2;
}
c.DrawText(ability.DisplayName, Width + Margin + xToAdd + 10, yaPos + Margin + textSize, namePaint);
Helper.DrawMultilineText(c, ability.Description, Width, Width + Margin + xToAdd + 10, ETextSide.Left,
new SKRect(Width + Margin + xToAdd + 10, textSize + yaPos + 27.5f, Width + AdditionalWidth - Margin, Height - 27.5f), descriptionPaint, out var _);
if (ability.Icon != null)
c.DrawBitmap(ability.Icon, new SKRect(Width + Margin, yaPos, Width + Margin + ability.Icon.Width, yaPos + ability.Icon.Height),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
yaPos += ability.Height + 48 + (Margin * 2);
}
}
private void DrawCenteredTitle(SKCanvas c, string title, float textSize, out float outTextSize)
{
SKPaint namePaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = textSize,
Color = SKColors.White,
TextAlign = SKTextAlign.Center,
};
float textWidth = namePaint.MeasureText(title);
while (textWidth > Width) // resize if too long
{
namePaint.TextSize = textSize -= 2;
textWidth = namePaint.MeasureText(title);
}
outTextSize = textSize;
float x1 = (Width / 2 - (textWidth / 2)) - 20;
float x2 = (x1 + textWidth) + 40;
float y1 = Margin + 5;
float y2 = Margin + namePaint.TextSize + 10;
c.DrawLine(new SKPoint(30, y1 + 5 + (namePaint.TextSize / 2)), new SKPoint(x1 - 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPaint { Color = SKColor.Parse("E2E8E6") });
c.DrawLine(new SKPoint(x2 + 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPoint(Width - 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPaint { Color = SKColor.Parse("E2E8E6") });
c.DrawLine(new SKPoint(x1, y1), new SKPoint(x2, y1), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // top
c.DrawLine(new SKPoint(x1, y2 + 5), new SKPoint(x2, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // bottom
c.DrawLine(new SKPoint(x1, y1), new SKPoint(x1, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // left
c.DrawLine(new SKPoint(x2, y1), new SKPoint(x2, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // right
c.DrawRect(new SKRect(x1 + 5, y1 + 5, x2 - 5, y2), new SKPaint { Color = SKColor.Parse("949598") });
c.DrawText(title, Width / 2, Margin + namePaint.TextSize, namePaint);
}
}
}

View File

@ -1,201 +0,0 @@
using FModel.Creator.Texts;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
using System.Collections.Generic;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{
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<Options> OptionValues = new List<Options>();
public int Width = 512;
public int Height = 128;
public int Margin = 32;
public BaseUserOption(IUExport export)
{
if (export.GetExport<TextProperty>("OptionDisplayName") is TextProperty optionDisplayName)
OptionDisplayName = Text.GetTextPropertyBase(optionDisplayName).ToUpperInvariant();
if (export.GetExport<TextProperty>("OptionDescription") is TextProperty optionDescription)
{
OptionDescription = Text.GetTextPropertyBase(optionDescription);
if (!string.IsNullOrEmpty(OptionDescription))
{
Height += (int)descriptionPaint.TextSize * Helper.SplitLines(OptionDescription, descriptionPaint, Width - Margin).Count;
Height += (int)descriptionPaint.TextSize;
}
}
if (export.GetExport<ArrayProperty>("OptionValues") is ArrayProperty optionValues)
{
OptionValues = new List<Options>(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<TextProperty>("OptionOnText") is TextProperty optionOnText)
OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOnText).ToUpperInvariant() });
if (export.GetExport<TextProperty>("OptionOffText") is TextProperty optionOffText)
OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOffText).ToUpperInvariant() });
if (export.GetExport<IntProperty>("Min", "DefaultValue") is IntProperty iMin &&
export.GetExport<IntProperty>("Max") is IntProperty iMax)
{
int increment = iMin.Value;
if (export.GetExport<IntProperty>("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<FloatProperty>("Min") is FloatProperty fMin &&
export.GetExport<FloatProperty>("Max") is FloatProperty fMax)
{
float increment = fMin.Value;
if (export.GetExport<FloatProperty>("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
};
SKPaint optionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = 20,
Color = SKColor.Parse("EEFFFF"),
TextAlign = SKTextAlign.Left
};
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
{
SKShaper shaper = new SKShaper(namePaint.Typeface);
float shapedTextWidth;
while (true)
{
SKShaper.Result shapedText = shaper.Shape(OptionDisplayName, namePaint);
shapedTextWidth = shapedText.Points[^1].X + namePaint.TextSize / 2f;
if (shapedTextWidth > (Width - (Margin * 2)))
{
namePaint.TextSize -= 2;
}
else
{
break;
}
}
c.DrawShapedText(shaper, OptionDisplayName, Margin, Margin + textSize, namePaint);
}
else
{
while (namePaint.MeasureText(OptionDisplayName) > (Width - (Margin * 2)))
{
namePaint.TextSize = textSize -= 2;
}
c.DrawText(OptionDisplayName, Margin, Margin + textSize, namePaint);
}
int y = (Margin + textSize) + ((int)descriptionPaint.TextSize + (Margin / 2));
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
});
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
{
SKShaper shaper = new SKShaper(optionPaint.Typeface);
SKShaper.Result shapedText = shaper.Shape(option.Option, optionPaint);
float shapedTextWidth = shapedText.Points[^1].X + optionPaint.TextSize / 2f;
c.DrawShapedText(shaper, option.Option, Margin + (space * 2), top + (20 * 1.1f), optionPaint);
}
else
{
c.DrawText(option.Option, Margin + (space * 2), top + (20 * 1.1f), optionPaint);
}
top += height + space;
}
}
}
}

View File

@ -0,0 +1,141 @@
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
{
public class BaseBundle : UCreator
{
private IList<BaseQuest> _quests;
private const int _headerHeight = 100;
public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
Height = _headerHeight;
Margin = 0;
}
public override void ParseForInfo()
{
_quests = new List<BaseQuest>();
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :)
{
foreach (var quest in quests)
{
if (!quest.TryGetValue(out FSoftObjectPath questDefinition, "QuestDefinition")) continue;
BaseQuest q;
var path = questDefinition.AssetPathName.Text;
do
{
if (!Utils.TryLoadObject(path, out UObject uObject)) break;
q = new BaseQuest(uObject, Style);
q.ParseForInfo();
_quests.Add(q);
path = path.SubstringBeforeWithLast('/') + q.NextQuestName + "." + q.NextQuestName;
} while (!string.IsNullOrEmpty(q.NextQuestName));
}
}
if (Object.TryGetValue(out FStructFallback[] completionRewards, "BundleCompletionRewards"))
{
foreach (var completionReward in completionRewards)
{
if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") ||
!completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue;
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out int quantity, "Quantity") ||
!reward.TryGetValue(out string templateId, "TemplateId") ||
!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue;
if (!itemDefinition.AssetPathName.IsNone &&
!itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") &&
!itemDefinition.AssetPathName.Text.Contains("/Items/Quests"))
{
_quests.Add(new BaseQuest(completionCount, itemDefinition, Style));
}
else if (!string.IsNullOrWhiteSpace(templateId))
{
_quests.Add(new BaseQuest(completionCount, quantity, templateId, Style));
}
}
}
}
Height += 256 * _quests.Count;
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
DrawDisplayName(c);
DrawQuests(c);
return SKImage.FromBitmap(ret);
}
private readonly SKPaint _headerPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
};
private void DrawHeader(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
var background = _quests.Count > 0 ? _quests[0].Background : Background;
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] {background[0].WithAlpha(50), background[1].WithAlpha(50)}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
new[] {SKColors.Black.WithAlpha(25), background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
_headerPaint.Shader = null;
_headerPaint.Color = SKColors.White;
while (_headerPaint.MeasureText(DisplayName) > Width)
{
_headerPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_headerPaint.Typeface);
var shapedText = shaper.Shape(DisplayName, _headerPaint);
c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint);
}
private void DrawQuests(SKCanvas c)
{
var y = _headerHeight;
foreach (var quest in _quests)
{
quest.DrawQuest(c, y);
y += quest.Height;
}
}
}
}

View File

@ -0,0 +1,305 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse.UE4.Versions;
using CUE4Parse_Fortnite.Enums;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
{
public class BaseCommunity : BaseIcon
{
private readonly CommunityDesign _design;
private string _rarityName;
private string _source;
private string _season;
private bool _lowerDrawn;
public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style)
{
Margin = 0;
_lowerDrawn = false;
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset == EEnabledDisabled.Enabled);
if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export))
_rarityName = export.Name;
else
_rarityName = GetRarityName(Object.GetOrDefault<FName>("Rarity"));
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
CosmeticSource = cosmeticItem.Name.ToUpper();
DisplayName = DisplayName.ToUpper();
Description = Description.ToUpper();
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
if (_design == null)
{
base.Draw(c);
}
else
{
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
if (_design.DrawSeason && _design.Fonts.TryGetValue("Season", out var font))
DrawToBottom(c, font, _season);
if (_design.DrawSource && _design.Fonts.TryGetValue("Source", out font))
DrawToBottom(c, font, _source);
DrawUserFacingFlags(c, _design.GameplayTags.DrawCustomOnly);
}
return SKImage.FromBitmap(ret);
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (_design == null) return;
if (_design.DrawSource)
{
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
_source = source.Text["Cosmetics.Source.".Length..].ToUpper();
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
_source = action.Text["Athena.ItemAction.".Length..].ToUpper();
}
if (_design.DrawSet && gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
Description += GetCosmeticSet(set.Text, _design.DrawSetShort);
if (_design.DrawSeason && gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
_season = GetCosmeticSeason(season.Text, _design.DrawSeasonShort);
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
}
private string GetCosmeticSet(string setName, bool bShort)
{
return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName);
}
private string GetCosmeticSeason(string seasonNumber, bool bShort)
{
if (!bShort) return base.GetCosmeticSeason(seasonNumber);
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
var number = int.Parse(s);
if (number == 10)
s = "X";
return number > 10 ? $"C{number / 10 + 1} S{s[^1..]}" : $"C1 S{s}";
}
private string GetRarityName(FName r)
{
var rarity = EFortRarity.Uncommon;
switch (r.Text)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
rarity = EFortRarity.Common;
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
rarity = EFortRarity.Rare;
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
rarity = EFortRarity.Epic;
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
rarity = EFortRarity.Legendary;
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
rarity = EFortRarity.Mythic;
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
rarity = EFortRarity.Unattainable;
break;
}
return rarity.GetDescription();
}
private new void DrawBackground(SKCanvas c)
{
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
{
c.DrawBitmap(rarity.Background, 0, 0, ImagePaint);
c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint);
}
else
{
base.DrawBackground(c);
}
}
private new void DrawTextBackground(SKCanvas c)
{
if (!_lowerDrawn && string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
_lowerDrawn = true;
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
{
c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint);
}
else
{
base.DrawTextBackground(c);
}
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font))
{
DisplayNamePaint.TextSize = font.FontSize;
DisplayNamePaint.TextScaleX = font.FontScale;
DisplayNamePaint.Color = font.FontColor;
DisplayNamePaint.TextSkewX = font.SkewValue;
DisplayNamePaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
DisplayNamePaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
var x = font.Alignment switch
{
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
_ => font.X
};
c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint);
}
else
{
base.DrawDisplayName(c);
}
}
private new void DrawDescription(SKCanvas c)
{
if (string.IsNullOrEmpty(Description)) return;
if (_design.Fonts.TryGetValue(nameof(Description), out var font))
{
DescriptionPaint.TextSize = font.FontSize;
DescriptionPaint.TextScaleX = font.FontScale;
DescriptionPaint.Color = font.FontColor;
DescriptionPaint.TextSkewX = font.SkewValue;
DescriptionPaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DescriptionPaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DescriptionPaint.Typeface = Utils.Typefaces.OnTheFly(path);
while (DescriptionPaint.MeasureText(Description) > Width - Margin * 2)
{
DescriptionPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
var shapedText = shaper.Shape(Description, DescriptionPaint);
var x = font.Alignment switch
{
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
_ => font.X
};
if (font.MaxLineCount < 2)
{
c.DrawShapedText(shaper, Description, x, font.Y, DescriptionPaint);
}
else
{
Utils.DrawMultilineText(c, Description, Width - Margin * 2, Margin, DescriptionPaint.TextAlign,
new SKRect(Margin, font.Y, Width - Margin, Height), DescriptionPaint, out _);
}
}
else
{
base.DrawDescription(c);
}
}
private void DrawToBottom(SKCanvas c, FontDesign font, string text)
{
if (string.IsNullOrEmpty(text)) return;
if (!_lowerDrawn)
{
_lowerDrawn = true;
DrawTextBackground(c);
}
DisplayNamePaint.TextSize = font.FontSize;
DisplayNamePaint.TextScaleX = font.FontScale;
DisplayNamePaint.Color = font.FontColor;
DisplayNamePaint.TextSkewX = font.SkewValue;
DisplayNamePaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var shapedText = shaper.Shape(text, DisplayNamePaint);
var x = font.Alignment switch
{
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
SKTextAlign.Right => font.X - DisplayNamePaint.MeasureText(text),
_ => font.X
};
c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint);
}
private void DrawUserFacingFlags(SKCanvas c, bool customOnly)
{
if (UserFacingFlags == null || UserFacingFlags.Length < 1) return;
if (customOnly)
{
c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint);
}
else
{
// add size to api
// draw
}
}
}
}

View File

@ -0,0 +1,297 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Conversion.Textures;
using CUE4Parse_Fortnite.Enums;
using FModel.Settings;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
{
public class BaseIcon : UCreator
{
public SKBitmap SeriesBackground { get; protected set; }
protected string ShortDescription { get; set; }
protected string CosmeticSource { get; set; }
protected SKBitmap[] UserFacingFlags { get; set; }
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public void ParseForReward(bool isUsingDisplayAsset)
{
// rarity
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
else GetRarity(Object.GetOrDefault<FName>("Rarity")); // default is uncommon
// preview
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
Preview = preview;
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
Preview = Utils.GetBitmap(itemDefinition);
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SidePanelIcon", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
Preview = Utils.GetBitmap(largePreview);
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
Preview = Utils.GetBitmap(s);
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
Preview = Utils.GetBitmap(otherPreview);
// text
if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription"))
Description = description.Text;
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
Description = string.Join('\n', descriptions.Select(x => x.Text));
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
ShortDescription = shortDescription.Text;
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
ShortDescription = "Wrap";
Description = Utils.RemoveHtmlTags(Description);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset == EEnabledDisabled.Enabled);
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
CosmeticSource = cosmeticItem.Name;
}
protected void Draw(SKCanvas c)
{
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
case EIconStyle.NoText:
DrawBackground(c);
DrawPreview(c);
DrawUserFacingFlags(c);
break;
default:
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
if (Description != ShortDescription)
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
DrawUserFacingFlags(c);
break;
}
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
Draw(c);
return SKImage.FromBitmap(ret);
}
private void GetSeries(FPackageIndex s)
{
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
GetSeries(export);
}
protected void GetSeries(UObject uObject)
{
if (uObject is UTexture2D texture2D)
{
SeriesBackground = SKBitmap.Decode(texture2D.Decode()?.Encode());
return;
}
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
{
SeriesBackground = Utils.GetBitmap(backgroundTexture);
}
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
colors.TryGetValue(out FLinearColor color1, "Color1") &&
colors.TryGetValue(out FLinearColor color2, "Color2") &&
colors.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)};
Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)};
}
if (uObject.Name.Equals("PlatformSeries") &&
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
{
foreach (var vectorParameter in material.VectorParameterValues)
{
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
continue;
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
}
}
}
private void GetRarity(FName r)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
var rarity = EFortRarity.Uncommon;
switch (r.Text)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
rarity = EFortRarity.Common;
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
rarity = EFortRarity.Rare;
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
rarity = EFortRarity.Epic;
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
rarity = EFortRarity.Legendary;
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
rarity = EFortRarity.Mythic;
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
rarity = EFortRarity.Unattainable;
break;
}
if (export.GetByIndex<FStructFallback>((int) rarity) is { } data &&
data.TryGetValue(out FLinearColor color1, "Color1") &&
data.TryGetValue(out FLinearColor color2, "Color2") &&
data.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)};
Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)};
}
}
protected string GetCosmeticSet(string setName)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
return string.Empty;
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
return string.Empty;
var name = string.Empty;
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
name = displayName.Text;
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
return string.Format(format, name);
}
protected string GetCosmeticSeason(string seasonNumber)
{
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
var number = int.Parse(s);
if (number == 10)
s = "X";
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
if (number <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
var d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..]));
return Utils.RemoveHtmlTags(string.Format(introduced, d));
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
CosmeticSource = action.Text["Athena.ItemAction.".Length..];
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
Description += GetCosmeticSet(set.Text);
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
Description += GetCosmeticSeason(season.Text);
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
}
protected void GetUserFacingFlags(IList<string> userFacingFlags)
{
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
return;
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
return;
UserFacingFlags = new SKBitmap[userFacingFlags.Count];
for (var i = 0; i < UserFacingFlags.Length; i++)
{
if (userFacingFlags[i].Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
{
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
else UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
}
else
{
foreach (var category in tertiaryCategories)
{
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(userFacingFlags[i], out _) &&
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
{
UserFacingFlags[i] = Utils.GetBitmap(texture);
}
}
}
}
}
private void DrawUserFacingFlags(SKCanvas c)
{
if (UserFacingFlags == null) return;
const int size = 25;
var x = Margin * (int) 2.5;
foreach (var flag in UserFacingFlags)
{
if (flag == null) continue;
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
x += size;
}
}
}
}

View File

@ -0,0 +1,319 @@
using System;
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Engine.Curves;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Fortnite.Enums;
using FModel.Extensions;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
{
public class BaseIconStats : BaseIcon
{
private readonly IList<IconStat> _statistics;
private const int _headerHeight = 128;
private bool _screenLayer;
public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
Height = _headerHeight;
Margin = 0;
_statistics = new List<IconStat>();
_screenLayer = uObject.ExportType.Equals("FortAccoladeItemDefinition", StringComparison.OrdinalIgnoreCase);
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
}
public override void ParseForInfo()
{
base.ParseForInfo();
DisplayName = DisplayName.ToUpperInvariant();
if (Object.TryGetValue(out FName accoladeType, "AccoladeType") &&
accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase))
{
_screenLayer = false;
}
if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") &&
Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) &&
uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
{
foreach (var location in poiLocations)
{
var locationName = "Unknown";
foreach (var poi in challengeMapPoiData)
{
if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") ||
!tagName.Text.Equals(location.Text, StringComparison.OrdinalIgnoreCase) || !poi.TryGetValue(out FText text, "Text")) continue;
locationName = text.Text;
break;
}
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "B0C091D7409B1657423C5F97E9CF4C77", "LOCATION NAME"), locationName.ToUpper()));
}
}
if (Object.TryGetValue(out FStructFallback maxStackSize, "MaxStackSize"))
{
if (maxStackSize.TryGetValue(out float v, "Value") && v > -1)
{
_statistics.Add(new IconStat("Max Stack", v));
}
else if (TryGetCurveTableStat(maxStackSize, out var s))
{
_statistics.Add(new IconStat("Max Stack", s));
}
}
if (Object.TryGetValue(out FStructFallback xpRewardAmount, "XpRewardAmount") && TryGetCurveTableStat(xpRewardAmount, out var x))
{
_statistics.Add(new IconStat("XP Amount", x));
}
if (Object.TryGetValue(out FStructFallback weaponStatHandle, "WeaponStatHandle") &&
weaponStatHandle.TryGetValue(out FName weaponRowName, "RowName") &&
weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") &&
dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue))
{
if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "BF7E3CF34A9ACFF52E95EAAD4F09F133", "Damage to Player"), dmgPb, 200));
}
if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50));
}
if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15));
}
if (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125));
}
if (weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime") && reloadTime != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 15));
}
if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) ||
Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) &&
weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") &&
weaponRowValue.TryGetValue(out FName durabilityRowName, "DurabilityRowName") &&
durabilityTable.TryGetDataTableRow(durabilityRowName.Text, StringComparison.OrdinalIgnoreCase, out var durability) &&
durability.TryGetValue(out int duraByRarity, GetRarityName(Object.GetOrDefault<FName>("Rarity"))))
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20));
}
}
if (!string.IsNullOrEmpty(Description))
Height += 40 + (int) _informationPaint.TextSize * Utils.SplitLines(Description, _informationPaint, Width - 20).Count;
Height += 50 * _statistics.Count;
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
DrawDisplayName(c);
DrawStatistics(c);
return SKImage.FromBitmap(ret);
}
private bool TryGetCurveTableStat(FStructFallback property, out float statValue)
{
if (property.TryGetValue(out FStructFallback curve, "Curve") &&
curve.TryGetValue(out FName rowName, "RowName") &&
curve.TryGetValue(out UCurveTable curveTable, "CurveTable") &&
curveTable.TryGetCurveTableRow(rowName.Text, StringComparison.OrdinalIgnoreCase, out var rowValue) &&
rowValue.TryGetValue(out FSimpleCurveKey[] keys, "Keys") && keys.Length > 0)
{
statValue = keys[0].KeyValue;
return true;
}
statValue = 0F;
return false;
}
private string GetRarityName(FName r)
{
var rarity = EFortRarity.Uncommon;
switch (r.Text)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
rarity = EFortRarity.Common;
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
rarity = EFortRarity.Rare;
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
rarity = EFortRarity.Epic;
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
rarity = EFortRarity.Legendary;
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
rarity = EFortRarity.Mythic;
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
rarity = EFortRarity.Unattainable;
break;
}
return rarity.GetDescription();
}
private readonly SKPaint _informationPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColor.Parse("#262630"), TextSize = 16,
Typeface = Utils.Typefaces.Description
};
private void DrawHeader(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] {Background[0].WithAlpha(180), Background[1].WithAlpha(220)}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(_headerHeight, 0, Width, _headerHeight), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] {SKColor.Parse("#262630"), SKColor.Parse("#1f1f26")}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, _headerHeight, Width, Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
using var rect = new SKPath {FillType = SKPathFillType.EvenOdd};
rect.MoveTo(0, 0);
rect.LineTo(_headerHeight + _headerHeight / 3, 0);
rect.LineTo(_headerHeight, _headerHeight);
rect.LineTo(0, _headerHeight);
rect.Close();
c.DrawPath(rect, _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(_headerHeight / 2, _headerHeight / 2), new SKPoint(_headerHeight / 2 + 100, _headerHeight / 2),
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
c.DrawPath(rect, _informationPaint);
_informationPaint.Shader = null;
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
c.DrawBitmap((Preview ?? DefaultPreview).Resize(_headerHeight), 0, 0, ImagePaint);
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
_informationPaint.TextSize = 50;
_informationPaint.Color = SKColors.White;
_informationPaint.Typeface = Utils.Typefaces.Bundle;
while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2)
{
_informationPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_informationPaint.Typeface);
shaper.Shape(DisplayName, _informationPaint);
c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2 + _informationPaint.TextSize / 3, _informationPaint);
}
private void DrawStatistics(SKCanvas c)
{
var outY = _headerHeight + 25f;
if (!string.IsNullOrEmpty(Description))
{
_informationPaint.TextSize = 16;
_informationPaint.Color = SKColors.White.WithAlpha(175);
_informationPaint.Typeface = Utils.Typefaces.Description;
Utils.DrawMultilineText(c, Description, Width - 40, 0, SKTextAlign.Center,
new SKRect(20, outY, Width - 20, Height), _informationPaint, out outY);
outY += 25;
}
foreach (var stat in _statistics)
{
stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY);
outY += 50;
}
}
}
public class IconStat
{
private readonly string _statName;
private readonly object _value;
private readonly float _maxValue;
public IconStat(string statName, object value, float maxValue = 0)
{
_statName = statName.ToUpperInvariant();
_value = value;
_maxValue = maxValue;
}
private readonly SKPaint _statPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
TextSize = 25, Typeface = Utils.Typefaces.DisplayName,
Color = SKColors.White
};
public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y)
{
while (_statPaint.MeasureText(_statName) > height * 2)
{
_statPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_statPaint.Typeface);
shaper.Shape(_statName, _statPaint);
c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint);
_statPaint.TextAlign = SKTextAlign.Right;
_statPaint.Typeface = Utils.Typefaces.BundleNumber;
_statPaint.Color = sliderColor;
var sliderRight = width - 100 - _statPaint.MeasureText(_value.ToString());
c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint);
_statPaint.Color = SKColors.White;
c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint);
if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return;
var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue);
c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint);
}
}
}

View File

@ -0,0 +1,100 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
{
public class BaseItemAccessToken : UCreator
{
private readonly SKBitmap _locked, _unlocked;
private string _unlockedDescription, _exportName;
private BaseIcon _icon;
public BaseItemAccessToken(UObject uObject, EIconStyle style) : base(uObject, style)
{
_unlocked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128.T-Icon-Unlocked-128").Resize(24);
_locked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.T-Icon-Lock-128").Resize(24);
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FPackageIndex accessItem, "access_item") &&
Utils.TryGetPackageIndexExport(accessItem, out UObject uObject))
{
_exportName = uObject.Name;
_icon = new BaseIcon(uObject, EIconStyle.Default);
_icon.ParseForReward(false);
}
if (Object.TryGetValue(out FText displayName, "DisplayName") && displayName.Text != "TBD")
DisplayName = displayName.Text;
else
DisplayName = _icon?.DisplayName;
Description = Object.TryGetValue(out FText description, "Description") ? description.Text : _icon?.Description;
if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text;
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
switch (Style)
{
case EIconStyle.NoBackground:
Preview = _icon.Preview;
DrawPreview(c);
break;
case EIconStyle.NoText:
Preview = _icon.Preview;
_icon.DrawBackground(c);
DrawPreview(c);
break;
default:
_icon.DrawBackground(c);
DrawInformation(c);
DrawToBottom(c, SKTextAlign.Right, _exportName);
break;
}
return SKImage.FromBitmap(ret);
}
private void DrawInformation(SKCanvas c)
{
var size = 45;
var left = Width / 2;
while (DisplayNamePaint.MeasureText(DisplayName) > Width - _icon.Margin * 2)
{
DisplayNamePaint.TextSize = size -= 2;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
c.DrawShapedText(shaper, DisplayName, left - shapedText.Points[^1].X / 2, _icon.Margin * 8 + size, DisplayNamePaint);
float topBase = _icon.Margin + size * 2;
if (!string.IsNullOrEmpty(_unlockedDescription))
{
c.DrawBitmap(_locked, new SKRect(50, topBase, 50 + _locked.Width, topBase + _locked.Height), ImagePaint);
Utils.DrawMultilineText(c, _unlockedDescription, Width, _icon.Margin, SKTextAlign.Left,
new SKRect(70 + _locked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
}
if (!string.IsNullOrEmpty(Description))
{
c.DrawBitmap(_unlocked, new SKRect(50, topBase, 50 + _unlocked.Width, topBase + _unlocked.Height), ImagePaint);
Utils.DrawMultilineText(c, Description, Width, _icon.Margin, SKTextAlign.Left,
new SKRect(70 + _unlocked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
}
var h = Width - _icon.Margin - topBase;
c.DrawBitmap(_icon.Preview ?? _icon.DefaultPreview, new SKRect(left - h / 2, topBase, left + h / 2, Width - _icon.Margin), ImagePaint);
}
}
}

View File

@ -0,0 +1,83 @@
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
{
public class BaseMaterialInstance : BaseIcon
{
public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style)
{
Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")};
Border = new[] {SKColor.Parse("9092AB")};
}
public override void ParseForInfo()
{
if (!(Object is UMaterialInstanceConstant material)) return;
foreach (var textureParameter in material.TextureParameterValues) // get texture from base material
{
if (!(textureParameter.ParameterValue is UTexture2D texture) || Preview != null) continue;
switch (textureParameter.ParameterInfo.Name.Text)
{
case "SeriesTexture":
GetSeries(texture);
break;
case "TextureA":
case "TextureB":
case "OfferImage":
Preview = Utils.GetBitmap(texture);
break;
}
}
while (material.VectorParameterValues.Length == 0 || // try to get color from parent if not found here
material.VectorParameterValues.All(x => x.ParameterInfo.Name.Text.Equals("FallOff_Color"))) // use parent if it only contains FallOff_Color
{
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
Utils.TryGetPackageIndexExport(parent, out material);
else return;
if (material == null) return;
}
foreach (var vectorParameter in material.VectorParameterValues)
{
if (vectorParameter.ParameterValue == null) continue;
switch (vectorParameter.ParameterInfo.Name.Text)
{
case "Background_Color_A":
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
Border[0] = Background[0];
break;
case "Background_Color_B": // Border color can be defaulted here in some case where Background_Color_A should be taken from parent but Background_Color_B from base
Background[1] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
break;
}
}
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
default:
DrawBackground(c);
DrawPreview(c);
break;
}
return SKImage.FromBitmap(ret);
}
}
}

View File

@ -0,0 +1,85 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
{
public class BaseMtxOffer : UCreator
{
public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style)
{
Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")};
Border = new[] {SKColor.Parse("9092AB")};
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FStructFallback typeImage, "DetailsImage", "TileImage") &&
typeImage.TryGetValue(out FPackageIndex resource, "ResourceObject"))
{
Preview = Utils.GetBitmap(resource);
}
if (Object.TryGetValue(out FStructFallback gradient, "Gradient") &&
gradient.TryGetValue(out FLinearColor start, "Start") &&
gradient.TryGetValue(out FLinearColor stop, "Stop"))
{
Background = new[] {SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex)};
}
if (Object.TryGetValue(out FLinearColor background, "Background"))
Border = new[] {SKColor.Parse(background.Hex)};
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText shortDescription, "ShortDescription"))
Description = shortDescription.Text;
if (Object.TryGetValue(out FStructFallback[] details, "DetailsAttributes"))
{
foreach (var detail in details)
{
if (detail.TryGetValue(out FText detailName, "Name"))
{
Description += $"\n- {detailName.Text.TrimEnd()}";
}
if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text))
{
Description += $" ({detailValue.Text})";
}
}
}
Description = Utils.RemoveHtmlTags(Description);
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
case EIconStyle.NoText:
DrawBackground(c);
DrawPreview(c);
break;
default:
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
break;
}
return SKImage.FromBitmap(ret);
}
}
}

View File

@ -0,0 +1,76 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Services;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
{
public class BasePlaylist : UCreator
{
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private SKBitmap _missionIcon;
public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style)
{
Margin = 0;
Width = 1024;
Height = 512;
Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Tiles/T_Athena_Tile_Matchmaking_Default.T_Athena_Tile_Matchmaking_Default");
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FText displayName, "UIDisplayName", "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "UIDescription", "Description"))
Description = description.Text;
if (Object.TryGetValue(out UTexture2D missionIcon, "MissionIcon"))
_missionIcon = Utils.GetBitmap(missionIcon).Resize(25);
if (!Object.TryGetValue(out FName playlistName, "PlaylistName") || string.IsNullOrWhiteSpace(playlistName.Text))
return;
var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text);
if (!playlist.IsSuccess || !playlist.Data.Images.HasShowcase ||
!_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image))
return;
Preview = Utils.GetBitmap(image).Resize(1024, 512); // Force size to 1024x512 to prevent huge previews.
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
case EIconStyle.NoText:
DrawPreview(c);
DrawMissionIcon(c);
break;
default:
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
DrawMissionIcon(c);
break;
}
return SKImage.FromBitmap(ret);
}
private void DrawMissionIcon(SKCanvas c)
{
if (_missionIcon == null) return;
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
}
}
}

View File

@ -0,0 +1,259 @@
using System;
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
{
public class BaseQuest : BaseIcon
{
private int _count;
private Reward _reward;
private readonly bool _screenLayer;
private readonly string[] _unauthorizedReward = {"Token", "ChallengeBundle", "GiftBox"};
public string NextQuestName { get; private set; }
public BaseQuest(UObject uObject, EIconStyle style) : base(uObject, style)
{
Margin = 0;
Width = 1024;
Height = 256;
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
if (uObject != null)
{
_screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase);
}
}
private BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion
{
var description = completionCount < 0 ?
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">all {0} challenges</> to earn the reward item") :
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">any {0} challenges</> to earn the reward item");
DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0);
}
public BaseQuest(int completionCount, FSoftObjectPath itemDefinition, EIconStyle style) : this(completionCount, style) // completion
{
_reward = Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject) ? new Reward(uObject) : new Reward();
}
public BaseQuest(int completionCount, int quantity, string reward, EIconStyle style) : this(completionCount, style) // completion
{
_reward = new Reward(quantity, reward);
}
public override void ParseForInfo()
{
ParseForReward(false);
if (Object.TryGetValue(out FStructFallback urgentQuestData, "UrgentQuestData"))
{
if (urgentQuestData.TryGetValue(out FText eventTitle, "EventTitle"))
DisplayName = eventTitle.Text;
if (urgentQuestData.TryGetValue(out FText eventDescription, "EventDescription"))
Description = eventDescription.Text;
if (urgentQuestData.TryGetValue(out FPackageIndex alertIcon, "AlertIcon", "BountyPriceImage"))
Preview = Utils.GetBitmap(alertIcon);
}
else
{
Description = ShortDescription;
if (Object.TryGetValue(out FText completionText, "CompletionText"))
Description += "\n" + completionText.Text;
if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "SidePanelIcon", "EntryListIcon", "ToastIcon"))
{
Preview = Utils.GetBitmap(tandemIcon);
}
}
if (Object.TryGetValue(out int objectiveCompletionCount, "ObjectiveCompletionCount"))
_count = objectiveCompletionCount;
if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0)
{
// actual description doesn't exist
if (string.IsNullOrEmpty(Description) && objectives[0].TryGetValue(out FText description, "Description"))
Description = description.Text;
// ObjectiveCompletionCount doesn't exist
if (_count == 0)
{
if (objectives[0].TryGetValue(out int count, "Count") && count > 1)
_count = count;
else
_count = objectives.Length;
}
}
if (Object.TryGetValue(out FStructFallback[] rewards, "Rewards"))
{
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out FStructFallback itemPrimaryAssetId, "ItemPrimaryAssetId") ||
!reward.TryGetValue(out int quantity, "Quantity")) continue;
if (!itemPrimaryAssetId.TryGetValue(out FStructFallback primaryAssetType, "PrimaryAssetType") ||
!itemPrimaryAssetId.TryGetValue(out FName primaryAssetName, "PrimaryAssetName") ||
!primaryAssetType.TryGetValue(out FName name, "Name")) continue;
if (name.Text.Equals("Quest", StringComparison.OrdinalIgnoreCase))
{
NextQuestName = primaryAssetName.Text;
}
else if (!_unauthorizedReward.Contains(name.Text))
{
_reward = new Reward(quantity, primaryAssetName);
}
}
}
if (_reward == null && Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
{
if (rewardsTable.TryGetDataTableRow("Default", StringComparison.InvariantCulture, out var row))
{
if (row.TryGetValue(out FName templateId, "TemplateId") &&
row.TryGetValue(out int quantity, "Quantity"))
{
_reward = new Reward(quantity, templateId);
}
}
}
if (_reward == null && Object.TryGetValue(out FStructFallback[] hiddenRewards, "HiddenRewards"))
{
foreach (var hiddenReward in hiddenRewards)
{
if (!hiddenReward.TryGetValue(out FName templateId, "TemplateId") ||
!hiddenReward.TryGetValue(out int quantity, "Quantity")) continue;
_reward = new Reward(quantity, templateId);
break;
}
}
_reward ??= new Reward();
}
public void DrawQuest(SKCanvas c, int y)
{
DrawBackground(c, y);
DrawPreview(c, y);
DrawTexts(c, y);
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawQuest(c, 0);
return SKImage.FromBitmap(ret);
}
private string ReformatString(string s, string completionCount, bool isAll)
{
s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "<text color=\"FFF\" case=\"upper\" fontface=\"black\">{0}</>");
var index = s.IndexOf("{0}|plural(", StringComparison.OrdinalIgnoreCase);
if (index > -1)
{
var p = s.Substring(index, s[index..].IndexOf(')') + 1);
s = s.Replace(p, string.Empty);
s = s.Insert(s.IndexOf("</>", StringComparison.OrdinalIgnoreCase), p.SubstringAfter("(").SubstringAfter("=").SubstringBefore(","));
}
var upper = s.SubstringAfter(">").SubstringBefore("</>");
return string.Format(Utils.RemoveHtmlTags(s.Replace(upper, upper.ToUpper())), isAll ? string.Empty : completionCount).Replace(" ", " ");
}
private readonly SKPaint _informationPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColor.Parse("#262630")
};
private void DrawBackground(SKCanvas c, int y)
{
c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4,
new[] {Background[0].WithAlpha(50), Background[1].WithAlpha(50)}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(Height / 2, y, Width, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, y + Height), new SKPoint(Width / 2, 75),
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, y + 75, Width, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(Margin, y, Height / 2, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Height / 2, y + Height / 2), new SKPoint(Height / 2 + 100, y + Height / 2),
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(Height / 2, y, Height / 2 + 100, y + Height), _informationPaint);
}
private void DrawPreview(SKCanvas c, int y)
{
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, Height - Margin, y + Height), ImagePaint);
}
private void DrawTexts(SKCanvas c, int y)
{
_informationPaint.Shader = null;
if (!string.IsNullOrWhiteSpace(DisplayName))
{
_informationPaint.TextSize = 40;
_informationPaint.Color = SKColors.White;
_informationPaint.Typeface = Utils.Typefaces.Bundle;
while (_informationPaint.MeasureText(DisplayName) > Width - Height - 10)
{
_informationPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_informationPaint.Typeface);
shaper.Shape(DisplayName, _informationPaint);
c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint);
}
var outY = y + 75f;
if (!string.IsNullOrWhiteSpace(Description))
{
_informationPaint.TextSize = 16;
_informationPaint.Color = SKColors.White.WithAlpha(175);
_informationPaint.Typeface = Utils.Typefaces.Description;
Utils.DrawMultilineText(c, Description, Width - Height, y, SKTextAlign.Left,
new SKRect(Height, outY, Width - 10, y + Height), _informationPaint, out outY);
}
_informationPaint.Color = Border[0].WithAlpha(100);
c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint);
if (_count > 0)
{
_informationPaint.TextSize = 25;
_informationPaint.Color = SKColors.White;
_informationPaint.Typeface = Utils.Typefaces.BundleNumber;
c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint);
_informationPaint.Color = Border[0];
c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _informationPaint);
}
_reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25));
}
}
}

View File

@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
{
public class BaseSeason : UCreator
{
private Reward _firstWinReward;
private Dictionary<int, List<Reward>> _bookXpSchedule;
private const int _headerHeight = 150;
// keep the list because rewards are ordered by least to most important
// we only care about the most but we also have filters so we can't just take the last reward
public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
Height = _headerHeight + 50;
Margin = 0;
}
public override void ParseForInfo()
{
_bookXpSchedule = new Dictionary<int, List<Reward>>();
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&
seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards"))
{
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
_firstWinReward = new Reward(uObject);
break;
}
}
var freeLevels = Array.Empty<FStructFallback>();
var paidLevels = Array.Empty<FStructFallback>();
if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData") &&
additionalSeasonData.Length > 0 && Utils.TryGetPackageIndexExport(additionalSeasonData[0], out UObject data) &&
data.TryGetValue(out FStructFallback battlePassXpScheduleFree, "BattlePassXpScheduleFree") &&
battlePassXpScheduleFree.TryGetValue(out freeLevels, "Levels") &&
data.TryGetValue(out FStructFallback battlePassXpSchedulePaid, "BattlePassXpSchedulePaid") &&
battlePassXpSchedulePaid.TryGetValue(out paidLevels, "Levels"))
{
// we got them boys
}
else if (Object.TryGetValue(out FStructFallback bookXpScheduleFree, "BookXpScheduleFree") &&
bookXpScheduleFree.TryGetValue(out freeLevels, "Levels") &&
Object.TryGetValue(out FStructFallback bookXpSchedulePaid, "BookXpSchedulePaid") &&
bookXpSchedulePaid.TryGetValue(out paidLevels, "Levels"))
{
// we got them boys
}
for (var i = 0; i < freeLevels.Length; i++)
{
_bookXpSchedule[i] = new List<Reward>();
if (!freeLevels[i].TryGetValue(out rewards, "Rewards")) continue;
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
_bookXpSchedule[i].Add(new Reward(uObject));
break;
}
}
for (var i = 0; i < paidLevels.Length; i++)
{
if (!paidLevels[i].TryGetValue(out rewards, "Rewards")) continue;
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
_bookXpSchedule[i].Add(new Reward(uObject));
break;
}
}
Height += 100 * _bookXpSchedule.Count / 10;
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
_firstWinReward?.DrawSeasonWin(c, _headerHeight);
DrawBookSchedule(c);
return SKImage.FromBitmap(ret);
}
private const int _DEFAULT_AREA_SIZE = 80;
private readonly SKPaint _headerPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
};
private readonly SKPaint _bookPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.BundleNumber,
Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 15
};
public void DrawHeader(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] {SKColors.SkyBlue.WithAlpha(50), SKColors.Blue.WithAlpha(50)}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
new[] {SKColors.Black.WithAlpha(25), SKColors.Blue.WithAlpha(0)}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
_headerPaint.Shader = null;
_headerPaint.Color = SKColors.White;
while (_headerPaint.MeasureText(DisplayName) > Width)
{
_headerPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_headerPaint.Typeface);
var shapedText = shaper.Shape(DisplayName, _headerPaint);
c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint);
}
private void DrawBookSchedule(SKCanvas c)
{
var x = 20;
var y = _headerHeight + 50;
foreach (var (index, reward) in _bookXpSchedule)
{
if (index == 0 || reward.Count == 0 || !reward[0].HasReward())
continue;
c.DrawText(index.ToString(), new SKPoint(x + _DEFAULT_AREA_SIZE / 2, y - 5), _bookPaint);
reward[0].DrawSeason(c, x, y, _DEFAULT_AREA_SIZE);
if (index != 1 && index % 10 == 0)
{
y += _DEFAULT_AREA_SIZE + 20;
x = 20;
}
else
{
x += _DEFAULT_AREA_SIZE + 20;
}
}
}
}
}

View File

@ -0,0 +1,27 @@
using CUE4Parse.UE4.Assets.Exports;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
{
public class BaseSeries : BaseIcon
{
public BaseSeries(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public override void ParseForInfo()
{
GetSeries(Object);
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
return SKImage.FromBitmap(ret);
}
}
}

View File

@ -0,0 +1,193 @@
using System.Collections.Generic;
using System.Globalization;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
{
public class BaseUserControl : UCreator
{
private List<Options> _optionValues = new();
private readonly SKPaint _displayNamePaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 45,
Color = SKColors.White, TextAlign = SKTextAlign.Left
};
private readonly SKPaint _descriptionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 25,
Color = SKColor.Parse("88DBFF"), TextAlign = SKTextAlign.Left
};
public BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 512;
Height = 128;
Margin = 32;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName"))
DisplayName = optionDisplayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FText optionDescription, "OptionDescription"))
{
Description = optionDescription.Text;
if (string.IsNullOrWhiteSpace(Description)) return;
Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count;
Height += (int) _descriptionPaint.TextSize;
}
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues"))
{
_optionValues = new List<Options>();
foreach (var option in optionValues)
{
if (option.TryGetValue(out FText displayName, "DisplayName"))
{
var opt = new Options {Option = displayName.Text.ToUpperInvariant()};
if (option.TryGetValue(out FLinearColor color, "Value"))
opt.Color = SKColor.Parse(color.Hex).WithAlpha(150);
_optionValues.Add(opt);
}
else if (option.TryGetValue(out FName primaryAssetName, "PrimaryAssetName"))
{
_optionValues.Add(new Options {Option = primaryAssetName.Text});
}
}
}
if (Object.TryGetValue(out FText optionOnText, "OptionOnText"))
_optionValues.Add(new Options {Option = optionOnText.Text});
if (Object.TryGetValue(out FText optionOffText, "OptionOffText"))
_optionValues.Add(new Options {Option = optionOffText.Text});
if (Object.TryGetValue(out int iMin, "Min", "DefaultValue") &&
Object.TryGetValue(out int iMax, "Max"))
{
var increment = iMin;
if (Object.TryGetValue(out int incrementValue, "IncrementValue"))
increment = incrementValue;
var format = "{0}";
if (Object.TryGetValue(out FText unitName, "UnitName"))
format = unitName.Text;
for (var i = iMin; i <= iMax; i += increment)
{
_optionValues.Add(new Options {Option = string.Format(format, i)});
}
}
if (Object.TryGetValue(out float fMin, "Min", "DefaultValue") &&
Object.TryGetValue(out float fMax, "Max"))
{
var increment = fMin;
if (Object.TryGetValue(out float incrementValue, "IncrementValue"))
increment = incrementValue;
var format = "{0}";
if (Object.TryGetValue(out FText unitName, "UnitName"))
format = unitName.Text;
for (var i = fMin; i <= fMax; i += increment)
{
_optionValues.Add(new Options {Option = string.Format(format, i)});
}
}
Height += Margin;
Height += 35 * _optionValues.Count;
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawInformation(c);
return SKImage.FromBitmap(ret);
}
private new void DrawBackground(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.Parse("01369C"), SKColor.Parse("1273C8")},
SKShaderTileMode.Clamp)
});
}
private void DrawInformation(SKCanvas c)
{
// display name
while (_displayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
_displayNamePaint.TextSize -= 2;
}
var shaper = new CustomSKShaper(_displayNamePaint.Typeface);
shaper.Shape(DisplayName, _displayNamePaint);
c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint);
#if DEBUG
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Margin + _displayNamePaint.TextSize), new SKPaint {Color = SKColors.Blue, IsStroke = true});
#endif
// description
float y = Margin;
if (!string.IsNullOrEmpty(DisplayName)) y += _displayNamePaint.TextSize;
if (!string.IsNullOrEmpty(Description)) y += _descriptionPaint.TextSize + Margin / 2F;
Utils.DrawMultilineText(c, Description, Width, Margin, SKTextAlign.Left,
new SKRect(Margin, y, Width - Margin, 256), _descriptionPaint, out var top);
// options
foreach (var option in _optionValues)
{
option.Draw(c, Margin, Width, ref top);
}
}
}
public class Options
{
private const int _SPACE = 5;
private const int _HEIGHT = 30;
private readonly SKPaint _optionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 20,
Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left
};
public string Option;
public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150);
public void Draw(SKCanvas c, int margin, int width, ref float top)
{
c.DrawRect(new SKRect(margin, top, width - margin, top + _HEIGHT), new SKPaint {IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Color});
c.DrawText(Option, margin + _SPACE * 2, top + 20 * 1.1f, _optionPaint);
top += _HEIGHT + _SPACE;
}
}
}

View File

@ -0,0 +1,153 @@
using System;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
{
public class Reward
{
private string _rewardQuantity;
private BaseIcon _theReward;
public bool HasReward() => _theReward != null;
public Reward()
{
_rewardQuantity = "x0";
}
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
{
}
public Reward(int quantity, string assetName) : this()
{
_rewardQuantity = $"x{quantity:###,###,###}".Trim();
if (assetName.Contains(':'))
{
var parts = assetName.Split(':');
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
{
if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p))
return;
_theReward = new BaseIcon(p, EIconStyle.Default);
_theReward.ParseForReward(false);
_theReward.Border[0] = SKColors.White;
_rewardQuantity = _theReward.DisplayName;
}
else GetReward(parts[1]);
}
else GetReward(assetName);
}
public Reward(UObject uObject)
{
_theReward = new BaseIcon(uObject, EIconStyle.Default);
_theReward.ParseForReward(false);
_theReward.Border[0] = SKColors.White;
_rewardQuantity = _theReward.DisplayName;
}
private readonly SKPaint _rewardPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High
};
public void DrawQuest(SKCanvas c, SKRect rect)
{
_rewardPaint.TextSize = 50;
if (HasReward())
{
c.DrawBitmap(_theReward.Preview.Resize((int) rect.Height), new SKPoint(rect.Left, rect.Top), _rewardPaint);
_rewardPaint.Color = _theReward.Border[0];
_rewardPaint.Typeface = _rewardQuantity.StartsWith("x") ? Utils.Typefaces.BundleNumber : Utils.Typefaces.Bundle;
_rewardPaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, _theReward.Background[0].WithAlpha(150));
while (_rewardPaint.MeasureText(_rewardQuantity) > rect.Width)
{
_rewardPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_rewardPaint.Typeface);
shaper.Shape(_rewardQuantity, _rewardPaint);
c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Height + 25, rect.MidY + 20, _rewardPaint);
}
else
{
_rewardPaint.Color = SKColors.White;
_rewardPaint.Typeface = Utils.Typefaces.BundleNumber;
c.DrawText("No Reward", new SKPoint(rect.Left, rect.MidY + 20), _rewardPaint);
}
}
public void DrawSeasonWin(SKCanvas c, int size)
{
if (!HasReward()) return;
c.DrawBitmap(_theReward.Preview.Resize(size), new SKPoint(0, 0), _rewardPaint);
}
public void DrawSeason(SKCanvas c, int x, int y, int areaSize)
{
if (!HasReward()) return;
// area + icon
_rewardPaint.Color = SKColor.Parse("#0F5CAF");
c.DrawRect(new SKRect(x, y, x + areaSize, y + areaSize), _rewardPaint);
c.DrawBitmap(_theReward.Preview.Resize(areaSize), new SKPoint(x, y), _rewardPaint);
// rarity color
_rewardPaint.Color = _theReward.Background[0];
var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd};
pathBottom.MoveTo(x, y + areaSize);
pathBottom.LineTo(x, y + areaSize - areaSize / 25 * 2.5f);
pathBottom.LineTo(x + areaSize, y + areaSize - areaSize / 25 * 4.5f);
pathBottom.LineTo(x + areaSize, y + areaSize);
pathBottom.Close();
c.DrawPath(pathBottom, _rewardPaint);
}
private void GetReward(string trigger)
{
switch (trigger.ToLower())
{
case "athenabattlestar":
_theReward = new BaseIcon(null, EIconStyle.Default);
_theReward.Border[0] = SKColor.Parse("FFDB67");
_theReward.Background[0] = SKColor.Parse("8F4A20");
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints.T-FNBR-BattlePoints");
break;
case "athenaseasonalxp":
_theReward = new BaseIcon(null, EIconStyle.Default);
_theReward.Border[0] = SKColor.Parse("E6FDB1");
_theReward.Background[0] = SKColor.Parse("51830F");
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium.T-FNBR-XPMedium");
break;
case "mtxgiveaway":
_theReward = new BaseIcon(null, EIconStyle.Default);
_theReward.Border[0] = SKColor.Parse("DCE6FF");
_theReward.Background[0] = SKColor.Parse("64A0AF");
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX");
break;
default:
{
var path = Utils.GetFullPath($"FortniteGame/Content/Athena/.*?/{trigger}.*"); // path has no objectname and its needed so we push the trigger again as the objectname
if (!string.IsNullOrWhiteSpace(path) && Utils.TryLoadObject(path.Replace("uasset", trigger), out UObject d))
{
_theReward = new BaseIcon(d, EIconStyle.Default);
_theReward.ParseForReward(false);
_theReward.Border[0] = SKColors.White;
_rewardQuantity = _theReward.DisplayName;
}
break;
}
}
}
}
}

View File

@ -1,17 +0,0 @@
using SkiaSharp;
namespace FModel.Creator.Bases
{
interface IBase
{
SKBitmap FallbackImage { get; }
SKBitmap IconImage { get; }
SKColor[] RarityBackgroundColors { get; }
SKColor[] RarityBorderColor { get; }
string DisplayName { get; }
string Description { get; }
int Width { get; }
int Height { get; }
int Margin { get; }
}
}

View File

@ -0,0 +1,48 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.SB
{
public class BaseDivision : UCreator
{
public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FPackageIndex icon, "Icon", "IconNoTier"))
{
Preview = Utils.GetBitmap(icon);
}
if (Object.TryGetValue(out FLinearColor lightColor, "UILightColor") &&
Object.TryGetValue(out FLinearColor mediumColor, "UIMediumColor") &&
Object.TryGetValue(out FLinearColor darkColor, "UIDarkColor") &&
Object.TryGetValue(out FLinearColor cardColor, "UICardColor"))
{
Background = new[] {SKColor.Parse(lightColor.Hex), SKColor.Parse(cardColor.Hex)};
Border = new[] {SKColor.Parse(mediumColor.Hex), SKColor.Parse(darkColor.Hex)};
}
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
return SKImage.FromBitmap(ret);
}
}
}

View File

@ -0,0 +1,55 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.SB
{
public class BaseLeague : UCreator
{
private int _promotionXp, _xpLostPerMatch;
public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style)
{
_promotionXp = 0;
_xpLostPerMatch = 0;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out int promotionXp, "PromotionXP"))
_promotionXp = promotionXp;
if (Object.TryGetValue(out int xpLostPerMatch, "XPLostPerMatch"))
_xpLostPerMatch = xpLostPerMatch;
if (Object.TryGetValue(out FPackageIndex division, "Division") &&
Utils.TryGetPackageIndexExport(division, out UObject div))
{
var d = new BaseDivision(div, Style);
d.ParseForInfo();
Preview = d.Preview;
Background = d.Background;
Border = d.Border;
}
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawToBottom(c, SKTextAlign.Left, $"PromotionXP: {_promotionXp}");
DrawToBottom(c, SKTextAlign.Right, $"XPLostPerMatch: {_xpLostPerMatch}");
return SKImage.FromBitmap(ret);
}
}
}

View File

@ -0,0 +1,98 @@
using System;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Creator.Bases.FN;
using SkiaSharp;
namespace FModel.Creator.Bases.SB
{
public class BaseSpellIcon : BaseIcon
{
private SKBitmap _seriesBackground2;
private readonly SKPaint _overlayPaint = new()
{
FilterQuality = SKFilterQuality.High,
IsAntialias = true,
Color = SKColors.Transparent.WithAlpha(75)
};
public BaseSpellIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
Background = new[] {SKColor.Parse("FFFFFF"), SKColor.Parse("636363")};
Border = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF")};
Width = Object.ExportType.StartsWith("BP_Cosmetic_Card") ? 1536 : 512;
Height = Object.ExportType.StartsWith("BP_Cosmetic_Card") ? 450 : 512;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FName rarity, "Rarity"))
GetRarity(rarity);
if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture"))
Preview = Utils.GetBitmap(preview);
else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture"))
Preview = Utils.GetBitmap(icon);
if (Object.TryGetValue(out FText displayName, "DisplayName", "Title"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description"))
Description = description.Text;
SeriesBackground = Utils.GetBitmap("g3/Content/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox");
_seriesBackground2 = Utils.GetBitmap("g3/Content/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT");
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackgrounds(c);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
return SKImage.FromBitmap(ret);
}
private void DrawBackgrounds(SKCanvas c)
{
if (SeriesBackground != null)
c.DrawBitmap(SeriesBackground, new SKRect(0, 0, Width, Height), ImagePaint);
if (_seriesBackground2 != null)
c.DrawBitmap(_seriesBackground2, new SKRect(0, 0, Width, Height), _overlayPaint);
var x = Margin * (int) 2.5;
const int radi = 15;
c.DrawCircle(x + radi, x + radi, radi, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(radi, radi), radi * 2 / 5 * 4,
Background, SKShaderTileMode.Clamp)
});
}
private void GetRarity(FName n)
{
if (!Utils.TryLoadObject("g3/Content/UI/UIKit/DT_RarityColors.DT_RarityColors", out UDataTable rarity)) return;
if (rarity.TryGetDataTableRow(n.Text["EXRarity::".Length..], StringComparison.Ordinal, out var row))
{
if (row.TryGetValue(out FLinearColor[] colors, "Colors"))
{
Background = new[] {SKColor.Parse(colors[0].Hex), SKColor.Parse(colors[2].Hex)};
Border = new[] {SKColor.Parse(colors[1].Hex), SKColor.Parse(colors[0].Hex)};
}
}
}
}
}

View File

@ -0,0 +1,230 @@
using System;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports;
using FModel.Creator.Bases.FN;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases
{
public abstract class UCreator
{
protected UObject Object { get; }
protected EIconStyle Style { get; }
public SKBitmap DefaultPreview { get; set; }
public SKBitmap Preview { get; set; }
public SKColor[] Background { get; protected set; }
public SKColor[] Border { get; protected set; }
public string DisplayName { get; protected set; }
public string Description { get; protected set; }
public int Margin { get; protected set; }
public int Width { get; protected set; }
public int Height { get; protected set; }
public abstract void ParseForInfo();
public abstract SKImage Draw();
protected UCreator(UObject uObject, EIconStyle style)
{
DefaultPreview = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream);
Background = new[] {SKColor.Parse("5BFD00"), SKColor.Parse("003700")};
Border = new[] {SKColor.Parse("1E8500"), SKColor.Parse("5BFD00")};
DisplayName = string.Empty;
Description = string.Empty;
Width = 512;
Height = 512;
Margin = 2;
Object = uObject;
Style = style;
}
private const int _STARTER_TEXT_POSITION = 380, _NAME_TEXT_SIZE = 45, _BOTTOM_TEXT_SIZE = 15;
protected readonly SKPaint DisplayNamePaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = _NAME_TEXT_SIZE,
Color = SKColors.White, TextAlign = SKTextAlign.Center
};
protected readonly SKPaint DescriptionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Description, TextSize = 13,
Color = SKColors.White
};
protected readonly SKPaint ImagePaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High
};
private readonly SKPaint _textBackgroundPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = new SKColor(0, 0, 50, 75)
};
private readonly SKPaint _shortDescriptionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColors.White
};
public void DrawBackground(SKCanvas c)
{
// reverse doesn't affect basic rarities
if (Background[0] == Background[1]) Background[0] = Border[0];
Background[0].ToHsl(out _, out _, out var l1);
Background[1].ToHsl(out _, out _, out var l2);
var reverse = l1 > l2;
// border
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), Border, SKShaderTileMode.Clamp)
});
if (this is BaseIcon {SeriesBackground: { }} baseIcon)
c.DrawBitmap(baseIcon.SeriesBackground, new SKRect(baseIcon.Margin, baseIcon.Margin, baseIcon.Width - baseIcon.Margin,
baseIcon.Height - baseIcon.Margin), ImagePaint);
else
{
switch (Style)
{
case EIconStyle.Flat:
{
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4),
new[] {Background[reverse ? 0 : 1].WithAlpha(150), Border[0]}, SKShaderTileMode.Clamp)
});
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
var pathTop = new SKPath {FillType = SKPathFillType.EvenOdd};
pathTop.MoveTo(Margin, Margin);
pathTop.LineTo(Margin + Width / 17 * 10, Margin);
pathTop.LineTo(Margin, Margin + Height / 17);
pathTop.Close();
c.DrawPath(pathTop, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = Background[1].WithAlpha(75)
});
break;
}
default:
{
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, Height / 2), Width / 5 * 4,
new[] {Background[reverse ? 0 : 1], Background[reverse ? 1 : 0]},
SKShaderTileMode.Clamp)
});
break;
}
}
}
}
protected void DrawPreview(SKCanvas c)
=> c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, Margin, Width - Margin, Height - Margin), ImagePaint);
protected void DrawTextBackground(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
switch (Style)
{
case EIconStyle.Flat:
{
var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd};
pathBottom.MoveTo(Margin, Height - Margin);
pathBottom.LineTo(Margin, Height - Margin - Height / 17 * 2.5f);
pathBottom.LineTo(Width - Margin, Height - Margin - Height / 17 * 4.5f);
pathBottom.LineTo(Width - Margin, Height - Margin);
pathBottom.Close();
c.DrawPath(pathBottom, _textBackgroundPaint);
break;
}
default:
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, Height - Margin), _textBackgroundPaint);
break;
}
}
protected void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName)) return;
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
DisplayNamePaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
var x = (Width - shapedText.Points[^1].X) / 2;
var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
switch (Style)
{
case EIconStyle.Flat:
{
DisplayNamePaint.TextAlign = SKTextAlign.Right;
x = Width - Margin * 2 - shapedText.Points[^1].X;
break;
}
}
#if DEBUG
c.DrawLine(x, 0, x, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true});
c.DrawLine(x + shapedText.Points[^1].X, 0, x + shapedText.Points[^1].X, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true});
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, y), new SKPaint {Color = SKColors.Blue, IsStroke = true});
#endif
c.DrawShapedText(shaper, DisplayName, x, y, DisplayNamePaint);
}
protected void DrawDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(Description)) return;
var maxLine = string.IsNullOrEmpty(DisplayName) ? 8 : 4;
var side = SKTextAlign.Center;
switch (Style)
{
case EIconStyle.Flat:
side = SKTextAlign.Right;
break;
}
Utils.DrawCenteredMultilineText(c, Description, maxLine, Width, Margin, side,
new SKRect(Margin, string.IsNullOrEmpty(DisplayName) ? _STARTER_TEXT_POSITION : _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, Width - Margin, Height - _BOTTOM_TEXT_SIZE), DescriptionPaint);
}
protected void DrawToBottom(SKCanvas c, SKTextAlign side, string text)
{
if (string.IsNullOrEmpty(text)) return;
_shortDescriptionPaint.TextAlign = side;
_shortDescriptionPaint.TextSize = Utils.Typefaces.Bottom == null ? 15 : 13;
switch (side)
{
case SKTextAlign.Left:
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface);
shaper.Shape(text, _shortDescriptionPaint);
c.DrawShapedText(shaper, text, Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
break;
case SKTextAlign.Right:
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.Default;
c.DrawText(text, Width - Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
break;
}
}
}
}

View File

@ -1,60 +0,0 @@
using FModel.Utils;
using System;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bundles
{
public class CompletionReward
{
private const string _TRIGGER1 = "<text color=\"FFF\" case=\"upper\" fontface=\"black\">";
private const string _TRIGGER2 = "</>";
public string CompletionText;
public Reward Reward;
public CompletionReward(IntProperty completionCount)
{
string all = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">all {0} challenges</> to earn the reward item");
string allFormated = ReformatString(all, completionCount.Value.ToString(), true);
string any = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">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}", "<text color=\"FFF\" case=\"upper\" fontface=\"black\">{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(" ,", ",");
}
}
}

View File

@ -1,106 +0,0 @@
using SkiaSharp;
using System;
using System.IO;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
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") && 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") && 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);
}
}
}
}

View File

@ -1,139 +0,0 @@
using FModel.Creator.Bases;
using FModel.Creator.Texts;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
using System;
using System.Linq;
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))
});
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))
});
}
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))
});
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;
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
{
SKShaper shaper = new SKShaper(paint.Typeface);
float shapedTextWidth;
while (true)
{
SKShaper.Result shapedText = shaper.Shape(text, paint);
shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
if (shapedTextWidth > (icon.Width - x))
{
paint.TextSize -= 1;
}
else
{
break;
}
}
if (char.IsDigit(text[text.Length - 1]))
{
int s = text.Count(k => Char.IsDigit(k));
float numberwidth = paint.MeasureText(text.Substring(text.Length - s));
c.DrawShapedText(shaper, text.Substring(text.Length - s), x, 155, paint);
c.DrawShapedText(shaper, text.Substring(0, text.Length - s), x + numberwidth, 155, paint);
}
else
{
//feels bad man
c.DrawShapedText(shaper, text, x, 155, paint);
}
}
else
{
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;
paint.Typeface = Text.TypeFaces.DefaultTypeface;
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);
}
}
}

View File

@ -1,94 +0,0 @@
using FModel.Creator.Texts;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.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("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("RewardsTable", out var v4) && v4 is ObjectProperty rewardsTable && rewardsTable.Value.Resource.OuterIndex.Resource != null)
{
Package p = Utils.GetPropertyPakPackage(rewardsTable.Value.Resource.OuterIndex.Resource?.ObjectName.String);
if (p != null && p.HasExport() && !p.Equals(default))
{
var u = p.GetExport<UDataTable>();
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("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;
}
}
}
}
}
}

View File

@ -1,241 +0,0 @@
using FModel.Creator.Bases;
using FModel.Creator.Texts;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
using System;
using System.Linq;
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;
if (!string.IsNullOrEmpty(q.Description))
{
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
{
SKShaper shaper = new SKShaper(paint.Typeface);
float shapedTextWidth;
while (true)
{
SKShaper.Result shapedText = shaper.Shape(q.Description, paint);
shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
if (shapedTextWidth > icon.Width - 65 - 165)
{
paint.TextSize -= 1;
}
else
{
break;
}
}
if (char.IsDigit(q.Description[q.Description.Length - 1]))
{
int s = q.Description.Count(k => Char.IsDigit(k));
c.DrawShapedText(shaper, q.Description.Substring(q.Description.Length - s), 65, y + paint.TextSize + 11, paint);
c.DrawShapedText(shaper, q.Description.Substring(0, q.Description.Length - s), 115, y + paint.TextSize + 11, paint);
}
else
{
c.DrawShapedText(shaper, q.Description, 65, y + paint.TextSize + 11, paint);
}
}
else
{
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));
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;
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
{
SKShaper shaper = new SKShaper(paint.Typeface);
float shapedTextWidth;
while (true)
{
SKShaper.Result shapedText = shaper.Shape(r.CompletionText, paint);
shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
if (shapedTextWidth > icon.Width - 65 - 165)
{
paint.TextSize -= 1;
}
else
{
break;
}
}
c.DrawShapedText(shaper, r.CompletionText, 65, y + paint.TextSize + 15, paint);
}
else
{
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));
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);
}
}
}
}

View File

@ -1,155 +0,0 @@
using FModel.Creator.Bases;
using SkiaSharp;
using System;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bundles
{
public class Reward
{
public int RewardQuantity;
public SKBitmap RewardIcon;
public BaseIcon TheReward;
public string RewardFillColor;
public string RewardBorderColor;
public bool IsCountShifted;
public Reward()
{
RewardQuantity = 0;
RewardIcon = null;
TheReward = 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))
{
Package p = Utils.GetPropertyPakPackage($"/Game/Items/BannerIcons/{parts[1]}.{parts[1]}");
if (p.HasExport() && !p.Equals(default))
{
if (p.GetExport<UObject>() is UObject banner)
{
TheReward = new BaseIcon(banner, $"{parts[1]}.uasset", false);
RewardIcon = TheReward.IconImage.Resize(64, 64);
}
}
}
else GetReward(parts[1]);
}
else GetReward(assetName);
}
public Reward(IntProperty quantity, FSoftObjectPath itemFullPath)
{
RewardQuantity = quantity.Value;
Package p = Utils.GetPropertyPakPackage(itemFullPath.AssetPathName.String);
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();
if (d != null)
{
int i = itemFullPath.AssetPathName.String.LastIndexOf('/');
TheReward = new BaseIcon(d, itemFullPath.AssetPathName.String[(i > 0 ? i : 0)..] + ".uasset", false);
RewardIcon = TheReward.IconImage.Resize(80, 80);
}
}
}
public Reward(IntProperty quantity, SoftObjectProperty itemDefinition) : this()
{
RewardQuantity = quantity.Value;
Package p = Utils.GetPropertyPakPackage(itemDefinition.Value.AssetPathName.String);
if (p != null && p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();
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;
TheReward = new BaseIcon(d, itemDefinition.Value.AssetPathName.String.Substring(s1, s2) + ".uasset", false);
RewardIcon = TheReward.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");
Package p = Utils.GetPropertyPakPackage(path);
if (p!= null && p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();
if (d != null)
{
int i = path.LastIndexOf('/');
IsCountShifted = false;
TheReward = new BaseIcon(d, path[(i > 0 ? i : 0)..] + ".uasset", false);
RewardIcon = TheReward.IconImage.Resize(64, 64);
}
}
break;
}
}
}
}
}

View File

@ -1,496 +0,0 @@
using FModel.Creator.Bases;
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 SkiaSharp;
using System.IO;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
namespace FModel.Creator
{
static class Creator
{
/// <summary>
/// i don't cache images because i don't wanna store a lot of SKCanvas in the memory
/// </summary>
/// <returns>true if an icon has been drawn</returns>
public static bool TryDrawIcon(string assetPath, FName[] exportTypes, IUExport[] exports)
{
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
int index;
{
if (Globals.Game.ActualGame == EGame.Valorant || Globals.Game.ActualGame == EGame.Spellbreak)
index = 1;
else
index = 0;
}
string exportType;
{
if (exportTypes.Length > index && (exportTypes[index].String == "BlueprintGeneratedClass" || exportTypes[index].String == "FortWeaponAdditionalData_AudioVisualizerData" || exportTypes[index].String == "FortWeaponAdditionalData_SingleWieldState"))
index++;
exportType = exportTypes.Length > index ? exportTypes[index].String : string.Empty;
}
switch (exportType)
{
case "AthenaConsumableEmoteItemDefinition":
case "AthenaSkyDiveContrailItemDefinition":
case "AthenaLoadingScreenItemDefinition":
case "AthenaVictoryPoseItemDefinition":
case "AthenaPetCarrierItemDefinition":
case "AthenaMusicPackItemDefinition":
case "AthenaBattleBusItemDefinition":
case "AthenaCharacterItemDefinition":
case "FortAlterationItemDefinition":
case "AthenaBackpackItemDefinition":
case "AthenaPickaxeItemDefinition":
case "AthenaGadgetItemDefinition":
case "AthenaGliderItemDefinition":
case "AthenaDailyQuestDefinition":
case "FortBackpackItemDefinition":
case "AthenaSprayItemDefinition":
case "AthenaDanceItemDefinition":
case "AthenaEmojiItemDefinition":
case "AthenaItemWrapDefinition":
case "AthenaToyItemDefinition":
case "FortHeroType":
case "FortTokenType":
case "FortAbilityKit":
case "FortWorkerType":
case "RewardGraphToken":
case "FortBannerTokenType":
case "FortVariantTokenType":
case "FortDecoItemDefinition":
case "FortFeatItemDefinition":
case "FortStatItemDefinition":
case "FortTrapItemDefinition":
case "FortAmmoItemDefinition":
case "FortTandemCharacterData":
case "FortQuestItemDefinition":
case "FortBadgeItemDefinition":
case "FortAwardItemDefinition":
case "FortGadgetItemDefinition":
case "FortPlaysetItemDefinition":
case "FortGiftBoxItemDefinition":
case "FortSpyTechItemDefinition":
case "FortOutpostItemDefinition":
case "FortAccoladeItemDefinition":
case "FortCardPackItemDefinition":
case "FortDefenderItemDefinition":
case "FortCurrencyItemDefinition":
case "FortResourceItemDefinition":
case "FortCodeTokenItemDefinition":
case "FortSchematicItemDefinition":
case "FortExpeditionItemDefinition":
case "FortIngredientItemDefinition":
case "FortAccountBuffItemDefinition":
case "FortWeaponMeleeItemDefinition":
case "FortContextTrapItemDefinition":
case "FortPlayerPerksItemDefinition":
case "FortPlaysetPropItemDefinition":
case "FortHomebaseNodeItemDefinition":
case "FortWeaponRangedItemDefinition":
case "FortNeverPersistItemDefinition":
case "RadioContentSourceItemDefinition":
case "FortPlaysetGrenadeItemDefinition":
case "FortPersonalVehicleItemDefinition":
case "FortGameplayModifierItemDefinition":
case "FortHardcoreModifierItemDefinition":
case "FortConsumableAccountItemDefinition":
case "FortConversionControlItemDefinition":
case "FortAccountBuffCreditItemDefinition":
case "FortEventCurrencyItemDefinitionRedir":
case "FortPersistentResourceItemDefinition":
case "FortHomebaseBannerIconItemDefinition":
case "FortCampaignHeroLoadoutItemDefinition":
case "FortConditionalResourceItemDefinition":
case "FortChallengeBundleScheduleDefinition":
case "FortWeaponMeleeDualWieldItemDefinition":
case "FortDailyRewardScheduleTokenDefinition":
case "FortCreativeRealEstatePlotItemDefinition":
{
BaseIcon icon = new BaseIcon(exports[index], exportType, ref assetName);
int height = icon.Size + icon.AdditionalSize;
using (var ret = new SKBitmap(icon.Size, height, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
Rarity.DrawRarity(c, icon);
}
LargeSmallImage.DrawPreviewImage(c, icon);
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
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 "FortPlaylistAthena":
{
BasePlaylist icon = new BasePlaylist(exports[index]);
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
Rarity.DrawRarity(c, icon);
}
LargeSmallImage.DrawNotStretchedPreviewImage(c, icon);
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText)
{
Text.DrawBackground(c, icon);
Text.DrawDisplayName(c, icon);
Text.DrawDescription(c, icon);
}
}
// Watermark.DrawWatermark(c); // boi why would you watermark something you don't own ¯\_(ツ)_/¯
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "AthenaSeasonItemDefinition":
{
BaseSeason icon = new BaseSeason(exports[index], assetFolder);
using (var ret = new SKBitmap(icon.Width, icon.HeaderHeight + icon.AdditionalSize,
SKColorType.Rgba8888, SKAlphaType.Opaque))
using (var c = new SKCanvas(ret))
{
icon.Draw(c);
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "FortMtxOfferData":
{
BaseOffer icon = new BaseOffer(exports[index]);
using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
icon.DrawBackground(c);
}
icon.DrawImage(c);
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "MaterialInstanceConstant":
{
if (assetFolder.Equals("MI_OfferImages") || assetFolder.Equals("RenderSwitch_Materials"))
{
BaseOfferMaterial icon = new BaseOfferMaterial(exports[index]);
using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
icon.DrawBackground(c);
}
icon.DrawImage(c);
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
return false;
}
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, exports[index]);
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(exports[index]);
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(exports[index], 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;
}
case "FortItemAccessTokenType":
{
BaseItemAccess icon = new BaseItemAccess(exports[index]);
using (var ret = new SKBitmap(icon.Size, icon.Size, 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 "MapUIData":
{
BaseMapUIData icon = new BaseMapUIData(exports[index]);
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
icon.Draw(c);
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "ArmorUIData":
case "SprayUIData":
case "ThemeUIData":
case "ContractUIData":
case "CurrencyUIData":
case "GameModeUIData":
case "CharacterUIData":
case "SprayLevelUIData":
case "EquippableUIData":
case "PlayerCardUIData":
case "Gun_UIData_Base_C":
case "CharacterRoleUIData":
case "EquippableSkinUIData":
case "EquippableCharmUIData":
case "EquippableSkinLevelUIData":
case "EquippableSkinChromaUIData":
case "EquippableCharmLevelUIData":
{
BaseUIData icon = new BaseUIData(exports, index);
using (var ret = new SKBitmap(icon.Width + icon.AdditionalWidth, icon.Height, SKColorType.Rgba8888,
SKAlphaType.Premul))
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 "StreamedVideoDataAsset": // must find a way to automatically gets the right version in the url
// {
// if (Globals.Game.ActualGame == EGame.Valorant && exports[index].GetExport<StructProperty>("Uuid") is StructProperty s && s.Value is FGuid uuid)
// {
// Process.Start(new ProcessStartInfo
// {
// FileName = string.Format(
// "http://valorant.dyn.riotcdn.net/x/videos/release-01.05/{0}_default_universal.mp4",
// $"{uuid.A:x8}-{uuid.B >> 16:x4}-{uuid.B & 0xFFFF:x4}-{uuid.C >> 16:x4}-{uuid.C & 0xFFFF:x4}{uuid.D:x8}"),
// UseShellExecute = true
// });
// }
// return false;
// }
case "GQuest":
case "GAccolade":
case "GCosmeticSkin":
case "GCharacterPerk":
case "GCosmeticTitle":
case "GCosmeticBadge":
case "GCosmeticEmote":
case "GCosmeticTriumph":
case "GCosmeticRunTrail":
case "GCosmeticArtifact":
case "GCosmeticDropTrail":
{
BaseGCosmetic icon = new BaseGCosmetic(exports[index], exportType);
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign == EIconDesign.Flat)
{
icon.Draw(c);
}
else if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
Rarity.DrawRarity(c, icon);
}
LargeSmallImage.DrawPreviewImage(c, icon);
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground &&
(EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText)
{
Text.DrawBackground(c, icon);
Text.DrawDisplayName(c, icon);
Text.DrawDescription(c, icon);
}
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "GCosmeticCard":
{
BaseGCosmetic icon = new BaseGCosmetic(exports[index], exportType);
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign == EIconDesign.Flat)
{
icon.Draw(c);
}
else
{
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
Rarity.DrawRarity(c, icon);
}
}
LargeSmallImage.DrawPreviewImage(c, icon);
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText)
{
Text.DrawBackground(c, icon);
Text.DrawDisplayName(c, icon);
Text.DrawDescription(c, icon);
}
}
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
// Battle Breakers
case "WExpGenericAccountItemDefinition":
{
BaseBBDefinition icon = new BaseBBDefinition(exports[index], exportType);
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
if (icon.RarityBackgroundImage != null)
{
c.DrawBitmap(icon.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
}
else
{
Rarity.DrawRarity(c, icon);
}
}
LargeSmallImage.DrawPreviewImage(c, icon);
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText)
{
Text.DrawBackground(c, icon);
Text.DrawDisplayName(c, icon);
Text.DrawDescription(c, icon);
}
}
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,202 @@
using System;
using System.Runtime.CompilerServices;
using CUE4Parse.UE4.Assets.Exports;
using FModel.Creator.Bases;
using FModel.Creator.Bases.BB;
using FModel.Creator.Bases.FN;
using FModel.Creator.Bases.SB;
namespace FModel.Creator
{
public class CreatorPackage : IDisposable
{
private UObject _object;
private EIconStyle _style;
public CreatorPackage(UObject uObject, EIconStyle style)
{
_object = uObject;
_style = style;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UCreator ConstructCreator()
{
TryConstructCreator(out var creator);
return creator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryConstructCreator(out UCreator creator)
{
switch (_object.ExportType)
{
// Fortnite
case "AthenaConsumableEmoteItemDefinition":
case "AthenaSkyDiveContrailItemDefinition":
case "AthenaLoadingScreenItemDefinition":
case "AthenaVictoryPoseItemDefinition":
case "AthenaPetCarrierItemDefinition":
case "AthenaMusicPackItemDefinition":
case "AthenaBattleBusItemDefinition":
case "AthenaCharacterItemDefinition":
case "AthenaMapMarkerItemDefinition":
case "AthenaBackpackItemDefinition":
case "AthenaPickaxeItemDefinition":
case "AthenaGadgetItemDefinition":
case "AthenaGliderItemDefinition":
case "AthenaSprayItemDefinition":
case "AthenaDanceItemDefinition":
case "AthenaEmojiItemDefinition":
case "AthenaItemWrapDefinition":
case "AthenaToyItemDefinition":
case "FortHeroType":
case "FortTokenType":
case "FortAbilityKit":
case "FortWorkerType":
case "RewardGraphToken":
case "FortBannerTokenType":
case "FortVariantTokenType":
case "FortDecoItemDefinition":
case "FortStatItemDefinition":
case "FortAmmoItemDefinition":
case "FortEmoteItemDefinition":
case "FortBadgeItemDefinition":
case "FortAwardItemDefinition":
case "FortGadgetItemDefinition":
case "FortPlaysetItemDefinition":
case "FortGiftBoxItemDefinition":
case "FortOutpostItemDefinition":
case "FortVehicleItemDefinition":
case "FortCardPackItemDefinition":
case "FortDefenderItemDefinition":
case "FortCurrencyItemDefinition":
case "FortResourceItemDefinition":
case "FortBackpackItemDefinition":
case "FortCodeTokenItemDefinition":
case "FortSchematicItemDefinition":
case "FortWorldMultiItemDefinition":
case "FortAlterationItemDefinition":
case "FortExpeditionItemDefinition":
case "FortIngredientItemDefinition":
case "FortAccountBuffItemDefinition":
case "FortWeaponMeleeItemDefinition":
case "FortPlayerPerksItemDefinition":
case "FortPlaysetPropItemDefinition":
case "FortHomebaseNodeItemDefinition":
case "FortNeverPersistItemDefinition":
case "RadioContentSourceItemDefinition":
case "FortPlaysetGrenadeItemDefinition":
case "FortPersonalVehicleItemDefinition":
case "FortGameplayModifierItemDefinition":
case "FortHardcoreModifierItemDefinition":
case "FortConsumableAccountItemDefinition":
case "FortConversionControlItemDefinition":
case "FortAccountBuffCreditItemDefinition":
case "FortEventCurrencyItemDefinitionRedir":
case "FortPersistentResourceItemDefinition":
case "FortHomebaseBannerIconItemDefinition":
case "FortCampaignHeroLoadoutItemDefinition":
case "FortConditionalResourceItemDefinition":
case "FortChallengeBundleScheduleDefinition":
case "FortWeaponMeleeDualWieldItemDefinition":
case "FortDailyRewardScheduleTokenDefinition":
case "FortCreativeRealEstatePlotItemDefinition":
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
creator = _style switch
{
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
_ => new BaseIcon(_object, _style)
};
return true;
case "FortTrapItemDefinition":
case "FortTandemCharacterData":
case "FortSpyTechItemDefinition":
case "FortAccoladeItemDefinition":
case "FortContextTrapItemDefinition":
case "FortWeaponRangedItemDefinition":
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
creator = new BaseIconStats(_object, _style);
return true;
case "FortItemSeriesDefinition":
creator = new BaseSeries(_object, _style);
return true;
case "MaterialInstanceConstant"
when _object.Owner != null &&
(_object.Owner.Name.EndsWith($"/MI_OfferImages/{_object.Name}") ||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}")):
creator = new BaseMaterialInstance(_object, _style);
return true;
case "FortMtxOfferData":
creator = new BaseMtxOffer(_object, _style);
return true;
case "FortPlaylistAthena":
creator = new BasePlaylist(_object, _style);
return true;
case "FortFeatItemDefinition":
case "FortQuestItemDefinition":
case "AthenaDailyQuestDefinition":
case "FortUrgentQuestItemDefinition":
creator = new BaseQuest(_object, _style);
return true;
case "FortCompendiumItemDefinition":
case "FortChallengeBundleItemDefinition":
creator = new BaseBundle(_object, _style);
return true;
case "AthenaSeasonItemDefinition":
creator = new BaseSeason(_object, _style);
return true;
case "FortItemAccessTokenType":
creator = new BaseItemAccessToken(_object, _style);
return true;
case "PlaylistUserOptionEnum":
case "PlaylistUserOptionBool":
case "PlaylistUserOptionString":
case "PlaylistUserOptionIntEnum":
case "PlaylistUserOptionIntRange":
case "PlaylistUserOptionColorEnum":
case "PlaylistUserOptionFloatEnum":
case "PlaylistUserOptionFloatRange":
case "PlaylistUserOptionPrimaryAsset":
case "PlaylistUserOptionCollisionProfileEnum":
creator = new BaseUserControl(_object, _style);
return true;
// Battle Breakers
case "WExpGenericAccountItemDefinition":
creator = new BaseBreakersIcon(_object, EIconStyle.Default);
return true;
// Spellbreak
case "GQuest":
case "GAccolade":
case "GCosmeticCard":
case "GCosmeticSkin":
case "GCharacterPerk":
case "GCosmeticTitle":
case "GCosmeticBadge":
case "GCosmeticEmote":
case "GCosmeticTriumph":
case "GCosmeticRunTrail":
case "GCosmeticArtifact":
case "GCosmeticDropTrail":
creator = new BaseSpellIcon(_object, EIconStyle.Default);
return true;
case "GLeagueTier":
creator = new BaseLeague(_object, EIconStyle.Default);
return true;
case "GLeagueDivision":
creator = new BaseDivision(_object, EIconStyle.Default);
return true;
default:
creator = null;
return false;
}
}
public override string ToString() => $"{_object.ExportType} | {_style}";
public void Dispose()
{
_object = null;
}
}
}

View File

@ -1,104 +0,0 @@
using FModel.Creator.Bases;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Icons
{
static class DisplayAssetImage
{
public static bool GetDisplayAssetImage(BaseIcon icon, IUExport o, ref string assetName)
{
string path = string.Empty;
bool displayExists = true;
if (o.TryGetValue("DisplayAssetPath", out var d) && d is StructProperty da && da.Value is FSoftObjectPath daOut)
path = daOut.AssetPathName.String;
else
{
displayExists = false;
path = "/Game/Catalog/MI_OfferImages/MI_" + assetName.Substring(0, assetName.LastIndexOf(".")).Replace("Athena_Commando_", "");
}
Package p = Utils.GetPropertyPakPackage(path);
if (!displayExists && p == default)
p = Utils.GetPropertyPakPackage(path[..path.LastIndexOf("_")]);
if (p != null && p.HasExport())
{
var obj = p.GetExport<UObject>();
if (obj != null)
{
if (obj.GetExport<ArrayProperty>("TextureParameterValues") is ArrayProperty textureParameterValues)
{
foreach (StructProperty textureParameter in textureParameterValues.Value)
{
if (textureParameter.Value is UObject parameter &&
parameter.TryGetValue("ParameterValue", out var i) && i is ObjectProperty value &&
parameter.TryGetValue("ParameterInfo", out var i1) && i1 is StructProperty i2 && i2.Value is UObject info &&
info.TryGetValue("Name", out var j1) && j1 is NameProperty name)
{
if (name.Value.String.Equals("OfferImage") || name.Value.String.Equals("Texture"))
{
icon.IconImage = Utils.GetObjectTexture(value) ?? Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_1") ?? Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_01");
assetName = "MI_" + assetName;
return true;
}
}
}
}
return GenerateOldFormat(icon, obj, ref assetName);
}
}
return GenerateOldFormat(icon, ref assetName);
}
private static bool GenerateOldFormat(BaseIcon icon, ref string assetName)
{
var p = Utils.GetPropertyPakPackage("/Game/Catalog/DisplayAssets/DA_Featured_" + assetName.Substring(0, assetName.LastIndexOf(".")));
if (p != null && p.HasExport())
{
var obj = p.GetExport<UObject>();
if (obj != null)
{
return GenerateOldFormat(icon, obj, ref assetName);
}
}
return false;
}
private static bool GenerateOldFormat(BaseIcon icon, UObject obj, ref 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;
}
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 &&
resourceObject.Value.Resource.OuterIndex.Resource != null &&
!resourceObject.Value.Resource.OuterIndex.Resource.ObjectName.String.Contains("/Game/Athena/Prototype/Textures/"))
{
icon.IconImage = Utils.GetObjectTexture(resourceObject);
assetName = "DA_Featured_" + assetName;
return true;
}
return false;
}
}
}

View File

@ -1,69 +0,0 @@
using FModel.Creator.Bases;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.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") == true)
path += "_" + assetName.Substring(assetName.LastIndexOf(".") - 1, 1);
Package p = Utils.GetPropertyPakPackage(path);
if (p != null && p.HasExport() && !p.Equals(default))
{
if (GetPreviewImage(icon, p.GetIndexedExport<UObject>(0), hightRes))
return;
else if (GetPreviewImage(icon, p.GetIndexedExport<UObject>(1), hightRes)) // FortniteGame/Content/Athena/Items/Cosmetics/Pickaxes/Pickaxe_ID_402_BlackKnightFemale1H.uasset
return;
}
}
public static void GetPreviewImage(BaseIcon icon, SoftObjectProperty s) => icon.IconImage = Utils.GetSoftObjectTexture(s);
private static bool GetPreviewImage(BaseIcon icon, UObject obj, bool hightRes)
{
if (obj != null)
{
if (hightRes && obj.TryGetValue("LargePreviewImage", out var sLarge) && sLarge is SoftObjectProperty largePreviewImage && !string.IsNullOrEmpty(largePreviewImage.Value.AssetPathName.String))
{
GetPreviewImage(icon, largePreviewImage);
return true;
}
else if (obj.TryGetValue("SmallPreviewImage", out var sSmall1) && sSmall1 is SoftObjectProperty smallPreviewImage1 && !string.IsNullOrEmpty(smallPreviewImage1.Value.AssetPathName.String))
{
GetPreviewImage(icon, smallPreviewImage1);
return true;
}
else if (obj.TryGetValue("SmallPreviewImage", out var sSmall2) && sSmall2 is ObjectProperty smallPreviewImage2 && !string.IsNullOrEmpty(smallPreviewImage2.Value.Resource.OuterIndex.Resource.ObjectName.String))
{
icon.IconImage = Utils.GetObjectTexture(smallPreviewImage2);
return true;
}
}
return false;
}
public static void DrawPreviewImage(SKCanvas c, IBase icon) =>
c.DrawBitmap(icon.IconImage ?? icon.FallbackImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
public static void DrawNotStretchedPreviewImage(SKCanvas c, IBase icon)
{
SKBitmap i = icon.IconImage ?? icon.FallbackImage;
int x = i.Width < icon.Width ? ((icon.Width / 2) - (i.Width / 2)) : icon.Margin;
c.DrawBitmap(i, new SKRect(x, icon.Margin, (x + i.Width) - (icon.Margin * 2), i.Height - icon.Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
}
}
}

View File

@ -1,73 +0,0 @@
using FModel.Creator.Bases;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Windows;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Icons
{
static class UserFacingFlag
{
public static void GetUserFacingFlags(List<string> uffs, BaseIcon icon, string exportType)
{
if (uffs.Count > 0)
{
Package p = Utils.GetPropertyPakPackage("/Game/Items/ItemCategories"); //PrimaryCategories - SecondaryCategories - TertiaryCategories
if (p.HasExport() && !p.Equals(default))
{
var o = p.GetExport<UObject>();
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;
}
}
}
}
}

View File

@ -1,31 +0,0 @@
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)
});
}
}
}
}

View File

@ -1,15 +0,0 @@
namespace FModel.Creator.Rarities
{
public enum EFortRarity : int
{
Common,
Uncommon,
Rare,
Epic,
Legendary,
Mythic,
Transcendent,
Unattainable,
//Impossible
}
}

View File

@ -1,204 +0,0 @@
using FModel.Creator.Bases;
using SkiaSharp;
using System.Linq;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Rarities
{
static class Rarity
{
public static void GetInGameRarity(BaseIcon icon, EnumProperty e)
{
Package p = Utils.GetPropertyPakPackage("/Game/Balance/RarityData");
if (p != null && p.HasExport())
{
var d = p.GetExport<UObject>();
if (d != null)
{
EFortRarity rarity = EFortRarity.Uncommon;
switch (e?.Value.String)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
rarity = EFortRarity.Common;
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
rarity = EFortRarity.Rare;
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
rarity = EFortRarity.Epic;
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
rarity = EFortRarity.Legendary;
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
rarity = EFortRarity.Mythic;
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
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 GetInGameRarity(BaseGCosmetic icon, EnumProperty e)
{
Package p = Utils.GetPropertyPakPackage("/Game/UI/UIKit/DT_RarityColors");
if (p != null || p.HasExport())
{
var d = p.GetExport<UDataTable>();
if (d != null)
{
if (e != null && d.TryGetValue(e.Value.String["EXRarity::".Length..], out object r) && r is UObject rarity &&
rarity.GetExport<ArrayProperty>("Colors") is ArrayProperty colors &&
colors.Value[0] is StructProperty s1 && s1.Value is FLinearColor color1 &&
colors.Value[1] is StructProperty s2 && s2.Value is FLinearColor color2 &&
colors.Value[2] 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) };
}
}
}
}
public static void GetHardCodedRarity(BaseIcon icon, EnumProperty e)
{
switch (e?.Value.String)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
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":
case "EFortRarity::Sturdy":
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":
case "EFortRarity::Fine":
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":
case "EFortRarity::Elegant":
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":
case "EFortRarity::Masterwork":
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("5CDCE2"), SKColor.Parse("72C5F8") };
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("28DAFB"), SKColor.Parse("28DAFB") };
break;
}
}
public static void DrawRarity(SKCanvas c, IBase icon)
{
// border
c.DrawRect(new SKRect(0, 0, icon.Width, icon.Height),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(icon.Width / 2, icon.Height),
new SKPoint(icon.Width, icon.Height / 4),
icon.RarityBorderColor,
SKShaderTileMode.Clamp)
});
switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
{
case EIconDesign.Flat:
{
if (icon is BaseIcon i && i.RarityBackgroundImage != null)
c.DrawBitmap(i.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
else
{
c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - 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.Width / 17 * 10), icon.Margin);
pathTop.LineTo(icon.Margin, icon.Margin + (icon.Height / 17));
pathTop.Close();
c.DrawPath(pathTop, paint);
var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
pathBottom.MoveTo(icon.Margin, icon.Height - icon.Margin);
pathBottom.LineTo(icon.Margin, icon.Height - icon.Margin - (icon.Height / 17 * 2.5f));
pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin - (icon.Height / 17 * 4.5f));
pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin);
pathBottom.Close();
c.DrawPath(pathBottom, paint);
}
break;
}
default:
{
if (icon is BaseIcon i && i.RarityBackgroundImage != null)
c.DrawBitmap(i.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
else
c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(icon.Width / 2, icon.Height / 2),
icon.Width / 5 * 4,
icon.RarityBackgroundColors,
SKShaderTileMode.Clamp)
});
break;
}
}
}
}
}

View File

@ -1,43 +0,0 @@
using FModel.Creator.Bases;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Rarities
{
static class Serie
{
public static void GetRarity(BaseIcon icon, ObjectProperty o)
{
if (o.Value.Resource != null)
{
Package p = Utils.GetPropertyPakPackage(o.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();
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) };
}
}
}
}
}

View File

@ -1,12 +0,0 @@
using SkiaSharp;
namespace FModel.Creator.Stats
{
public class Statistic
{
public SKBitmap Icon;
public string Description;
public string DisplayName;
public int Height;
}
}

View File

@ -1,231 +0,0 @@
using FModel.Creator.Bases;
using FModel.Creator.Texts;
using FModel.Utils;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
using System;
using System.Windows;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Stats
{
static class Statistics
{
public static void GetAmmoData(BaseIcon icon, SoftObjectProperty ammoData)
{
if (!ammoData.Value.AssetPathName.String.StartsWith("/Game/Athena/Items/Consumables/"))
{
Package p = Utils.GetPropertyPakPackage(ammoData.Value.AssetPathName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();
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)
{
Package p = Utils.GetPropertyPakPackage(dataTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var table = p.GetExport<UDataTable>();
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)
{
Package p = Utils.GetPropertyPakPackage(heroGameplayDefinition.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();
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)
{
Package k = Utils.GetPropertyPakPackage(grantedAbilityKit.Value.AssetPathName.String);
if (k.HasExport() && !k.Equals(default))
{
var kit = k.GetExport<UObject>();
if (kit != null &&
kit.GetExport<TextProperty>("DisplayName") is TextProperty displayName &&
kit.GetExport<StructProperty>("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)
});
if (stat.Icon != null) 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,
};
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
{
SKShaper shaper = new SKShaper(statPaint.Typeface);
float shapedTextWidth;
while (true)
{
SKShaper.Result shapedText = shaper.Shape(stat.Description, statPaint);
shapedTextWidth = shapedText.Points[^1].X + statPaint.TextSize / 2f;
if (shapedTextWidth > (icon.Size - (icon.Margin * 2) - iconSize))
{
statPaint.TextSize -= 2;
}
else
{
break;
}
}
c.DrawShapedText(shaper, stat.Description, (icon.Size - shapedTextWidth) / 2f, y + 32, statPaint);
}
else
{
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;
}
}
}
}

View File

@ -1,9 +0,0 @@
namespace FModel.Creator.Texts
{
public enum ETextSide
{
Center,
Right,
Left
}
}

View File

@ -1,73 +0,0 @@
using FModel.Creator.Bases;
using FModel.Creator.Icons;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
using FModel.Utils;
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)
{
Package p = Utils.GetPropertyPakPackage("/Game/Athena/Items/Cosmetics/Metadata/CosmeticSets");
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UDataTable>();
if (d != null && d.TryGetCaseInsensitiveValue(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 <SeasonText>{0}</>.")
.Replace("<SeasonText>", 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));
}
}
}

View File

@ -1,161 +0,0 @@
using FModel.Creator.Bases;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Text;
using FModel.Properties;
using SkiaSharp.HarfBuzz;
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, IBase icon, ETextSide side, SKRect area, SKPaint paint)
=> DrawCenteredMultilineText(canvas, text, maxLineCount, icon.Width, 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;
List<Line> lines = SplitLines(text, paint, area.Width - margin);
if (lines == null)
return;
if (lines.Count <= maxLineCount)
maxLineCount = lines.Count;
float height = maxLineCount * lineHeight;
float y = area.MidY - height / 2;
SKShaper shaper = (ELanguage)Settings.Default.AssetsLanguage == ELanguage.Arabic ? new SKShaper(paint.Typeface) : null;
for (int i = 0; i < maxLineCount; i++)
{
Line line = lines[i];
y += lineHeight;
float x = side switch
{
ETextSide.Center => area.MidX - line.Width / 2,
ETextSide.Right => size - margin - line.Width,
ETextSide.Left => margin,
_ => area.MidX - line.Width / 2
};
string lineText = line.Value.TrimEnd();
if (shaper == null)
{
canvas.DrawText(lineText, x, y, paint);
}
else
{
if (side == ETextSide.Center)
{
SKShaper.Result shapedText = shaper.Shape(lineText, paint);
float shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
canvas.DrawShapedText(shaper, lineText, (area.Width - shapedTextWidth) / 2f, y, paint);
}
else
{
canvas.DrawShapedText(shaper, lineText, 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;
List<Line> lines = SplitLines(text, paint, area.Width);
if (lines == null)
{
yPos = (int)area.Top;
return;
}
float y = area.Top;
SKShaper shaper = (ELanguage)Settings.Default.AssetsLanguage == ELanguage.Arabic ? new SKShaper(paint.Typeface) : null;
for (int i = 0; i < lines.Count; i++)
{
var line = lines[i];
float x = side switch
{
ETextSide.Center => area.MidX - line.Width / 2,
ETextSide.Right => size - margin - line.Width,
ETextSide.Left => area.Left,
_ => area.MidX - line.Width / 2
};
string lineText = line.Value.TrimEnd();
if (shaper == null)
{
canvas.DrawText(lineText, x, y, paint);
}
else
{
if (side == ETextSide.Center)
{
SKShaper.Result shapedText = shaper.Shape(lineText, paint);
float shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
canvas.DrawShapedText(shaper, lineText, (area.Width - shapedTextWidth) / 2f, y, paint);
}
else
{
canvas.DrawShapedText(shaper, lineText, x, y, paint);
}
}
y += lineHeight;
}
yPos = (int)area.Top + ((int)lineHeight * lines.Count);
}
public static List<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<Line> ret = new List<Line>(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(' ');
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;
}
}
}

View File

@ -1,300 +0,0 @@
using System.Collections.Generic;
using FModel.Creator.Bases;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
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 { } text)
if (text.Text is FTextHistory.None n)
return n.CultureInvariantString;
else if (text.Text is FTextHistory.Base b)
return b.SourceString.Replace("<Emphasized>", string.Empty).Replace("</>", string.Empty);
else if (text.Text is FTextHistory.StringTableEntry s)
{
Package p = Utils.GetPropertyPakPackage(s.TableId.String);
if (p.HasExport() && !p.Equals(default))
{
var table = p.GetExport<UStringTable>();
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<string, string> 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 { } 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}";
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
{
Package p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var table = p.GetExport<UCurveTable>();
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 string GetXpRewardAmount(StructProperty xpRewardAmount)
{
if (xpRewardAmount.Value is UObject o1)
{
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
{
Package p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var table = p.GetExport<UCurveTable>();
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 $"{amount.KeyValue} Xp";
}
}
}
}
}
return string.Empty;
}
public static void DrawBackground(SKCanvas c, IBase icon)
{
switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
{
case EIconDesign.Flat:
{
var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
pathBottom.MoveTo(icon.Margin, icon.Height - icon.Margin);
pathBottom.LineTo(icon.Margin, icon.Height - icon.Margin - icon.Height / 17 * 2.5f);
pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin - icon.Height / 17 * 4.5f);
pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - 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.Width - icon.Margin, icon.Height - icon.Margin),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = new SKColor(0, 0, 50, 75),
});
break;
}
}
}
public static void DrawDisplayName(SKCanvas c, IBase icon)
{
_NAME_TEXT_SIZE = 45;
string text = icon.DisplayName;
if (string.IsNullOrEmpty(text)) return;
SKTextAlign side = SKTextAlign.Center;
int x = icon.Width / 2;
int y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
{
case EIconDesign.Mini:
{
_NAME_TEXT_SIZE = 47;
text = text.ToUpperInvariant();
break;
}
case EIconDesign.Flat:
{
_NAME_TEXT_SIZE = 47;
side = SKTextAlign.Right;
x = icon.Width - 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
};
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
{
SKShaper shaper = new SKShaper(namePaint.Typeface);
float shapedTextWidth;
while (true)
{
SKShaper.Result shapedText = shaper.Shape(text, namePaint);
shapedTextWidth = shapedText.Points[^1].X + namePaint.TextSize / 2f;
if (shapedTextWidth > icon.Width - icon.Margin * 2)
{
namePaint.TextSize = _NAME_TEXT_SIZE -= 1;
}
else
{
break;
}
}
c.DrawShapedText(shaper, text, side == SKTextAlign.Right ? (x - shapedTextWidth) : ((icon.Width - shapedTextWidth) / 2f), y, namePaint);
}
else
{
// resize if too long
while (namePaint.MeasureText(text) > icon.Width - icon.Margin * 2)
{
namePaint.TextSize = _NAME_TEXT_SIZE -= 1;
}
c.DrawText(text, x, y, namePaint);
}
}
public static void DrawDescription(SKCanvas c, IBase icon)
{
_BOTTOM_TEXT_SIZE = 15;
string text = icon.Description;
if (string.IsNullOrEmpty(text)) return;
int maxLine = 4;
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.Width - icon.Margin, icon.Height - _BOTTOM_TEXT_SIZE),
descriptionPaint);
}
public static void DrawToBottom(SKCanvas c, BaseIcon icon, ETextSide side, string text)
{
if (string.IsNullOrEmpty(text)) return;
SKPaint shortDescriptionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = side == ETextSide.Left ? TypeFaces.BottomDefaultTypeface ?? TypeFaces.DisplayNameTypeface : TypeFaces.BottomDefaultTypeface ?? TypeFaces.DefaultTypeface,
TextSize = TypeFaces.BottomDefaultTypeface == null ? 15 : 13,
Color = SKColors.White,
TextAlign = side == ETextSide.Left ? SKTextAlign.Left : SKTextAlign.Right,
};
if (side == ETextSide.Left)
{
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
{
shortDescriptionPaint.TextSize -= 4f;
SKShaper shaper = new SKShaper(shortDescriptionPaint.Typeface);
c.DrawShapedText(shaper, text, icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f - shortDescriptionPaint.TextSize * .5f /* ¯\_(ツ)_/¯ */, shortDescriptionPaint);
}
else
{
c.DrawText(text, icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f, shortDescriptionPaint);
}
}
else
{
c.DrawText(text, icon.Size - icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f, shortDescriptionPaint);
}
}
}
}

View File

@ -1,243 +0,0 @@
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 _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
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 ig
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"; // simplified chinese fortnite
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional 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";
private const string _VALORANT_BASE_PATH = "/Game/";
private const string _TUNGSTEN_BOLD = "Tungsten-Bold";
private const string _DINNEXT_LTARABIC_HEAVY = "UI/Fonts/FinalFonts/LOCFonts/DIN_Next_Arabic/DINNextLTArabic-Heavy";
private const string _TUNGSTEN_CYRILLIC = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/Cyrillic_Tungsten";
private const string _TUNGSTEN_JAPANESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/JP_Tungsten";
private const string _TUNGSTEN_KOREAN = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/KR_Tungsten";
private const string _TUNGSTEN_SIMPLIFIED_CHINESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/zh-CN_Tungsten";
private const string _TUNGSTEN_TRADITIONAL_CHINESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/zh-TW_Tungsten";
private const string _DINNEXT_W1G_LIGHT = "UI/Fonts/FinalFonts/DINNextW1G-Light";
private const string _DINNEXT_LTARABIC_LIGHT = "UI/Fonts/FinalFonts/LOCFonts/DIN_Next_Arabic/DINNextLTArabic-Light";
private const string _NOTOSANS_CJK_LIGHT = "UI/Fonts/FinalFonts/LOCFonts/CJK/NotoSansCJK-Light"; // chinese, japanese, korean
private const string _DINNEXT_W1G_BOLD = "UI/Fonts/FinalFonts/DINNextW1G-Bold";
private const string _SPELLBREAK_BASE_PATH = "/Game/UI/Fonts/";
private const string _MONTSERRAT_SEMIBOLD = "Montserrat-Semibold";
private const string _MONTSERRAT_SEMIBOLD_ITALIC = "Montserrat-SemiBoldItalic";
private const string _NANUM_GOTHIC = "NanumGothic";
private const string _QUADRAT_BOLD = "Quadrat_Bold";
private const string _SEGOE_BOLD_ITALIC = "Segoe_Bold_Italic";
private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/";
private const string _HEMIHEAD426 = "HemiHead426";
private const string _ROBOTO_BOLD = "Roboto-Bold";
private const string _ROBOTO_BOLD_ALL_CAPS = "Roboto-BoldAllCaps";
private const string _ROBOTO_REGULAR = "Roboto-Regular";
private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular";
private const string _LATO_LIGHT = "Lato-Light";
private const string _LATO_MEDIUM = "Lato-Medium";
private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic";
private const string _LATO_BLACK = "Lato-Black";
#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 BottomDefaultTypeface;
public SKTypeface DisplayNameTypeface;
public SKTypeface DescriptionTypeface;
public SKTypeface BundleDisplayNameTypeface;
public Typefaces()
{
DefaultTypeface = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
if (Globals.Game.ActualGame == EGame.Fortnite)
{
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK);
if (t != null && t.Length == 3)
{
if (t[0].Array != null)
BundleDefaultTypeface = SKTypeface.FromStream(t[0].AsStream());
if (BundleDefaultTypeface != null && t[2].Array != null)
BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
}
else BundleDefaultTypeface = DefaultTypeface;
string namePath = _FORTNITE_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_CONDENSED_BLACK :
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_TC_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_BLACK :
string.Empty);
if (!namePath.Equals(_FORTNITE_BASE_PATH))
{
t = Utils.GetPropertyArraySegmentByte(namePath);
if (t != null && t.Length == 3)
{
if (t[0].Array != null)
DisplayNameTypeface = SKTypeface.FromStream(t[0].AsStream());
if (DisplayNameTypeface != null && t[2].Array != null)
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
}
}
else DisplayNameTypeface = DefaultTypeface;
string bottomPath = _FORTNITE_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? string.Empty :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? string.Empty :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? string.Empty :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? string.Empty :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? string.Empty :
_BURBANK_SMALL_BOLD);
t = Utils.GetPropertyArraySegmentByte(bottomPath);
if (t != null && t.Length == 3)
{
if (t[0].Array != null)
BottomDefaultTypeface = SKTypeface.FromStream(t[0].AsStream());
if (BottomDefaultTypeface != null && t[2].Array != null)
BottomDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
}
string descriptionPath = _FORTNITE_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_TC_REGULAR :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_REGULAR :
_NOTO_SANS_REGULAR);
t = Utils.GetPropertyArraySegmentByte(descriptionPath);
if (t != null && t.Length == 3)
{
if (t[0].Array != null)
DescriptionTypeface = SKTypeface.FromStream(t[0].AsStream());
if (DescriptionTypeface != null && t[2].Array != null)
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
}
else DescriptionTypeface = DefaultTypeface;
string bundleNamePath = _FORTNITE_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_CONDENSED_BLACK :
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_TC_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_BLACK :
string.Empty);
if (!bundleNamePath.Equals(_FORTNITE_BASE_PATH))
{
t = Utils.GetPropertyArraySegmentByte(bundleNamePath);
if (t != null && t.Length == 3)
{
if (t[0].Array != null)
BundleDisplayNameTypeface = SKTypeface.FromStream(t[0].AsStream());
if (BundleDisplayNameTypeface != null && t[2].Array != null)
BundleDisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
}
}
else BundleDisplayNameTypeface = BundleDefaultTypeface;
}
else if (Globals.Game.ActualGame == EGame.Valorant)
{
string namePath = _VALORANT_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _TUNGSTEN_KOREAN :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _TUNGSTEN_CYRILLIC :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _TUNGSTEN_JAPANESE :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _DINNEXT_LTARABIC_HEAVY :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _TUNGSTEN_TRADITIONAL_CHINESE :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _TUNGSTEN_SIMPLIFIED_CHINESE :
_TUNGSTEN_BOLD);
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(namePath);
if (t != null && t.Length == 3 && t[2].Array != null)
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
else DisplayNameTypeface = DefaultTypeface;
string descriptionPath = _VALORANT_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTOSANS_CJK_LIGHT :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTOSANS_CJK_LIGHT :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _DINNEXT_LTARABIC_LIGHT :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTOSANS_CJK_LIGHT :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTOSANS_CJK_LIGHT :
_DINNEXT_W1G_LIGHT);
t = Utils.GetPropertyArraySegmentByte(descriptionPath);
if (t != null && t.Length == 3 && t[2].Array != null)
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
else DescriptionTypeface = DefaultTypeface;
t = Utils.GetPropertyArraySegmentByte(_VALORANT_BASE_PATH + _DINNEXT_W1G_BOLD);
if (t != null && t.Length == 3 && t[2].Array != null)
BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
else BundleDefaultTypeface = DefaultTypeface;
}
else if (Globals.Game.ActualGame == EGame.Spellbreak)
{
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD);
if (t != null && t.Length == 3 && t[2].Array != null)
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
else DisplayNameTypeface = DefaultTypeface;
t = Utils.GetPropertyArraySegmentByte(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD);
if (t != null && t.Length == 3 && t[2].Array != null)
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
else DescriptionTypeface = DefaultTypeface;
}
else if (Globals.Game.ActualGame == EGame.BattleBreakers)
{
string namePath = _BATTLE_BREAKERS_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _LATO_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_REGULAR :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_REGULAR :
_HEMIHEAD426);
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(namePath);
if (t != null && t.Length == 3 && t[2].Array != null)
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
else DisplayNameTypeface = DefaultTypeface;
string descriptionPath = _BATTLE_BREAKERS_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _LATO_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_REGULAR :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_REGULAR :
_HEMIHEAD426);
t = Utils.GetPropertyArraySegmentByte(descriptionPath);
if (t != null && t.Length == 3 && t[2].Array != null)
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
else DescriptionTypeface = DefaultTypeface;
}
}
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);
}
}

224
FModel/Creator/Typefaces.cs Normal file
View File

@ -0,0 +1,224 @@
using System;
using System.IO;
using System.Windows;
using CUE4Parse.UE4.Versions;
using FModel.Settings;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator
{
public class Typefaces
{
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new Uri("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf");
private const string _EXT = ".ufont";
// FortniteGame
private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black";
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
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"; // simplified chinese fortnite
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional 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";
// WorldExplorers
private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/";
private const string _HEMIHEAD426 = "HemiHead426";
private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular";
private const string _LATO_BLACK = "Lato-Black";
private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic";
private const string _LATO_LIGHT = "Lato-Light";
private const string _LATO_MEDIUM = "Lato-Medium";
private readonly CUE4ParseViewModel _viewModel;
public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...)
public readonly SKTypeface DisplayName;
public readonly SKTypeface Description;
public readonly SKTypeface Bottom; // must be null for non-latin base languages
public readonly SKTypeface Bundle;
public readonly SKTypeface BundleNumber;
public Typefaces(CUE4ParseViewModel viewModel)
{
byte[] data;
_viewModel = viewModel;
var language = UserSettings.Default.AssetLanguage;
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
switch (viewModel.Game)
{
case FGame.FortniteGame:
{
var namePath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NIS_JYAU,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => string.Empty
};
if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
DisplayName = SKTypeface.FromStream(m);
}
else DisplayName = Default;
var descriptionPath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_REGULAR,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_REGULAR,
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
_ => _NOTO_SANS_REGULAR
};
if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
Description = SKTypeface.FromStream(m);
}
else Description = Default;
var bottomPath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => string.Empty,
ELanguage.Japanese => string.Empty,
ELanguage.Arabic => string.Empty,
ELanguage.TraditionalChinese => string.Empty,
ELanguage.Chinese => string.Empty,
_ => _BURBANK_SMALL_BOLD
};
if (viewModel.Provider.TrySaveAsset(bottomPath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
Bottom = SKTypeface.FromStream(m);
}
// else keep it null
if (viewModel.Provider.TrySaveAsset(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
BundleNumber = SKTypeface.FromStream(m);
}
else BundleNumber = Default;
var bundleNamePath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NIS_JYAU,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => string.Empty
};
if (viewModel.Provider.TrySaveAsset(bundleNamePath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
Bundle = SKTypeface.FromStream(m);
}
else Bundle = BundleNumber;
break;
}
case FGame.WorldExplorers:
{
var namePath = _BATTLE_BREAKERS_BASE_PATH +
language switch
{
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
ELanguage.Russian => _LATO_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
_ => _HEMIHEAD426
};
if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
DisplayName = SKTypeface.FromStream(m);
}
else DisplayName = Default;
var descriptionPath = _BATTLE_BREAKERS_BASE_PATH +
language switch
{
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
ELanguage.Russian => _LATO_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
_ => _HEMIHEAD426
};
if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
Description = SKTypeface.FromStream(m);
}
else Description = Default;
break;
}
case FGame.ShooterGame:
break;
case FGame.DeadByDaylight:
break;
case FGame.OakGame:
break;
case FGame.Dungeons:
break;
case FGame.g3:
break;
case FGame.StateOfDecay2:
break;
case FGame.Prospect:
break;
case FGame.Indiana:
break;
case FGame.RogueCompany:
break;
case FGame.SwGame:
break;
case FGame.Platform:
break;
}
}
public SKTypeface OnTheFly(string path)
{
if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return Default;
var m = new MemoryStream(data) {Position = 0};
return SKTypeface.FromStream(m);
}
}
}

View File

@ -1,129 +1,309 @@
using FModel.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse.Utils;
using CUE4Parse_Conversion.Textures;
using FModel.Framework;
using FModel.Services;
using FModel.ViewModels;
using SkiaSharp;
using System;
using System.Runtime.CompilerServices;
using FModel.PakReader;
using FModel.PakReader.IO;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator
{
static class Utils
public static class Utils
{
public static string GetFullPath(FPackageId id)
private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private static readonly Regex _htmlRegex = new("<.*?>");
public static Typefaces Typefaces;
public static string RemoveHtmlTags(string s)
{
foreach (var ioStore in Globals.CachedIoStores.Values)
var match = _htmlRegex.Match(s);
while (match.Success)
{
if (ioStore.Chunks.TryGetValue(id.Id, out string path))
s = s.Replace(match.Value, string.Empty);
match = match.NextMatch();
}
return s;
}
public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview)
{
if (uObject.TryGetValue(out FSoftObjectPath displayAsset, "DisplayAssetPath"))
{
preview = GetDisplayAsset(displayAsset);
return preview != null;
}
if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
{
preview = GetBitmap(sidePanelIcon);
return preview != null;
}
var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}";
if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition
{
if (!TryLoadObject($"{path[..path.LastIndexOf('_')]}_{path.SubstringAfterLast('_').ToLower()}", out material)) // Try to get MI with lowercase obfuscation
TryLoadObject(path[..path.LastIndexOf('_')], out material); // hopefully gets obfuscated item definition
}
preview = GetBitmap(material);
return preview != null;
}
public static SKBitmap GetDisplayAsset(FSoftObjectPath path)
{
if (!TryLoadObject(path.AssetPathName.Text, out UObject obj)) return null;
if (obj.TryGetValue(out FStructFallback type, "DetailsImage") &&
type.TryGetValue(out FPackageIndex resource, "ResourceObject") && resource.ResolvedObject?.Outer != null &&
!resource.ResolvedObject.Outer.Name.Text.Contains("FortniteGame/Content/Athena/Prototype/Textures/"))
{
return GetBitmap(resource);
}
return null;
}
public static SKBitmap GetBitmap(FPackageIndex packageIndex)
{
while (true)
{
if (!TryGetPackageIndexExport(packageIndex, out UExport export)) return null;
switch (export)
{
return ioStore.MountPoint + path;
case UTexture2D texture:
return GetBitmap(texture);
case UMaterialInstanceConstant material:
return GetBitmap(material);
case UObject uObject:
{
if (uObject.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage);
if (uObject.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
if (uObject.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage"))
{
packageIndex = smallPreview;
continue;
}
return null;
}
default:
return null;
}
}
}
public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
{
if (material == null) return null;
foreach (var textureParameter in material.TextureParameterValues)
{
if (!(textureParameter.ParameterValue is UTexture2D texture)) continue;
switch (textureParameter.ParameterInfo.Name.Text)
{
case "TextureA":
case "TextureB":
case "OfferImage":
{
return GetBitmap(texture);
}
}
}
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetFullPath(string partialPath)
{
foreach (var fileReader in Globals.CachedPakFiles.Values)
if (fileReader.TryGetPartialKey(partialPath, out var fullPath))
{
return fullPath;
}
public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) {Position = 0});
public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text);
public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null;
public static SKBitmap GetBitmap(UTexture2D texture) => texture.IsVirtual ? null : SKBitmap.Decode(texture.Decode()?.Encode());
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
foreach (var ioStoreReader in Globals.CachedIoStores.Values)
{
if (ioStoreReader.TryGetPartialKey(partialPath, out var fullPath))
{
return fullPath;
}
}
return string.Empty;
}
public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size);
public static Package GetPropertyPakPackage(string value)
{
string path = Strings.FixPath(value);
foreach (var fileReader in Globals.CachedPakFiles.Values)
if (fileReader.TryGetCaseInsensiteveValue(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.GetPackage(entry, mount);
}
foreach (var ioStoreReader in Globals.CachedIoStores.Values)
if (ioStoreReader.TryGetCaseInsensiteveValue(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.GetPackage(entry, mount);
}
return default;
}
public static ArraySegment<byte>[] GetPropertyArraySegmentByte(string value)
{
string path = Strings.FixPath(value);
foreach (var fileReader in Globals.CachedPakFiles.Values)
if (fileReader.TryGetCaseInsensiteveValue(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);
}
foreach (var ioStoreReader in Globals.CachedIoStores.Values)
if (ioStoreReader.TryGetCaseInsensiteveValue(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;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SKBitmap NewZeroedBitmap(int width, int height) => new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SKBitmap Resize(this SKBitmap me, int width, int height)
{
var bmp = NewZeroedBitmap(width, height);
var bmp = new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels);
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)
public static bool TryGetPackageIndexExport<T>(FPackageIndex packageIndex, out T export) where T : UExport
{
if (s != null)
if (packageIndex.ResolvedObject == null)
{
if (s.Equals("/Game/UI/Foundation/Textures/BattleRoyale/FeaturedItems/Outfit/T_UI_InspectScreen_annualPass"))
s += "_1024";
else if (s.Equals("/Game/UI/Foundation/Textures/BattleRoyale/BattlePass/T-BattlePass-Season14-Tile") || s.Equals("/Game/UI/Foundation/Textures/BattleRoyale/BattlePass/T-BattlePassWithLevels-Season14-Tile"))
s += "_1";
else if (s.Equals("/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_WolfsBlood_UIT"))
s = "/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_Wolfsblood_UIT";
else if (s.Equals("/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_Timeweaver_UIT"))
s = "/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_TimeWeaver_UIT";
export = default;
return false;
}
var p = GetPropertyPakPackage(s);
if (p != null && p.HasExport() && !p.Equals(default))
var outerChain = new List<string>();
var current = packageIndex.ResolvedObject.Outer;
while (current != null)
{
var i = p.GetExport<UTexture2D>();
if (i != null)
return SKBitmap.Decode(i.Image.Encode());
var u = p.GetExport<UObject>();
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);
outerChain.Add(current.Name.Text);
current = current.Outer;
}
return null;
if (outerChain.Count < 1)
{
export = default;
return false;
}
if (!_applicationView.CUE4Parse.Provider.TryLoadPackage(outerChain[^1], out var pkg))
{
export = default;
return false;
}
export = pkg.GetExport(packageIndex.ResolvedObject.Index) as T;
return export != null;
}
// fullpath must be either without any extension or with the export objectname
public static bool TryLoadObject<T>(string fullPath, out T export) where T : UExport
{
return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export);
}
public static IEnumerable<UExport> LoadExports(string fullPath)
{
return _applicationView.CUE4Parse.Provider.LoadObjectExports(fullPath);
}
public static string GetLocalizedResource(string namespacee, string key, string defaultValue)
{
return _applicationView.CUE4Parse.Provider.GetLocalizedString(namespacee, key, defaultValue);
}
public static string GetFullPath(string partialPath)
{
var regex = new Regex(partialPath, RegexOptions.IgnoreCase | RegexOptions.Compiled);
foreach (var path in _applicationView.CUE4Parse.Provider.Files.Keys)
{
if (regex.IsMatch(path))
{
return path;
}
}
return string.Empty;
}
public static void DrawCenteredMultilineText(SKCanvas c, string text, int maxCount, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint)
{
var lineHeight = paint.TextSize * 1.2f;
var lines = SplitLines(text, paint, area.Width - margin);
#if DEBUG
c.DrawRect(new SKRect(area.Left, area.Top, area.Right, area.Bottom), new SKPaint {Color = SKColors.Red, IsStroke = true});
#endif
if (lines == null) return;
if (lines.Count <= maxCount) maxCount = lines.Count;
var height = maxCount * lineHeight;
var y = area.MidY - height / 2;
var shaper = new CustomSKShaper(paint.Typeface);
for (var i = 0; i < maxCount; i++)
{
var line = lines[i];
if (line == null) continue;
var lineText = line.Trim();
var shapedText = shaper.Shape(lineText, paint);
y += lineHeight;
var x = side switch
{
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
SKTextAlign.Left => margin,
_ => throw new NotImplementedException()
};
c.DrawShapedText(shaper, lineText, x, y, paint);
}
}
public static void DrawMultilineText(SKCanvas c, string text, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint, out float yPos)
{
yPos = area.Top;
var lineHeight = paint.TextSize * 1.2f;
var lines = SplitLines(text, paint, area.Width);
if (lines == null) return;
foreach (var line in lines)
{
if (line == null) continue;
var lineText = line.Trim();
var shaper = new CustomSKShaper(paint.Typeface);
var shapedText = shaper.Shape(lineText, paint);
var x = side switch
{
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
SKTextAlign.Left => area.Left,
_ => throw new NotImplementedException()
};
c.DrawShapedText(shaper, lineText, x, yPos, paint);
yPos += lineHeight;
}
#if DEBUG
c.DrawRect(new SKRect(area.Left, area.Top - paint.TextSize, area.Right, yPos), new SKPaint {Color = SKColors.Red, IsStroke = true});
#endif
}
public static List<string> SplitLines(string text, SKPaint paint, float maxWidth)
{
if (string.IsNullOrEmpty(text)) return null;
var spaceWidth = paint.MeasureText(" ");
var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries);
var ret = new List<string>(lines.Length);
foreach (var line in lines)
{
if (string.IsNullOrWhiteSpace(line)) continue;
float width = 0;
var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var lineResult = new StringBuilder();
foreach (var word in words)
{
var wordWidth = paint.MeasureText(word);
var wordWithSpaceWidth = wordWidth + spaceWidth;
var wordWithSpace = word + " ";
if (width + wordWidth > maxWidth)
{
ret.Add(lineResult.ToString());
lineResult = new StringBuilder(wordWithSpace);
width = wordWithSpaceWidth;
}
else
{
lineResult.Append(wordWithSpace);
width += wordWithSpaceWidth;
}
}
ret.Add(lineResult.ToString());
}
return ret;
}
}
}
}

View File

@ -1,70 +0,0 @@
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();
public static void Deinitialize() => _client.Deinitialize();
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 = string.Format(Properties.Resources.Idling, Globals.Game.GetName())
});
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()
{
if (_presence != null)
{
_client.SetPresence(_presence);
_client.Invoke();
}
}
public static void SaveCurrentPresence() => _presence = _client.CurrentPresence;
}
}

View File

@ -1,86 +1,139 @@
namespace FModel
{
public enum EGame
{
Unknown,
Fortnite,
Valorant,
DeadByDaylight,
Borderlands3,
MinecraftDungeons,
BattleBreakers,
Spellbreak,
StateOfDecay2,
TheCycle,
TheOuterWorlds,
RogueCompany
}
using System.ComponentModel;
public enum EFModel
namespace FModel
{
public enum EBuildKind
{
Debug,
Release,
Unknown
}
public enum EPakLoader
public enum EErrorKind
{
Close,
Ignore,
Restart
}
public enum SettingsOut
{
Restart,
ReloadLocres,
Nothing
}
public enum EStatusKind
{
Ready, // ready
Loading, // doing stuff
Stopping, // trying to stop
Stopped, // stopped
Failed, // crashed
Completed // worked
}
public enum EAesReload
{
[Description("Always")]
Always,
[Description("Never")]
Never,
[Description("Once Per Day")]
OncePerDay
}
public enum EDiscordRpc
{
[Description("Always")]
Always,
[Description("Never")]
Never
}
public enum EEnabledDisabled
{
[Description("Disabled")]
Disabled,
[Description("Enabled")]
Enabled
}
public enum FGame
{
[Description("Unknown")]
Unknown,
[Description("Fortnite")]
FortniteGame,
[Description("Valorant")]
ShooterGame,
[Description("Dead By Daylight")]
DeadByDaylight,
[Description("Borderlands 3")]
OakGame,
[Description("Minecraft Dungeons")]
Dungeons,
[Description("Battle Breakers")]
WorldExplorers,
[Description("Spellbreak")]
g3,
[Description("State Of Decay 2")]
StateOfDecay2,
[Description("The Cycle")]
Prospect,
[Description("The Outer Worlds")]
Indiana,
[Description("Rogue Company")]
RogueCompany,
[Description("Star Wars: Jedi Fallen Order")]
SwGame,
[Description("Core")]
Platform
}
public enum ELoadingMode
{
[Description("Single")]
Single,
[Description("Multiple")]
Multiple,
[Description("All")]
All,
New,
Modified,
NewModified
[Description("All (New)")]
AllButNew,
[Description("All (Modified)")]
AllButModified
}
public enum ECopy
public enum EUpdateMode
{
Path,
PathNoExt,
PathNoFile,
File,
FileNoExt
[Description("Stable")]
Stable,
[Description("Beta")]
Beta
}
public enum ELanguage : long
public enum ECompressedAudio
{
English = 0,
AustralianEnglish = 15,
BritishEnglish = 16,
French = 1,
German = 2,
Italian = 3,
Spanish = 4,
SpanishLatin = 5,
SpanishMexico = 17,
Arabic = 6,
Japanese = 7,
Korean = 8,
Polish = 9,
PortugueseBrazil = 10,
PortuguesePortugal = 18,
Russian = 11,
Turkish = 12,
Chinese = 13,
TraditionalChinese = 14,
Swedish = 19,
Thai = 20,
Indonesian = 21,
VietnameseVietnam = 22,
Zulu = 23
[Description("Play the decompressed data")]
PlayDecompressed,
[Description("Play the compressed data (might not always be a valid audio data)")]
PlayCompressed
}
public enum EJsonType: long
{
Default,
Positioned
}
public enum EIconDesign : long
public enum EIconStyle
{
[Description("Default")]
Default,
[Description("No Background")]
NoBackground,
[Description("No Text")]
NoText,
Mini,
Flat
[Description("Flat")]
Flat,
[Description("Cataba")]
Cataba,
// [Description("Community")]
// CommunityMade
}
}
}

View File

@ -0,0 +1,47 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
namespace FModel.Extensions
{
public static class AvalonExtensions
{
private static readonly IHighlightingDefinition _jsonHighlighter = LoadHighlighter("Json.xshd");
private static readonly IHighlightingDefinition _iniHighlighter = LoadHighlighter("Ini.xshd");
private static readonly IHighlightingDefinition _xmlHighlighter = LoadHighlighter("Xml.xshd");
private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IHighlightingDefinition LoadHighlighter(string resourceName)
{
var executingAssembly = Assembly.GetExecutingAssembly();
using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{resourceName}");
using var reader = new XmlTextReader(stream);
return HighlightingLoader.Load(reader, HighlightingManager.Instance);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IHighlightingDefinition HighlighterSelector(string ext)
{
switch (ext)
{
case "ini":
case "csv":
return _iniHighlighter;
case "xml":
return _xmlHighlighter;
case "h":
case "cpp":
return _cppHighlighter;
case "bat":
case "txt":
case "po":
return null;
default:
return _jsonHighlighter;
}
}
}
}

View File

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace FModel.Extensions
{
public static class CollectionExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Next<T>(this IList<T> collection, T value)
{
var i = collection.IndexOf(value) + 1;
return i >= collection.Count ? collection.First() : collection[i];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Next<T>(this IList<T> collection, int index)
{
var i = index + 1;
return i >= collection.Count ? collection.First() : collection[i];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Previous<T>(this IList<T> collection, T value)
{
var i = collection.IndexOf(value) - 1;
return i < 0 ? collection.Last() : collection[i];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Previous<T>(this IList<T> collection, int index)
{
var i = index - 1;
return i < 0 ? collection.Last() : collection[i];
}
}
}

View File

@ -0,0 +1,84 @@
using FModel.Properties;
using System;
using System.ComponentModel;
using System.Resources;
using System.Runtime.CompilerServices;
namespace FModel.Extensions
{
public static class EnumExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetDescription(this Enum value)
{
var fi = value.GetType().GetField(value.ToString());
if (fi == null) return $"{value} ({value:D})";
var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : $"{value} ({value:D})";
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetLocalizedDescription(this Enum value) => value.GetLocalizedDescription(Resources.ResourceManager);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetLocalizedDescription(this Enum value, ResourceManager resourceManager)
{
var resourceName = value.GetType().Name + "_" + value;
var description = resourceManager.GetString(resourceName);
if (string.IsNullOrEmpty(description))
{
description = value.GetDescription();
}
return description;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetLocalizedCategory(this Enum value) => value.GetLocalizedCategory(Resources.ResourceManager);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetLocalizedCategory(this Enum value, ResourceManager resourceManager)
{
var resourceName = value.GetType().Name + "_" + value + "_Category";
var description = resourceManager.GetString(resourceName);
return description;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ToEnum<T>(this string value, T defaultValue)
{
if (!Enum.TryParse(typeof(T), value, true, out var ret))
return defaultValue;
return (T) ret;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasAnyFlags<T>(this T flags, T contains) where T : Enum, IConvertible => (flags.ToInt32(null) & contains.ToInt32(null)) != 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetIndex(this Enum value)
{
var values = Enum.GetValues(value.GetType());
return Array.IndexOf(values, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Next<T>(this Enum value)
{
var values = Enum.GetValues(value.GetType());
var i = Array.IndexOf(values, value) + 1;
return i == values.Length ? (T) values.GetValue(0) : (T) values.GetValue(i);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Previous<T>(this Enum value)
{
var values = Enum.GetValues(value.GetType());
var i = Array.IndexOf(values, value) - 1;
return i == -1 ? (T) values.GetValue(values.Length - 1) : (T) values.GetValue(i);
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace FModel.Extensions
{
public enum Endianness
{
LittleEndian,
BigEndian,
}
public static class StreamExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadUInt32(this Stream s, Endianness endian = Endianness.LittleEndian)
{
var b1 = s.ReadByte();
var b2 = s.ReadByte();
var b3 = s.ReadByte();
var b4 = s.ReadByte();
return endian switch
{
Endianness.LittleEndian => (uint) (b4 << 24 | b3 << 16 | b2 << 8 | b1),
Endianness.BigEndian => (uint) (b1 << 24 | b2 << 16 | b3 << 8 | b4),
_ => throw new Exception("unknown endianness")
};
}
}
}

View File

@ -1,113 +1,90 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
namespace FModel.Utils
namespace FModel.Extensions
{
public static class Strings
public static class StringExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetReadableSize(double size)
{
string[] sizes = { Properties.Resources.B, Properties.Resources.KB, Properties.Resources.MB, Properties.Resources.GB, Properties.Resources.TB };
int order = 0;
if (size == 0) return "0 B";
string[] sizes = {"B", "KB", "MB", "GB", "TB"};
var order = 0;
while (size >= 1024 && order < sizes.Length - 1)
{
order++;
size /= 1024;
}
return string.Format("{0:# ###.##} {1}", size, sizes[order]).TrimStart();
return $"{size:# ###.##} {sizes[order]}".TrimStart();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string FixPath(string path)
{
if (string.IsNullOrWhiteSpace(path) || path.Length < 5)
return string.Empty;
string trigger;
{
if (path.Contains("/"))
{
string tempPath = path[1..];
trigger = tempPath.Substring(0, tempPath.IndexOf('/'));
}
else
trigger = path;
}
Regex regex = new Regex(trigger);
if (trigger.Equals("SrirachaRanchCore") || trigger.Equals("SrirachaRanchHoagie") || trigger.Equals("SrirachaRanchValet"))
trigger = "SrirachaRanch/" + trigger;
string fixedPath = trigger switch
{
"Game" => regex.Replace(path, $"{Folders.GetGameName()}/Content", 1),
"RegionCN" => regex.Replace(path, $"{Folders.GetGameName()}/Plugins/{trigger}/Content", 1),
_ => regex.Replace(path, $"{Folders.GetGameName()}/Plugins/GameFeatures/{trigger}/Content", 1)
};
int sep = fixedPath.LastIndexOf('.');
return fixedPath.Substring(0, sep > 0 ? sep : fixedPath.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBefore(this string s, char delimiter)
{
var index = s.IndexOf(delimiter);
return index == -1 ? s : s.Substring(0, index);
return index == -1 ? s : s[..index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBefore(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
{
var index = s.IndexOf(delimiter, comparisonType);
return index == -1 ? s : s.Substring(0, index);
return index == -1 ? s : s[..index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringAfter(this string s, char delimiter)
{
var index = s.IndexOf(delimiter);
return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringAfter(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
{
var index = s.IndexOf(delimiter, comparisonType);
return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBeforeLast(this string s, char delimiter)
{
var index = s.LastIndexOf(delimiter);
return index == -1 ? s : s.Substring(0, index);
return index == -1 ? s : s[..index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBeforeWithLast(this string s, char delimiter)
{
var index = s.LastIndexOf(delimiter);
return index == -1 ? s : s.Substring(0, index + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBeforeLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
{
var index = s.LastIndexOf(delimiter, comparisonType);
return index == -1 ? s : s.Substring(0, index);
return index == -1 ? s : s[..index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBeforeWithLast(this string s, char delimiter)
{
var index = s.LastIndexOf(delimiter);
return index == -1 ? s : s[..(index + 1)];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBeforeWithLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
{
var index = s.LastIndexOf(delimiter, comparisonType);
return index == -1 ? s : s[..(index + 1)];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringAfterLast(this string s, char delimiter)
{
var index = s.LastIndexOf(delimiter);
return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringAfterLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
{
@ -116,7 +93,42 @@ namespace FModel.Utils
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Contains(this string orig, string value, StringComparison comparisonType) =>
orig.IndexOf(value, comparisonType) >= 0;
public static int GetLineNumber(this string s, string lineToFind)
{
if (int.TryParse(lineToFind, out var index))
return s.GetLineNumber(index);
lineToFind = $" \"Name\": \"{lineToFind}\",";
using var reader = new StringReader(s);
var lineNum = 0;
string line;
while ((line = reader.ReadLine()) != null)
{
lineNum++;
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
return lineNum;
}
return 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetLineNumber(this string s, int index)
{
using var reader = new StringReader(s);
var lineNum = 0;
string line;
while ((line = reader.ReadLine()) != null)
{
lineNum++;
if (line.Equals(" {"))
index--;
if (index == -1)
return lineNum + 1;
}
return 1;
}
}
}
}

View File

@ -1,201 +1,185 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationIcon>FModel.ico</ApplicationIcon>
<StartupObject>FModel.App</StartupObject>
<Authors>Asval</Authors>
<Company></Company>
<AssemblyVersion>3.1.2.0</AssemblyVersion>
<FileVersion>3.1.2.0</FileVersion>
<PackageIcon>FModel.ico</PackageIcon>
<PackageIconUrl />
<PackageProjectUrl>https://github.com/iAmAsval/FModel</PackageProjectUrl>
<Description></Description>
<Version>3.1.2</Version>
<Platforms>x64</Platforms>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<Version>4.0.0</Version>
<AssemblyVersion>4.0.0.0</AssemblyVersion>
<FileVersion>4.0.0.0</FileVersion>
<IsPackable>false</IsPackable>
<IsPublishable>true</IsPublishable>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>1701;1702;NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>1701;1702;NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Remove="FModel.ico" />
<None Remove="Resources\alert.ico" />
<None Remove="Resources\api-off.ico" />
<None Remove="Resources\api.ico" />
<None Remove="Resources\backup-restore.png" />
<None Remove="Resources\battlebreakers.ico" />
<None Remove="Resources\borderlands3.ico" />
<None Remove="Resources\BurbankBigCondensed-Bold.ttf" />
<None Remove="Resources\check-circle.ico" />
<None Remove="Resources\ColorPickerOne.png" />
<None Remove="Resources\ColorPickerTwo.png" />
<None Remove="Resources\content-copy.png" />
<None Remove="Resources\Cpp.xshd" />
<None Remove="Resources\delete-forever.png" />
<None Remove="Resources\egl2.ico" />
<None Remove="Resources\EIconDesign_Default.png" />
<None Remove="Resources\EIconDesign_Flat.png" />
<None Remove="Resources\EIconDesign_Mini.png" />
<None Remove="Resources\EIconDesign_NoBackground.png" />
<None Remove="Resources\EIconDesign_NoText.png" />
<None Remove="Resources\file-image.ico" />
<None Remove="Resources\file-multiple.png" />
<None Remove="Resources\file.png" />
<None Remove="Resources\fmodel.png" />
<None Remove="Resources\folder-download.png" />
<None Remove="Resources\fortnite.ico" />
<None Remove="Resources\image-plus.png" />
<None Remove="Resources\image-remove.png" />
<None Remove="Resources\Ini.xshd" />
<None Remove="Resources\Json.xshd" />
<None Remove="Resources\lock-open-variant.ico" />
<None Remove="Resources\minecraftdungeons.ico" />
<None Remove="Resources\open-in-new.png" />
<None Remove="Resources\pause.png" />
<None Remove="Resources\pencil.png" />
<None Remove="Resources\play.png" />
<None Remove="Resources\playlist-plus.png" />
<None Remove="Resources\power.png" />
<None Remove="Resources\progress-download.png" />
<None Remove="Resources\refresh.png" />
<None Remove="Resources\android.png" />
<None Remove="Resources\apple.png" />
<None Remove="Resources\battlebreakers.png" />
<None Remove="Resources\blueprint.png" />
<None Remove="Resources\borderlands.png" />
<None Remove="Resources\empty_folder.png" />
<None Remove="Resources\engine.png" />
<None Remove="Resources\fallenorder.png" />
<None Remove="Resources\FModel.ico" />
<None Remove="Resources\folder.png" />
<None Remove="Resources\fortnite.png" />
<None Remove="Resources\fortnitebr.png" />
<None Remove="Resources\gear.png" />
<None Remove="Resources\localization.png" />
<None Remove="Resources\materialicon.png" />
<None Remove="Resources\pc.png" />
<None Remove="Resources\puzzle.png" />
<None Remove="Resources\roguecompany.png" />
<None Remove="Resources\rotate-3d.png" />
<None Remove="Resources\share-all.png" />
<None Remove="Resources\share.png" />
<None Remove="Resources\sign-direction-plus.png" />
<None Remove="Resources\sign-direction-remove.png" />
<None Remove="Resources\spellbreak.ico" />
<None Remove="Resources\stop.png" />
<None Remove="Resources\sod2.ico" />
<None Remove="Resources\T-Icon-Pets-64.png" />
<None Remove="Resources\T-Icon-Quests-64.png" />
<None Remove="Resources\thecycle.ico" />
<None Remove="Resources\theouterworlds.png" />
<None Remove="Resources\sound.png" />
<None Remove="Resources\creative.png" />
<None Remove="Resources\spellbreak.png" />
<None Remove="Resources\texture.png" />
<None Remove="Resources\thecycle.png" />
<None Remove="Resources\ui.png" />
<None Remove="Resources\valorant.png" />
<None Remove="Resources\weapon.png" />
<None Remove="Resources\windows.png" />
<None Remove="Resources\cinematics.png" />
<None Remove="Resources\archive.png" />
<None Remove="Resources\archive_enabled.png" />
<None Remove="Resources\archive_disabled.png" />
<None Remove="Resources\unknown_asset.png" />
<None Remove="Resources\asset.png" />
<None Remove="Resources\asset_ini.png" />
<None Remove="Resources\asset_psd.png" />
<None Remove="Resources\asset_png.png" />
<None Remove="Resources\athena.png" />
<None Remove="Resources\Json.xshd" />
<None Remove="Resources\Ini.xshd" />
<None Remove="Resources\Xml.xshd" />
<None Remove="Resources\Cpp.xshd" />
<None Remove="Resources\unix.png" />
<None Remove="Resources\linux.png" />
<None Remove="Resources\T_Placeholder_Item_Image.png" />
<None Remove="Resources\T_ClipSize_Weapon_Stats.png" />
<None Remove="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<None Remove="Resources\T_Placeholder_Challenge_Image.png" />
<None Remove="Resources\T_Placeholder_Item_Image.png" />
<None Remove="Resources\T_ReloadTime_Weapon_Stats.png" />
<None Remove="Resources\battle-breakers-item-background.png" />
<None Remove="Resources\valorant.live.ico" />
<None Remove="Resources\volume-minus.png" />
<None Remove="Resources\volume-mute.png" />
<None Remove="Resources\volume-plus.png" />
<None Remove="Resources\wifi-strength-off.ico" />
<None Remove="Resources\Xml.xshd" />
<None Include="FModel.ico">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
<None Remove="Resources\T-Icon-Pets-64.png" />
<None Remove="Resources\T-Icon-Quests-64.png" />
<None Remove="Resources\city_pin.png" />
<None Remove="Resources\pin.png" />
<None Remove="Resources\Default.png" />
<None Remove="Resources\NoBackground.png" />
<None Remove="Resources\NoText.png" />
<None Remove="Resources\Flat.png" />
<None Remove="Resources\Cataba.png" />
<None Remove="Resources\BurbankBigCondensed-Bold.ttf" />
<None Remove="Resources\add_directory.png" />
<None Remove="Resources\delete.png" />
<None Remove="Resources\edit.png" />
<None Remove="Resources\go_to_directory.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Cpp.xshd" />
<EmbeddedResource Include="Resources\Ini.xshd" />
<EmbeddedResource Include="Resources\Json.xshd" />
<EmbeddedResource Include="Resources\Ini.xshd" />
<EmbeddedResource Include="Resources\Xml.xshd" />
<None Remove="Resources\Detex.dll" />
<EmbeddedResource Include="Resources\Detex.dll" />
<EmbeddedResource Include="Resources\Cpp.xshd" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AdonisUI.ClassicTheme.NET5" Version="1.17.1" />
<PackageReference Include="AdonisUI.NET5" Version="1.17.1" />
<PackageReference Include="Autoupdater.NET.Official" Version="1.6.4" />
<PackageReference Include="AvalonEdit" Version="6.0.1" />
<PackageReference Include="CSCore" Version="1.2.1.2" />
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="DotNetZip" Version="1.15.0" />
<PackageReference Include="EpicManifestParser" Version="1.2.0" />
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.0.2" />
<PackageReference Include="Fortnite-API-Wrapper" Version="2.2.0" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.2.6" />
<PackageReference Include="LZMA-SDK" Version="19.0.0" />
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="1.2.0" />
<PackageReference Include="SkiaSharp" Version="2.80.2" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="3.1.0" />
<PackageReference Include="RestSharp" Version="106.11.7" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.2" />
<PackageReference Include="System.Data.HashFunction.CityHash" Version="2.0.0" />
<PackageReference Include="ToastNotifications" Version="2.5.1" />
<PackageReference Include="ToastNotifications.Messages" Version="2.5.1" />
<PackageReference Include="Usmap.NET" Version="1.1.1" />
<PackageReference Include="WriteableBitmapEx" Version="1.6.7" />
</ItemGroup>
<ItemGroup>
<Resource Include="FModel.ico" />
<Resource Include="Resources\alert.ico" />
<Resource Include="Resources\api-off.ico" />
<Resource Include="Resources\api.ico" />
<Resource Include="Resources\backup-restore.png" />
<Resource Include="Resources\battlebreakers.ico" />
<Resource Include="Resources\borderlands3.ico" />
<Resource Include="Resources\BurbankBigCondensed-Bold.ttf" />
<Resource Include="Resources\check-circle.ico" />
<Resource Include="Resources\ColorPickerOne.png" />
<Resource Include="Resources\ColorPickerTwo.png" />
<Resource Include="Resources\content-copy.png" />
<Resource Include="Resources\delete-forever.png" />
<Resource Include="Resources\egl2.ico" />
<Resource Include="Resources\EIconDesign_Default.png" />
<Resource Include="Resources\EIconDesign_Flat.png" />
<Resource Include="Resources\EIconDesign_Mini.png" />
<Resource Include="Resources\EIconDesign_NoBackground.png" />
<Resource Include="Resources\EIconDesign_NoText.png" />
<Resource Include="Resources\file-image.ico" />
<Resource Include="Resources\file-multiple.png" />
<Resource Include="Resources\file.png" />
<Resource Include="Resources\fmodel.png" />
<Resource Include="Resources\folder-download.png" />
<Resource Include="Resources\fortnite.ico" />
<Resource Include="Resources\image-plus.png" />
<Resource Include="Resources\image-remove.png" />
<Resource Include="Resources\lock-open-variant.ico" />
<Resource Include="Resources\minecraftdungeons.ico" />
<Resource Include="Resources\open-in-new.png" />
<Resource Include="Resources\pause.png" />
<Resource Include="Resources\pencil.png" />
<Resource Include="Resources\play.png" />
<Resource Include="Resources\playlist-plus.png" />
<Resource Include="Resources\power.png" />
<Resource Include="Resources\progress-download.png" />
<Resource Include="Resources\refresh.png" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Fortnite\CUE4Parse-Fortnite.csproj" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse\CUE4Parse.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\android.png" />
<Resource Include="Resources\apple.png" />
<Resource Include="Resources\battlebreakers.png" />
<Resource Include="Resources\blueprint.png" />
<Resource Include="Resources\borderlands.png" />
<Resource Include="Resources\fallenorder.png" />
<Resource Include="Resources\FModel.ico" />
<Resource Include="Resources\folder.png" />
<Resource Include="Resources\fortnite.png" />
<Resource Include="Resources\fortnitebr.png" />
<Resource Include="Resources\empty_folder.png" />
<Resource Include="Resources\engine.png" />
<Resource Include="Resources\gear.png" />
<Resource Include="Resources\localization.png" />
<Resource Include="Resources\materialicon.png" />
<Resource Include="Resources\pc.png" />
<Resource Include="Resources\puzzle.png" />
<Resource Include="Resources\roguecompany.png" />
<Resource Include="Resources\share-all.png" />
<Resource Include="Resources\share.png" />
<Resource Include="Resources\sign-direction-plus.png" />
<Resource Include="Resources\sign-direction-remove.png" />
<Resource Include="Resources\spellbreak.ico" />
<Resource Include="Resources\stop.png" />
<Resource Include="Resources\sod2.ico" />
<Resource Include="Resources\T-Icon-Pets-64.png" />
<Resource Include="Resources\T-Icon-Quests-64.png" />
<Resource Include="Resources\thecycle.ico" />
<Resource Include="Resources\theouterworlds.png" />
<Resource Include="Resources\spellbreak.png" />
<Resource Include="Resources\sound.png" />
<Resource Include="Resources\creative.png" />
<Resource Include="Resources\texture.png" />
<Resource Include="Resources\thecycle.png" />
<Resource Include="Resources\valorant.png" />
<Resource Include="Resources\ui.png" />
<Resource Include="Resources\weapon.png" />
<Resource Include="Resources\windows.png" />
<Resource Include="Resources\cinematics.png" />
<Resource Include="Resources\archive.png" />
<Resource Include="Resources\archive_enabled.png" />
<Resource Include="Resources\archive_disabled.png" />
<Resource Include="Resources\unknown_asset.png" />
<Resource Include="Resources\asset.png" />
<Resource Include="Resources\asset_ini.png" />
<Resource Include="Resources\asset_psd.png" />
<Resource Include="Resources\asset_png.png" />
<Resource Include="Resources\athena.png" />
<Resource Include="Resources\unix.png" />
<Resource Include="Resources\linux.png" />
<Resource Include="Resources\T_Placeholder_Item_Image.png" />
<Resource Include="Resources\T_ClipSize_Weapon_Stats.png" />
<Resource Include="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<Resource Include="Resources\T_Placeholder_Challenge_Image.png" />
<Resource Include="Resources\T_Placeholder_Item_Image.png" />
<Resource Include="Resources\T_ReloadTime_Weapon_Stats.png" />
<Resource Include="Resources\battle-breakers-item-background.png" />
<Resource Include="Resources\valorant.live.ico" />
<Resource Include="Resources\volume-minus.png" />
<Resource Include="Resources\volume-mute.png" />
<Resource Include="Resources\volume-plus.png" />
<Resource Include="Resources\wifi-strength-off.ico" />
<Resource Include="Resources\T-Icon-Pets-64.png" />
<Resource Include="Resources\T-Icon-Quests-64.png" />
<Resource Include="Resources\city_pin.png" />
<Resource Include="Resources\pin.png" />
<Resource Include="Resources\Default.png" />
<Resource Include="Resources\NoBackground.png" />
<Resource Include="Resources\NoText.png" />
<Resource Include="Resources\Flat.png" />
<Resource Include="Resources\Cataba.png" />
<Resource Include="Resources\BurbankBigCondensed-Bold.ttf" />
<Resource Include="Resources\add_directory.png" />
<Resource Include="Resources\delete.png" />
<Resource Include="Resources\edit.png" />
<Resource Include="Resources\go_to_directory.png" />
</ItemGroup>
<ItemGroup>
@ -204,11 +188,6 @@
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
@ -218,10 +197,4 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>PublicSettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>

43
FModel/FModel.sln Normal file
View File

@ -0,0 +1,43 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel.csproj", "{B1F494EA-90A6-4C24-800E-2F724A1884CA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse", "..\CUE4Parse\CUE4Parse\CUE4Parse.csproj", "{C4620341-BBB7-4384-AC7D-5082D3E0386E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Fortnite", "..\CUE4Parse\CUE4Parse-Fortnite\CUE4Parse-Fortnite.csproj", "{7765FB4C-B54D-427B-ABB6-1073687E56BD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Conversion", "..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj", "{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.Build.0 = Release|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.Build.0 = Release|Any CPU
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Release|Any CPU.Build.0 = Release|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {53DB7A15-4E15-4575-9402-0110BDF2794E}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks.Dataflow;
namespace FModel.Framework
{
public class AsyncQueue<T> : IAsyncEnumerable<T>
{
private readonly SemaphoreSlim _semaphore = new(1);
private readonly BufferBlock<T> _buffer = new();
public int Count => _buffer.Count;
public void Enqueue(T item) => _buffer.Post(item);
public async IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token = default)
{
await _semaphore.WaitAsync(token);
try
{
while (Count > 0)
{
token.ThrowIfCancellationRequested();
yield return await _buffer.ReceiveAsync(token);
}
}
finally
{
_semaphore.Release();
}
}
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Windows.Input;
namespace FModel.Framework
{
public abstract class Command : ICommand
{
public abstract void Execute(object parameter);
public abstract bool CanExecute(object parameter);
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public event EventHandler CanExecuteChanged;
}
}

View File

@ -0,0 +1,96 @@
using System;
using HarfBuzzSharp;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
using Buffer = HarfBuzzSharp.Buffer;
namespace FModel.Framework
{
public class CustomSKShaper : SKShaper
{
private const int _FONT_SIZE_SCALE = 512;
private readonly Font _font;
private readonly Buffer _buffer;
public CustomSKShaper(SKTypeface typeface) : base(typeface)
{
using (var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob())
using (var face = new Face(blob, index))
{
face.Index = index;
face.UnitsPerEm = Typeface.UnitsPerEm;
_font = new Font(face);
_font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE);
_font.SetFunctionsOpenType();
}
_buffer = new Buffer();
}
public new Result Shape(Buffer buffer, float xOffset, float yOffset, SKPaint paint)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
if (paint == null)
throw new ArgumentNullException(nameof(paint));
// do the shaping
_font.Shape(buffer);
// get the shaping results
var len = buffer.Length;
var info = buffer.GlyphInfos;
var pos = buffer.GlyphPositions;
// get the sizes
var textSizeY = paint.TextSize / _FONT_SIZE_SCALE;
var textSizeX = textSizeY * paint.TextScaleX;
var points = new SKPoint[len];
var clusters = new uint[len];
var codepoints = new uint[len];
for (var i = 0; i < len; i++)
{
// move the cursor
xOffset += pos[i].XAdvance * textSizeX;
yOffset += pos[i].YAdvance * textSizeY;
codepoints[i] = info[i].Codepoint;
clusters[i] = info[i].Cluster;
points[i] = new SKPoint(xOffset + pos[i].XOffset * textSizeX, yOffset - pos[i].YOffset * textSizeY);
}
return new Result(codepoints, clusters, points);
}
public new Result Shape(string text, SKPaint paint) => Shape(text, 0, 0, paint);
public new Result Shape(string text, float xOffset, float yOffset, SKPaint paint)
{
if (string.IsNullOrEmpty(text))
return new Result();
using var buffer = new Buffer();
switch (paint.TextEncoding)
{
case SKTextEncoding.Utf8:
buffer.AddUtf8(text);
break;
case SKTextEncoding.Utf16:
buffer.AddUtf16(text);
break;
case SKTextEncoding.Utf32:
buffer.AddUtf32(text);
break;
default:
throw new NotSupportedException("TextEncoding of type GlyphId is not supported.");
}
buffer.GuessSegmentProperties();
return Shape(buffer, xOffset, yOffset, paint);
}
}
}

View File

@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace FModel.Framework
{
public class FullyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
/// <summary>
/// Occurs when a property is changed within an item.
/// </summary>
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public FullyObservableCollection()
{
}
public FullyObservableCollection(List<T> list) : base(list)
{
ObserveAll();
}
public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
{
ObserveAll();
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action is NotifyCollectionChangedAction.Remove or
NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
}
if (e.Action is NotifyCollectionChangedAction.Add or
NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.NewItems)
item.PropertyChanged += ChildPropertyChanged;
}
base.OnCollectionChanged(e);
}
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
{
OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
}
protected override void ClearItems()
{
foreach (T item in Items)
item.PropertyChanged -= ChildPropertyChanged;
base.ClearItems();
}
private void ObserveAll()
{
foreach (T item in Items)
item.PropertyChanged += ChildPropertyChanged;
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var typedSender = (T) sender;
var i = Items.IndexOf(typedSender);
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
OnItemPropertyChanged(i, e);
}
}
/// <summary>
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// </summary>
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
{
/// <summary>
/// Gets the index in the collection for which the property change has occurred.
/// </summary>
/// <value>
/// Index in parent collection.
/// </value>
public int CollectionIndex { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index in the collection of changed item.</param>
/// <param name="name">The name of the property that changed.</param>
public ItemPropertyChangedEventArgs(int index, string name) : base(name)
{
CollectionIndex = index;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
{
}
}
}

View File

@ -0,0 +1,50 @@
using System.Text;
using System.Windows.Input;
namespace FModel.Framework
{
public class Hotkey : ViewModel
{
private Key _key;
public Key Key
{
get => _key;
set => SetProperty(ref _key, value);
}
private ModifierKeys _modifiers;
public ModifierKeys Modifiers
{
get => _modifiers;
set => SetProperty(ref _modifiers, value);
}
public Hotkey(Key key, ModifierKeys modifiers = ModifierKeys.None)
{
Key = key;
Modifiers = modifiers;
}
public bool IsTriggered(Key e)
{
return e == Key && Keyboard.Modifiers.HasFlag(Modifiers);
}
public override string ToString()
{
var str = new StringBuilder();
if (Modifiers.HasFlag(ModifierKeys.Control))
str.Append("Ctrl + ");
if (Modifiers.HasFlag(ModifierKeys.Shift))
str.Append("Shift + ");
if (Modifiers.HasFlag(ModifierKeys.Alt))
str.Append("Alt + ");
if (Modifiers.HasFlag(ModifierKeys.Windows))
str.Append("Win + ");
str.Append(Key);
return str.ToString();
}
}
}

View File

@ -0,0 +1,43 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using RestSharp;
using RestSharp.Serialization;
namespace FModel.Framework
{
public class JsonNetSerializer : IRestSerializer
{
public static readonly JsonSerializerSettings SerializerSettings = new()
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
public string Serialize(object obj)
{
return JsonConvert.SerializeObject(obj);
}
[Obsolete]
public string Serialize(Parameter parameter)
{
return JsonConvert.SerializeObject(parameter.Value);
}
public T Deserialize<T>(IRestResponse response)
{
return JsonConvert.DeserializeObject<T>(response.Content, SerializerSettings);
}
public string[] SupportedContentTypes { get; } =
{
"application/json", "application/json; charset=UTF-8"
};
public string ContentType { get; set; } = "application/json; charset=UTF-8";
public DataFormat DataFormat => DataFormat.Json;
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace FModel.Framework
{
public sealed class RangeObservableCollection<T> : ObservableCollection<T>
{
private bool _suppressNotification;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_suppressNotification)
base.OnCollectionChanged(e);
}
public void AddRange(IEnumerable<T> list)
{
if (list == null)
throw new ArgumentNullException(nameof(list));
_suppressNotification = true;
foreach (var item in list)
Add(item);
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void SetSuppressionState(bool state)
{
_suppressNotification = state;
}
public void InvokeOnCollectionChanged(NotifyCollectionChangedAction changedAction = NotifyCollectionChangedAction.Reset)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(changedAction));
}
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
namespace FModel.Framework
{
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErrorInfo
{
private readonly Dictionary<string, IList<string>> _validationErrors = new();
public string this[string propertyName]
{
get
{
if (string.IsNullOrEmpty(propertyName))
return Error;
return _validationErrors.ContainsKey(propertyName) ? string.Join(Environment.NewLine, _validationErrors[propertyName]) : string.Empty;
}
}
[JsonIgnore] public string Error => string.Join(Environment.NewLine, GetAllErrors());
[JsonIgnore] public bool HasErrors => _validationErrors.Any();
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
return _validationErrors.SelectMany(kvp => kvp.Value);
return _validationErrors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty<object>();
}
private IEnumerable<string> GetAllErrors()
{
return _validationErrors.SelectMany(kvp => kvp.Value).Where(e => !string.IsNullOrEmpty(e));
}
public void AddValidationError(string propertyName, string errorMessage)
{
if (!_validationErrors.ContainsKey(propertyName))
_validationErrors.Add(propertyName, new List<string>());
_validationErrors[propertyName].Add(errorMessage);
}
public void ClearValidationErrors(string propertyName)
{
if (_validationErrors.ContainsKey(propertyName))
_validationErrors.Remove(propertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
#pragma warning disable 67
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
#pragma warning disable 67
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
RaisePropertyChanged(propertyName);
return true;
}
}
}

View File

@ -0,0 +1,50 @@
using System;
namespace FModel.Framework
{
public abstract class ViewModelCommand<TContextViewModel> : Command where TContextViewModel : ViewModel
{
private WeakReference _parent;
public TContextViewModel ContextViewModel
{
get
{
if (_parent is {IsAlive: true})
return (TContextViewModel) _parent.Target;
return null;
}
private set
{
if (ContextViewModel == value)
return;
_parent = value != null ? new WeakReference(value) : null;
}
}
protected ViewModelCommand(TContextViewModel contextViewModel)
{
ContextViewModel = contextViewModel /*?? throw new ArgumentNullException(nameof(contextViewModel))*/;
}
public sealed override void Execute(object parameter)
{
Execute(ContextViewModel, parameter);
}
public abstract void Execute(TContextViewModel contextViewModel, object parameter);
public sealed override bool CanExecute(object parameter)
{
return CanExecute(ContextViewModel, parameter);
}
public virtual bool CanExecute(TContextViewModel contextViewModel, object parameter)
{
return true;
}
}
}

View File

@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
using System.Windows;
using FModel.PakReader.IO;
using FModel.PakReader.Pak;
using FModel.PakReader.Parsers.Objects;
using FModel.Properties;
using ToastNotifications;
using ToastNotifications.Lifetime;
using ToastNotifications.Position;
using UsmapNET.Classes;
namespace FModel
{
static class Globals
{
/// <summary>
/// string is the pak file name
/// PakFileReader is the reader where you can grab the FPakEntries, MountPoint and more
/// </summary>
public static readonly Dictionary<string, PakFileReader> CachedPakFiles = new Dictionary<string, PakFileReader>();
public static readonly Dictionary<string, FFileIoStoreReader> CachedIoStores = new Dictionary<string, FFileIoStoreReader>();
public static readonly Dictionary<string, FUnversionedType> CachedSchemas = new Dictionary<string, FUnversionedType>();
public static Usmap Usmap = null;
public static FIoGlobalData GlobalData = null;
public static readonly Notifier gNotifier = new Notifier(cfg =>
{
cfg.LifetimeSupervisor = new TimeAndCountBasedLifetimeSupervisor(TimeSpan.FromSeconds(7), MaximumNotificationCount.FromCount(15));
cfg.PositionProvider = new PrimaryScreenPositionProvider(Corner.BottomRight, 5, 5);
cfg.Dispatcher = Application.Current.Dispatcher;
});
public static bool bSearch = false; // trigger the event to select a file thank to the search window
public static string sSearch = string.Empty; // this will be the file name triggered
public static FGame Game = new FGame(EGame.Unknown, EPakVersion.LATEST, 0);
public const EFModel Build =
#if RELEASE
EFModel.Release;
#elif DEBUG
EFModel.Debug;
#else
EFModel.Unknown;
#endif
}
public class FGame
{
public EGame ActualGame;
public EPakVersion Version;
public int SubVersion;
public FGame(EGame game, EPakVersion version, int subVersion)
{
ActualGame = game;
Version = version;
SubVersion = subVersion;
}
public string GetName()
{
return ActualGame switch
{
EGame.Fortnite => Resources.GameName_Fortnite,
EGame.Valorant => Resources.GameName_Valorant,
EGame.DeadByDaylight => Resources.GameName_DeadByDaylight,
EGame.Borderlands3 => Resources.GameName_Borderlands3,
EGame.MinecraftDungeons => Resources.GameName_MinecraftDungeons,
EGame.BattleBreakers => Resources.GameName_BattleBreakers,
EGame.Spellbreak => Resources.GameName_Spellbreak,
EGame.StateOfDecay2 => Resources.GameName_StateofDecay2,
EGame.TheCycle => Resources.GameName_TheCycle,
EGame.TheOuterWorlds => Resources.GameName_TheOuterWorlds,
EGame.RogueCompany => Resources.GameName_RogueCompany,
EGame.Unknown => "Unknown",
_ => "Unknown"
};
}
}
static class FColors
{
public const string Red = "#E06C75";
public const string Orange = "#D19A66";
public const string Yellow = "#E5C07B";
public const string Purple = "#C678DD";
public const string Blue = "#61AFEF";
public const string Discord = "#8B9BD4";
public const string Green = "#98C379";
public const string LightGray = "#BBBBBB";
public const string DarkGray = "#9B9B9B";
public const string White = "#EFEFEF";
}
}

View File

@ -1,37 +0,0 @@
using FModel.Logger;
using FModel.Utils;
using FModel.Windows.CustomNotifier;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
namespace FModel.Grabber.Aes
{
static class AesData
{
public static async Task<BenResponse> GetData()
{
if (NetworkInterface.GetIsNetworkAvailable())
{
BenResponse data = await Endpoints.GetJsonEndpoint<BenResponse>(Endpoints.BENBOT_AES, string.Empty).ConfigureAwait(false);
return data;
}
else
{
Globals.gNotifier.ShowCustomMessage(Properties.Resources.AES, Properties.Resources.NoInternet, "/FModel;component/Resources/wifi-strength-off.ico");
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", "No internet");
return null;
}
}
}
public class BenResponse
{
[JsonProperty("mainKey")]
public string MainKey { get; set; }
[JsonProperty("dynamicKeys")]
public Dictionary<string, string> DynamicKeys { get; set; }
}
}

View File

@ -1,85 +0,0 @@
using FModel.Logger;
using FModel.ViewModels.MenuItem;
using FModel.Windows.CustomNotifier;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FModel.Grabber.Aes
{
static class AesGrabber
{
public static async Task<bool> Load(bool forceReload = false)
{
if (Globals.Game.ActualGame == EGame.Fortnite && MenuItems.pakFiles.AtLeastOnePak())
{
if (forceReload)
{
Dictionary<string, string> staticKeys = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(Properties.Settings.Default.StaticAesKeys))
staticKeys = JsonConvert.DeserializeObject<Dictionary<string, string>>(Properties.Settings.Default.StaticAesKeys);
Dictionary<string, Dictionary<string, string>> oldDynamicKeys = new Dictionary<string, Dictionary<string, string>>();
try
{
if (!string.IsNullOrEmpty(Properties.Settings.Default.DynamicAesKeys))
oldDynamicKeys = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string>>>(Properties.Settings.Default.DynamicAesKeys);
}
catch (JsonSerializationException) { /* Needed for the transition bewteen global dynamic keys and "per game" dynamic keys */ }
BenResponse benResponse = await AesData.GetData().ConfigureAwait(false);
if (benResponse != null)
{
if (!string.IsNullOrEmpty(benResponse.MainKey))
{
string mainKey = $"0x{benResponse.MainKey[2..].ToUpper()}";
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"BenBot Main key is {mainKey}");
staticKeys[Globals.Game.ActualGame.ToString()] = mainKey;
Properties.Settings.Default.StaticAesKeys = JsonConvert.SerializeObject(staticKeys, Formatting.None);
}
if (oldDynamicKeys.TryGetValue(Globals.Game.ActualGame.ToString(), out var gameDict))
{
Dictionary<string, string> difference = benResponse.DynamicKeys
.Where(x => !x.Key.Contains("optional") && (!gameDict.ContainsKey(x.Key) || !gameDict[x.Key].Equals(x.Value)))
.ToDictionary(x => x.Key, x => x.Value);
foreach (KeyValuePair<string, string> KvP in difference)
{
Globals.gNotifier.ShowCustomMessage(
Properties.Resources.PakFiles,
string.Format(
Properties.Resources.PakCanBeOpened,
KvP.Key[(KvP.Key.IndexOf("Paks/") + "Paks/".Length)..]),
"/FModel;component/Resources/lock-open-variant.ico");
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"{KvP.Key} with key {KvP.Value} can be opened");
}
}
foreach (var (key, value) in benResponse.DynamicKeys.ToList())
{
if (key.Contains("optional"))
{
if (!benResponse.DynamicKeys.TryGetValue(key.Replace("optional", ""), out string _))
benResponse.DynamicKeys[key.Replace("optional", "")] = value;
}
else
{
if (!benResponse.DynamicKeys.TryGetValue(key.Replace("-WindowsClient", "optional-WindowsClient"), out string _))
benResponse.DynamicKeys[key.Replace("-WindowsClient", "optional-WindowsClient")] = value;
}
}
oldDynamicKeys[Globals.Game.ActualGame.ToString()] = benResponse.DynamicKeys;
Properties.Settings.Default.DynamicAesKeys = JsonConvert.SerializeObject(oldDynamicKeys, Formatting.None);
Properties.Settings.Default.Save();
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"BenBot Dynamic keys are {Properties.Settings.Default.DynamicAesKeys}");
return true;
}
}
}
return false;
}
}
}

View File

@ -1,53 +0,0 @@
using AutoUpdaterDotNET;
using FModel.Logger;
using FModel.Utils;
using FModel.Windows.CustomNotifier;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
namespace FModel.Grabber.Cdn
{
static class CdnData
{
public static readonly bool bInternet = NetworkInterface.GetIsNetworkAvailable();
public static async Task<CdnResponse> GetData()
{
if (bInternet)
return await Endpoints.GetJsonEndpoint<CdnResponse>(Endpoints.FMODEL_JSON).ConfigureAwait(false);
else
{
Globals.gNotifier.ShowCustomMessage("CDN", Properties.Resources.NoInternet, "/FModel;component/Resources/wifi-strength-off.ico");
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[CDN]", "No internet");
return null;
}
}
}
public class CdnResponse
{
public Dictionary<string, Backup[]> Backups { get; set; }
public Dictionary<string, GlobalMessage[]> GlobalMessages { get; set; }
public FUpdater Updater { get; set; }
}
public class Backup
{
public string Header { get; set; }
public string DownloadUrl { get; set; }
public double Size { get; set; }
}
public class GlobalMessage
{
public string Message { get; set; }
public string Color { get; set; }
public bool NewLine { get; set; }
}
public class FUpdater
{
public string Version { get; set; }
public string Url { get; set; }
public string Changelog { get; set; }
public Mandatory Mandatory { get; set; }
}
}

View File

@ -1,102 +0,0 @@
using FModel.Logger;
using FModel.Utils;
using FModel.ViewModels.MenuItem;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
namespace FModel.Grabber.Cdn
{
static class CdnDataGrabber
{
private static CdnResponse _data = null; // avoid multiple requests on same endpoint
public static async Task DoCDNStuff()
{
await PopulateBackups().ConfigureAwait(false); // step by step
await ShowGMessages().ConfigureAwait(false); // step by step
}
public static async Task PopulateBackups()
{
Application.Current.Dispatcher.Invoke(delegate
{
// Backup PAKs Menu Item
MenuItems.backupFiles.Add(new BackupMenuItemViewModel
{
Header = Properties.Resources.BackupPaks,
Icon = new Image { Source = new BitmapImage(new Uri("Resources/backup-restore.png", UriKind.Relative)) }
});
MenuItems.backupFiles.Add(new Separator { });
});
List<BackupMenuItemViewModel> backupsInfos = await GetBackups().ConfigureAwait(false);
if (backupsInfos.Any())
{
foreach (BackupMenuItemViewModel b in backupsInfos)
{
Application.Current.Dispatcher.Invoke(delegate
{
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[CDN]", $"{b.Header} is available to download");
MenuItems.backupFiles.Add(b);
});
}
}
}
public static async Task<FUpdater> GetUpdateData()
{
if (_data == null)
_data = await CdnData.GetData().ConfigureAwait(false);
if (_data != null)
{
return _data.Updater;
}
return null;
}
private static async Task<List<BackupMenuItemViewModel>> GetBackups()
{
if (_data == null)
_data = await CdnData.GetData().ConfigureAwait(false);
if (_data != null && _data.Backups.TryGetValue(Globals.Game.ActualGame.ToString(), out var backups))
{
return JsonConvert.DeserializeObject<List<BackupMenuItemViewModel>>(JsonConvert.SerializeObject(backups));
}
return new List<BackupMenuItemViewModel>();
}
public static async Task ShowGMessages()
{
Dictionary<string, GlobalMessage[]> globalMessages = await GetGlobalMessages().ConfigureAwait(false);
if (globalMessages.Any())
{
string version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
if (globalMessages.ContainsKey(version))
if (!string.IsNullOrEmpty(globalMessages[version][0].Message))
foreach (GlobalMessage gm in globalMessages[version])
FConsole.AppendText(gm.Message, gm.Color, gm.NewLine);
}
}
private static async Task<Dictionary<string, GlobalMessage[]>> GetGlobalMessages()
{
if (_data == null && CdnData.bInternet) // if data is still null after getting backups, that means there's no internet, do not try again
_data = await CdnData.GetData().ConfigureAwait(false); // ^ will also avoid showing "No internet" notifier twice
if (_data != null)
{
return JsonConvert.DeserializeObject<Dictionary<string, GlobalMessage[]>>(JsonConvert.SerializeObject(_data.GlobalMessages));
}
return new Dictionary<string, GlobalMessage[]>();
}
}
}

View File

@ -1,38 +0,0 @@
using EpicManifestParser.Objects;
using FModel.Utils;
using System;
using System.Threading.Tasks;
namespace FModel.Grabber.Manifests
{
static class ManifestGrabber
{
public static async Task<ManifestInfo> TryGetLatestManifestInfo()
{
if (IsExpired())
{
OAuth auth = await Endpoints.GetOAuthInfo().ConfigureAwait(false);
if (auth != null)
{
Properties.Settings.Default.AccessToken = auth.AccessToken;
Properties.Settings.Default.LauncherExpiration = DateTimeOffset.Now.AddSeconds(Convert.ToDouble(auth.ExpiresIn)).ToUnixTimeMilliseconds();
Properties.Settings.Default.Save();
}
}
string ret = await Endpoints.GetStringEndpoint("https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live", Properties.Settings.Default.AccessToken).ConfigureAwait(false);
return string.IsNullOrEmpty(ret) ? null : new ManifestInfo(ret);
}
private static bool IsExpired()
{
if (string.IsNullOrEmpty(Properties.Settings.Default.AccessToken)) return true;
long currentTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
return currentTime - 60000 >= Properties.Settings.Default.LauncherExpiration;
}
}
}

View File

@ -1,780 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using FModel.PakReader;
using FModel.Utils;
using Ionic.Zlib;
namespace FModel.Grabber.Manifests
{
public class ValorantAPIManifestV1
{
private const string _url = "https://fmodel.fortnite-api.com/valorant/v1/manifest";
private readonly HttpClient _client;
private readonly DirectoryInfo _chunkDirectory;
public readonly ulong Id;
public readonly Dictionary<ulong, ValorantChunkV1> Chunks;
public readonly ValorantPakV1[] Paks;
public ValorantAPIManifestV1(byte[] data, DirectoryInfo directoryInfo) : this(new MemoryStream(data, false), directoryInfo) { }
public ValorantAPIManifestV1(Stream stream, DirectoryInfo directoryInfo) : this(new BinaryReader(stream), directoryInfo) { }
public ValorantAPIManifestV1(BinaryReader reader, DirectoryInfo directoryInfo)
{
using (reader)
{
Id = reader.ReadUInt64();
var chunks = reader.ReadInt32();
Chunks = new Dictionary<ulong, ValorantChunkV1>(chunks);
for (var i = 0; i < chunks; i++)
{
var chunk = new ValorantChunkV1(reader);
Chunks.Add(chunk.Id, chunk);
}
Paks = reader.ReadTArray(() => new ValorantPakV1(reader));
}
_client = new HttpClient(new HttpClientHandler
{
UseProxy = false,
UseCookies = false,
AutomaticDecompression = DecompressionMethods.All,
CheckCertificateRevocationList = false,
PreAuthenticate = false,
MaxConnectionsPerServer = 1337
});
_chunkDirectory = directoryInfo;
}
public Stream GetPakStream(int index)
{
return new ValorantPakV1Stream(this, index);
}
public async Task PrefetchChunk(ValorantChunkV1 chunk, CancellationToken cancellationToken)
{
var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk");
if (File.Exists(chunkPath))
{
return;
}
using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url);
using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
var chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
}
#if DEBUG
else
{
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Debugger.Break();
}
#endif
}
public async Task<byte[]> GetChunkBytes(ValorantChunkV1 chunk, CancellationToken cancellationToken)
{
var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk");
byte[] chunkBytes;
if (File.Exists(chunkPath))
{
chunkBytes = new byte[chunk.Size];
await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read);
await fs.ReadAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
}
else
{
using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url);
using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
}
else
{
#if DEBUG
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Debugger.Break();
#endif
chunkBytes = null;
}
}
return chunkBytes;
}
public static async Task<ValorantAPIManifestV1> DownloadAndParse(DirectoryInfo directoryInfo)
{
using var client = new HttpClient(new HttpClientHandler
{
UseProxy = false,
UseCookies = false,
AutomaticDecompression = DecompressionMethods.All,
CheckCertificateRevocationList = false,
PreAuthenticate = false
});
using var request = new HttpRequestMessage(HttpMethod.Get, _url);
try
{
using var response = await client.SendAsync(request).ConfigureAwait(false);
if (response.StatusCode != HttpStatusCode.OK)
{
return null;
}
var responseStream = await response.Content.ReadAsStreamAsync();
return new ValorantAPIManifestV1(responseStream, directoryInfo);
}
catch
{
return null;
}
}
}
public class ValorantAPIManifestV2
{
private const string _url = "https://fmodel.fortnite-api.com/valorant/v2/manifest";
private readonly HttpClient _client;
private readonly DirectoryInfo _chunkDirectory;
public readonly ValorantAPIManifestHeaderV2 Header;
public readonly ValorantChunkV2[] Chunks;
public readonly ValorantPakV2[] Paks;
public ValorantAPIManifestV2(byte[] data, DirectoryInfo directoryInfo) : this(new MemoryStream(data, false), directoryInfo) { }
public ValorantAPIManifestV2(Stream stream, DirectoryInfo directoryInfo) : this(new BinaryReader(stream), directoryInfo) { }
public ValorantAPIManifestV2(BinaryReader reader, DirectoryInfo directoryInfo)
{
using (reader)
{
Header = new ValorantAPIManifestHeaderV2(reader);
var compressedBuffer = reader.ReadBytes((int)Header.CompressedSize);
var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
if (uncompressedBuffer.Length != Header.UncompressedSize)
{
throw new FileLoadException("invalid decompressed manifest body");
}
using var bodyMs = new MemoryStream(uncompressedBuffer, false);
using var bodyReader = new BinaryReader(bodyMs);
Chunks = new ValorantChunkV2[Header.ChunkCount];
for (var i = 0u; i < Header.ChunkCount; i++)
{
Chunks[i] = new ValorantChunkV2(bodyReader);
}
Paks = new ValorantPakV2[Header.PakCount];
for (var i = 0u; i < Header.PakCount; i++)
{
Paks[i] = new ValorantPakV2(bodyReader);
}
}
_client = new HttpClient(new HttpClientHandler
{
UseProxy = false,
UseCookies = false,
AutomaticDecompression = DecompressionMethods.All,
CheckCertificateRevocationList = false,
PreAuthenticate = false,
MaxConnectionsPerServer = 1337,
UseDefaultCredentials = false,
AllowAutoRedirect = false
});
_chunkDirectory = directoryInfo;
}
public Stream GetPakStream(int index)
{
return new ValorantPakV2Stream(this, index);
}
public async Task PrefetchChunk(ValorantChunkV2 chunk, CancellationToken cancellationToken)
{
var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk");
if (File.Exists(chunkPath))
{
return;
}
using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url);
using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
var chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
}
#if DEBUG
else
{
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Debugger.Break();
}
#endif
}
public async Task<byte[]> GetChunkBytes(ValorantChunkV2 chunk, CancellationToken cancellationToken)
{
var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk");
byte[] chunkBytes;
if (File.Exists(chunkPath))
{
chunkBytes = new byte[chunk.Size];
await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read);
await fs.ReadAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
}
else
{
using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url);
using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
}
else
{
#if DEBUG
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Debugger.Break();
#endif
chunkBytes = null;
}
}
return chunkBytes;
}
public static async Task<ValorantAPIManifestV2> DownloadAndParse(DirectoryInfo directoryInfo)
{
using var client = new HttpClient(new HttpClientHandler
{
UseProxy = false,
UseCookies = false,
AutomaticDecompression = DecompressionMethods.All,
CheckCertificateRevocationList = false,
PreAuthenticate = false
});
using var request = new HttpRequestMessage(HttpMethod.Get, _url);
try
{
using var response = await client.SendAsync(request).ConfigureAwait(false);
if (response.StatusCode != HttpStatusCode.OK)
{
return null;
}
var responseStream = await response.Content.ReadAsStreamAsync();
return new ValorantAPIManifestV2(responseStream, directoryInfo);
}
catch
{
return null;
}
}
}
public readonly struct ValorantAPIManifestHeaderV2
{
public const uint MAGIC = 0xC3D088F7u;
public readonly uint Magic;
public readonly uint HeaderSize;
public readonly ulong ManifestId;
public readonly uint UncompressedSize;
public readonly uint CompressedSize;
public readonly uint ChunkCount;
public readonly uint PakCount;
public readonly string GameVersion;
public ValorantAPIManifestHeaderV2(BinaryReader reader)
{
Magic = reader.ReadUInt32();
if (Magic != MAGIC)
{
throw new FileLoadException("invalid manifest magic");
}
HeaderSize = reader.ReadUInt32();
ManifestId = reader.ReadUInt64();
UncompressedSize = reader.ReadUInt32();
CompressedSize = reader.ReadUInt32();
ChunkCount = reader.ReadUInt32();
PakCount = reader.ReadUInt32();
var gameVersionLength = (int)reader.ReadByte();
if (gameVersionLength == 0)
{
GameVersion = null;
}
else
{
var gameVersionBuffer = reader.ReadBytes(gameVersionLength);
GameVersion = Encoding.ASCII.GetString(gameVersionBuffer);
}
reader.BaseStream.Position = HeaderSize;
}
}
public readonly struct ValorantChunkV1
{
private const string _baseUrl = "https://fmodel.fortnite-api.com/valorant/v1/chunks/";
public readonly ulong Id;
public readonly uint Size;
public string Url => _baseUrl + Id;
public ValorantChunkV1(BinaryReader reader)
{
Id = reader.ReadUInt64();
Size = reader.ReadUInt32();
}
public override string ToString()
{
return $"{Id:X8} | {Strings.GetReadableSize(Size)}";
}
}
public readonly struct ValorantChunkV2
{
private const string _baseUrl = "https://fmodel.fortnite-api.com/valorant/v2/chunks/";
public readonly ulong Id;
public readonly uint Size;
public string Url => $"{_baseUrl}{Id}";
public ValorantChunkV2(BinaryReader reader)
{
Id = reader.ReadUInt64();
Size = reader.ReadUInt32();
}
public override string ToString()
{
return $"{Id:X8} | {Strings.GetReadableSize(Size)}";
}
}
public readonly struct ValorantPakV1
{
public readonly ulong Id;
public readonly uint Size;
public readonly string Name;
public readonly ulong[] ChunkIds;
public ValorantPakV1(BinaryReader reader)
{
Id = reader.ReadUInt64();
Size = reader.ReadUInt32();
var nameLength = reader.ReadInt32();
var nameBytes = reader.ReadBytes(nameLength);
Name = Encoding.ASCII.GetString(nameBytes);
ChunkIds = new ulong[reader.ReadInt32()];
for (var i = 0; i < ChunkIds.Length; i++)
{
ChunkIds[i] = reader.ReadUInt64();
}
}
public override string ToString()
{
return $"{Name} | {Strings.GetReadableSize(Size)}";
}
}
public readonly struct ValorantPakV2
{
public readonly ulong Id;
public readonly uint Size;
public readonly uint[] ChunkIndices;
public readonly string Name;
public ValorantPakV2(BinaryReader reader)
{
Id = reader.ReadUInt64();
Size = reader.ReadUInt32();
var chunkIndicesLength = reader.ReadUInt32();
ChunkIndices = new uint[chunkIndicesLength];
for (uint i = 0; i < chunkIndicesLength; i++)
{
ChunkIndices[i] = reader.ReadUInt32();
}
var nameLength = (int)reader.ReadByte();
var nameBytes = reader.ReadBytes(nameLength);
Name = Encoding.ASCII.GetString(nameBytes);
}
public override string ToString()
{
return $"{Name} | {Strings.GetReadableSize(Size)}";
}
}
public class ValorantPakV1Stream : Stream
{
public override bool CanRead { get; } = true;
public override bool CanSeek { get; } = true;
public override bool CanWrite { get; } = false;
public override long Length { get; }
private long _position;
public override long Position
{
get => _position;
set
{
if (value >= Length || value < 0)
{
throw new ArgumentOutOfRangeException();
}
_position = value;
}
}
public string FileName { get; }
private readonly ValorantAPIManifestV1 _manifest;
private readonly ValorantChunkV1[] _chunks;
public ValorantPakV1Stream(ValorantAPIManifestV1 manifest, int pakIndex)
{
_manifest = manifest;
var pak = manifest.Paks[pakIndex];
FileName = pak.Name;
Length = pak.Size;
_chunks = new ValorantChunkV1[pak.ChunkIds.Length];
for (var i = 0; i < _chunks.Length; i++)
{
_chunks[i] = manifest.Chunks[pak.ChunkIds[i]];
}
}
public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
}
public async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
{
var tasks = new List<Task>();
var sem = new SemaphoreSlim(concurrentDownloads);
while (count > 0)
{
await sem.WaitAsync(cancellationToken).ConfigureAwait(false);
var chunk = _chunks[i++];
tasks.Add(PrefetchChunkAsync(chunk));
if (i == _chunks.Length)
{
break;
}
count -= chunk.Size - startPos;
startPos = 0u;
}
await Task.WhenAll(tasks).ConfigureAwait(false);
sem.Dispose();
async Task PrefetchChunkAsync(ValorantChunkV1 chunk)
{
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
sem.Release();
}
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var (i, startPos) = GetChunkIndex(_position);
if (i == -1)
{
return 0;
}
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
var bytesRead = 0;
while (true)
{
var chunk = _chunks[i];
var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false);
var chunkBytes = chunk.Size - startPos;
var bytesLeft = count - bytesRead;
if (bytesLeft <= chunkBytes)
{
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint)bytesLeft);
bytesRead += bytesLeft;
break;
}
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes);
bytesRead += (int)chunkBytes;
startPos = 0u;
if (++i == _chunks.Length)
{
break;
}
}
_position += bytesRead;
return bytesRead;
}
private (int Index, uint ChunkPos) GetChunkIndex(long position)
{
for (var i = 0; i < _chunks.Length; i++)
{
var size = _chunks[i].Size;
if (position < size)
{
return (i, (uint)position);
}
position -= size;
}
return (-1, 0u);
}
public override long Seek(long offset, SeekOrigin origin)
{
Position = origin switch
{
SeekOrigin.Begin => offset,
SeekOrigin.Current => offset + _position,
SeekOrigin.End => Length + offset,
_ => throw new ArgumentOutOfRangeException()
};
return _position;
}
public override void Flush()
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
public class ValorantPakV2Stream : Stream
{
public override bool CanRead { get; } = true;
public override bool CanSeek { get; } = true;
public override bool CanWrite { get; } = false;
public override long Length { get; }
private long _position;
public override long Position
{
get => _position;
set
{
if (value >= Length || value < 0)
{
throw new ArgumentOutOfRangeException();
}
_position = value;
}
}
public string FileName { get; }
private readonly ValorantAPIManifestV2 _manifest;
private readonly ValorantChunkV2[] _chunks;
public ValorantPakV2Stream(ValorantAPIManifestV2 manifest, int pakIndex)
{
_manifest = manifest;
var pak = manifest.Paks[pakIndex];
FileName = pak.Name;
Length = pak.Size;
_chunks = new ValorantChunkV2[pak.ChunkIndices.Length];
for (var i = 0; i < pak.ChunkIndices.Length; i++)
{
_chunks[i] = manifest.Chunks[pak.ChunkIndices[i]];
}
}
public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
}
public async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
{
var tasks = new List<Task>();
var sem = new SemaphoreSlim(concurrentDownloads);
while (count > 0)
{
await sem.WaitAsync(cancellationToken).ConfigureAwait(false);
var chunk = _chunks[i++];
tasks.Add(PrefetchChunkAsync(chunk));
if (i == _chunks.Length)
{
break;
}
count -= chunk.Size - startPos;
startPos = 0u;
}
await Task.WhenAll(tasks).ConfigureAwait(false);
sem.Dispose();
async Task PrefetchChunkAsync(ValorantChunkV2 chunk)
{
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
sem.Release();
}
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var (i, startPos) = GetChunkIndex(_position);
if (i == -1)
{
return 0;
}
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
var bytesRead = 0;
while (true)
{
var chunk = _chunks[i];
var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false);
var chunkBytes = chunk.Size - startPos;
var bytesLeft = count - bytesRead;
if (bytesLeft <= chunkBytes)
{
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint)bytesLeft);
bytesRead += bytesLeft;
break;
}
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes);
bytesRead += (int)chunkBytes;
startPos = 0u;
if (++i == _chunks.Length)
{
break;
}
}
_position += bytesRead;
return bytesRead;
}
private (int Index, uint ChunkPos) GetChunkIndex(long position)
{
for (var i = 0; i < _chunks.Length; i++)
{
var size = _chunks[i].Size;
if (position < size)
{
return (i, (uint)position);
}
position -= size;
}
return (-1, 0u);
}
public override long Seek(long offset, SeekOrigin origin)
{
Position = origin switch
{
SeekOrigin.Begin => offset,
SeekOrigin.Current => offset + _position,
SeekOrigin.End => Length + offset,
_ => throw new ArgumentOutOfRangeException()
};
return _position;
}
public override void Flush()
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
}

View File

@ -1,50 +0,0 @@
using FModel.Logger;
using FModel.Utils;
using FModel.Windows.CustomNotifier;
using Newtonsoft.Json;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
namespace FModel.Grabber.Mappings
{
static class MappingsData
{
public static async Task<Mapping[]> GetData()
{
if (NetworkInterface.GetIsNetworkAvailable())
{
Mapping[] data = await Endpoints.GetJsonEndpoint<Mapping[]>(Endpoints.BENBOT_MAPPINGS, string.Empty).ConfigureAwait(false);
return data;
}
else
{
Globals.gNotifier.ShowCustomMessage("Mappings", Properties.Resources.NoInternet, "/FModel;component/Resources/wifi-strength-off.ico");
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Mappings]", "No internet");
return null;
}
}
}
public class Mapping
{
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("fileName")]
public string FileName { get; set; }
[JsonProperty("hash")]
public string Hash { get; set; }
[JsonProperty("length")]
public string Length { get; set; }
[JsonProperty("uploaded")]
public string Uploaded { get; set; }
[JsonProperty("meta")]
public Metadata Meta { get; set; }
}
public class Metadata
{
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("compressionMethod")]
public string CompressionMethod { get; set; }
}
}

View File

@ -1,57 +0,0 @@
using FModel.Utils;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UsmapNET.Classes;
namespace FModel.Grabber.Mappings
{
static class MappingsGrabber
{
public static async Task<bool> Load(bool forceReload = false)
{
if (Globals.Game.ActualGame == EGame.Fortnite)
{
Mapping[] benMappings = await MappingsData.GetData().ConfigureAwait(false);
if (benMappings != null)
{
foreach (Mapping mapping in benMappings)
{
if (mapping.Meta.CompressionMethod == "Brotli")
{
DirectoryInfo chunksDir = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"));
string mappingPath = Path.Combine(chunksDir.FullName, mapping.FileName);
byte[] mappingsData;
if (!forceReload && File.Exists(mappingPath))
{
mappingsData = await File.ReadAllBytesAsync(mappingPath);
}
else
{
mappingsData = await Endpoints.GetRawDataAsync(new Uri(mapping.Url)).ConfigureAwait(false);
await File.WriteAllBytesAsync(mappingPath, mappingsData).ConfigureAwait(false);
}
FConsole.AppendText($"Mappings pulled from {mapping.FileName}", FColors.Yellow, true);
Globals.Usmap = new Usmap(mappingsData);
return true;
}
}
}
var latestUsmaps = new DirectoryInfo(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks")).GetFiles("*_br.usmap");
if (Globals.Usmap == null && latestUsmaps.Length > 0)
{
var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last();
byte[] mappingsData = await File.ReadAllBytesAsync(latestUsmapInfo.FullName);
FConsole.AppendText($"Mappings pulled from {latestUsmapInfo.Name}", FColors.Yellow, true);
Globals.Usmap = new Usmap(mappingsData);
return true;
}
}
return false;
}
}
}

View File

@ -1,11 +0,0 @@
using Newtonsoft.Json;
using System.Collections.Generic;
namespace FModel.Grabber.Paks
{
public class InstallsJson
{
[JsonProperty("associated_client")]
public Dictionary<string, string> AssociatedClient;
}
}

View File

@ -1,14 +0,0 @@
namespace FModel.Grabber.Paks
{
public class LauncherDat
{
public InstallationList[] InstallationList;
}
public class InstallationList
{
public string InstallLocation;
public string AppName;
public string AppVersion;
}
}

View File

@ -1,7 +0,0 @@
namespace FModel.Grabber.Paks
{
public class LauncherSettings
{
public string productLibraryDir;
}
}

View File

@ -1,289 +0,0 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using EpicManifestParser.Objects;
using FModel.Grabber.Manifests;
using FModel.Logger;
using FModel.PakReader.IO;
using FModel.PakReader.Pak;
using FModel.Utils;
using FModel.ViewModels.MenuItem;
using FModel.Windows.Launcher;
namespace FModel.Grabber.Paks
{
static class PaksGrabber
{
private static readonly Regex _pakFileRegex = new Regex(@"FortniteGame(/|\\)Content(/|\\)Paks(/|\\)(pakchunk(?:0|10.*|\w+)-WindowsClient|global)\.(pak|ucas)$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
public static async Task PopulateMenu()
{
await Application.Current.Dispatcher.InvokeAsync(delegate
{
PopulateBase();
});
await Task.Run(async () =>
{
if (string.IsNullOrEmpty(Properties.Settings.Default.PakPath))
{
await Application.Current.Dispatcher.InvokeAsync(delegate
{
var launcher = new FLauncher();
bool? result = launcher.ShowDialog();
if (result.HasValue && result.Value)
{
Properties.Settings.Default.PakPath = launcher.Path;
Properties.Settings.Default.Save();
}
});
}
// Add Pak Files
if (Properties.Settings.Default.PakPath.EndsWith("-fn.manifest"))
{
ManifestInfo manifestInfo = await ManifestGrabber.TryGetLatestManifestInfo().ConfigureAwait(false);
if (manifestInfo == null)
{
throw new Exception("Failed to load latest manifest.");
}
DirectoryInfo chunksDir = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"));
string manifestPath = Path.Combine(chunksDir.FullName, manifestInfo.Filename);
byte[] manifestData;
if (File.Exists(manifestPath))
{
manifestData = await File.ReadAllBytesAsync(manifestPath);
}
else
{
manifestData = await manifestInfo.DownloadManifestDataAsync().ConfigureAwait(false);
await File.WriteAllBytesAsync(manifestPath, manifestData).ConfigureAwait(false);
}
Manifest manifest = new Manifest(manifestData, new ManifestOptions
{
ChunkBaseUri = new Uri("http://epicgames-download1.akamaized.net/Builds/Fortnite/CloudDir/ChunksV3/", UriKind.Absolute),
ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"))
});
int pakFiles = 0;
foreach (FileManifest fileManifest in manifest.FileManifests)
{
if (!_pakFileRegex.IsMatch(fileManifest.Name))
{
continue;
}
var pakStream = fileManifest.GetStream();
if (pakStream.Length == 365) continue;
var pakFileName = fileManifest.Name.Replace('/', '\\');
if (pakFileName.EndsWith(".pak"))
{
PakFileReader pakFile = new PakFileReader(pakFileName, pakStream);
if (pakFiles++ == 0)
{
// define the current game thank to the pak path
Folders.SetGameName(pakFileName);
Globals.Game.Version = pakFile.Info.Version;
Globals.Game.SubVersion = pakFile.Info.SubVersion;
}
await Application.Current.Dispatcher.InvokeAsync(delegate
{
MenuItems.pakFiles.Add(new PakMenuItemViewModel
{
PakFile = pakFile,
IsEnabled = false
});
});
}
else if (pakFileName.EndsWith(".ucas"))
{
var utocStream = manifest.FileManifests.FirstOrDefault(x => x.Name.Equals(fileManifest.Name.Replace(".ucas", ".utoc")));
var ioStore = new FFileIoStoreReader(pakFileName.SubstringAfterLast('\\'), pakFileName.SubstringBeforeLast('\\'), utocStream.GetStream(), pakStream);
await Application.Current.Dispatcher.InvokeAsync(delegate
{
MenuItems.pakFiles.Add(new PakMenuItemViewModel
{
IoStore = ioStore,
IsEnabled = false
});
});
}
}
FConsole.AppendText($"Fortnite-Manifest version: {manifest.Version}-{manifest.CL}", FColors.Yellow, true);
}
else if (Properties.Settings.Default.PakPath.EndsWith("-val.manifest"))
{
//var manifest = await ValorantAPIManifestV1.DownloadAndParse(Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"))).ConfigureAwait(false);
var manifest = await ValorantAPIManifestV2.DownloadAndParse(Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"))).ConfigureAwait(false);
if (manifest == null)
{
throw new Exception("Failed to load latest manifest.");
}
for (var i = 0; i < manifest.Paks.Length; i++)
{
var pak = manifest.Paks[i];
var pakFileName = @$"ShooterGame\Content\Paks\{pak.Name}";
var pakFile = new PakFileReader(pakFileName, manifest.GetPakStream(i));
if (i == 0)
{
Folders.SetGame(EGame.Valorant);
Globals.Game.Version = pakFile.Info.Version;
Globals.Game.SubVersion = pakFile.Info.SubVersion;
}
await Application.Current.Dispatcher.InvokeAsync(delegate
{
MenuItems.pakFiles.Add(new PakMenuItemViewModel
{
PakFile = pakFile,
IsEnabled = false
});
});
}
FConsole.AppendText($"Valorant-Manifest version: {manifest.Header.GameVersion ?? "Unknown"}", FColors.Yellow, true);
}
else if (Directory.Exists(Properties.Settings.Default.PakPath))
{
// define the current game thank to the pak path
Folders.SetGameName(Properties.Settings.Default.PakPath);
// paks
string[] paks = Directory.GetFiles(Properties.Settings.Default.PakPath);
for (int i = 0; i < paks.Length; i++)
{
var pakInfo = new FileInfo(paks[i]);
if (pakInfo.Length == 365)
{
continue;
}
if (!Utils.Paks.IsFileReadLocked(pakInfo))
{
if (paks[i].EndsWith(".pak"))
{
PakFileReader pakFile = new PakFileReader(paks[i]);
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Registering]", $"{pakFile.FileName} with GUID {pakFile.Info.EncryptionKeyGuid.Hex}");
if (i == 0)
{
Globals.Game.Version = pakFile.Info.Version;
Globals.Game.SubVersion = pakFile.Info.SubVersion;
}
await Application.Current.Dispatcher.InvokeAsync(delegate
{
MenuItems.pakFiles.Add(new PakMenuItemViewModel
{
PakFile = pakFile,
IsEnabled = false
});
});
}
else if (paks[i].EndsWith(".ucas"))
{
var utoc = paks[i].Replace(".ucas", ".utoc");
if (!Utils.Paks.IsFileReadLocked(new FileInfo(utoc)))
{
var utocStream = File.OpenRead(utoc);
var ucasStream = File.OpenRead(paks[i]);
var ioStore = new FFileIoStoreReader(paks[i].SubstringAfterLast('\\'), paks[i].SubstringBeforeLast('\\'), utocStream, ucasStream);
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[IO Store]", "[Registering]", $"{ioStore.FileName} with GUID {ioStore.TocResource.Header.EncryptionKeyGuid.Hex}");
await Application.Current.Dispatcher.InvokeAsync(delegate
{
MenuItems.pakFiles.Add(new PakMenuItemViewModel
{
IoStore = ioStore,
IsEnabled = false
});
});
}
else
{
FConsole.AppendText(string.Format(Properties.Resources.PakFileLocked, Path.GetFileNameWithoutExtension(utoc)), FColors.Red, true);
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[IO Store]", "[Locked]", utoc);
}
}
}
else
{
FConsole.AppendText(string.Format(Properties.Resources.PakFileLocked, Path.GetFileNameWithoutExtension(paks[i])), FColors.Red, true);
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Locked]", paks[i]);
}
}
}
});
}
private static void PopulateBase()
{
// Loading Mode
PakMenuItemViewModel parent = new PakMenuItemViewModel
{
Header = $"{Properties.Resources.LoadingMode} 🠞 {Properties.Resources.Default}",
Icon = new Image { Source = new BitmapImage(new Uri("Resources/progress-download.png", UriKind.Relative)) }
};
parent.Childrens = new ObservableCollection<PakMenuItemViewModel>
{
new PakMenuItemViewModel // Default Mode
{
Header = Properties.Resources.Default,
Parent = parent,
IsCheckable = true,
IsChecked = true,
StaysOpenOnClick = true
},
new PakMenuItemViewModel
{
Header = Properties.Resources.NewFiles,
Parent = parent,
IsCheckable = true,
StaysOpenOnClick = true
},
new PakMenuItemViewModel
{
Header = Properties.Resources.ModifiedFiles,
Parent = parent,
IsCheckable = true,
StaysOpenOnClick = true
},
new PakMenuItemViewModel
{
Header = Properties.Resources.NewModifiedFiles,
Parent = parent,
IsCheckable = true,
StaysOpenOnClick = true
}
};
MenuItems.pakFiles.Add(parent);
// Load All
MenuItems.pakFiles.Add(new PakMenuItemViewModel
{
Header = Properties.Resources.LoadAll,
Icon = new Image { Source = new BitmapImage(new Uri("Resources/folder-download.png", UriKind.Relative)) },
IsEnabled = false
});
// Separator
MenuItems.pakFiles.Add(new Separator { });
}
}
}

88
FModel/Helper.cs Normal file
View File

@ -0,0 +1,88 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
namespace FModel
{
public static class Helper
{
[StructLayout(LayoutKind.Explicit)]
private struct NanUnion
{
[FieldOffset(0)]
internal double DoubleValue;
[FieldOffset(0)]
internal readonly ulong UlongValue;
}
public static void OpenWindow<T>(string windowName, Action action) where T : Window
{
if (!IsWindowOpen<T>(windowName))
{
action();
}
else
{
var w = GetOpenedWindow<T>(windowName);
if (windowName == "Search View") w.WindowState = WindowState.Normal;
w.Focus();
}
}
public static T GetWindow<T>(string windowName, Action action) where T : Window
{
if (!IsWindowOpen<T>(windowName))
{
action();
}
var ret = (T) GetOpenedWindow<T>(windowName);
ret.Focus();
ret.Activate();
return ret;
}
public static void CloseWindow<T>(string windowName) where T : Window
{
if (!IsWindowOpen<T>(windowName)) return;
GetOpenedWindow<T>(windowName).Close();
}
private static bool IsWindowOpen<T>(string name = "") where T : Window
{
return string.IsNullOrEmpty(name)
? Application.Current.Windows.OfType<T>().Any()
: Application.Current.Windows.OfType<T>().Any(w => w.Title.Equals(name));
}
private static Window GetOpenedWindow<T>(string name) where T : Window
{
return Application.Current.Windows.OfType<T>().FirstOrDefault(w => w.Title.Equals(name));
}
public static bool IsNaN(double value)
{
var t = new NanUnion {DoubleValue = value};
var exp = t.UlongValue & 0xfff0000000000000;
var man = t.UlongValue & 0x000fffffffffffff;
return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0;
}
public static bool AreVirtuallyEqual(double d1, double d2)
{
if (double.IsPositiveInfinity(d1))
return double.IsPositiveInfinity(d2);
if (double.IsNegativeInfinity(d1))
return double.IsNegativeInfinity(d2);
if (IsNaN(d1))
return IsNaN(d2);
var n = d1 - d2;
var d = (Math.Abs(d1) + Math.Abs(d2) + 10) * 1.0e-15;
return -d < n && d > n;
}
}
}

View File

@ -1,41 +0,0 @@
using System;
using System.Configuration;
using System.Diagnostics;
namespace FModel.Logger
{
static class DebugHelper
{
public static Logger Logger { get; private set; }
public static void Init(string logFilePath) => Logger = new Logger(logFilePath);
public static void WriteLine(string message = "")
{
if (Logger != null)
Logger.WriteLine(message);
else
Debug.WriteLine(message);
}
public static void WriteLine(string format, params object[] args) => WriteLine(string.Format(format, args));
public static void WriteException(string exception, string message = "Exception")
{
if (Logger != null)
Logger.WriteException(exception, message);
else
Debug.WriteLine(exception);
}
public static void WriteException(Exception exception, string message = "Exception") => WriteException(exception.ToString(), message);
public static void WriteUserSettings()
{
foreach (SettingsProperty currentProperty in Properties.Settings.Default.Properties)
{
WriteLine("{0} {1} {2} {3}", "[FModel]", "[User Settings]", $"[{currentProperty.Name}]", Properties.Settings.Default[currentProperty.Name]);
}
}
}
}

View File

@ -1,178 +0,0 @@
using FModel.Windows.DarkMessageBox;
using Microsoft.Win32;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace FModel.Logger
{
class Logger
{
public delegate void MessageAddedEventHandler(string message);
public event MessageAddedEventHandler MessageAdded;
public string MessageFormat { get; set; } = "{0:yyyy-MM-dd HH:mm:ss.fff} - {1}";
public bool AsyncWrite { get; set; } = true;
public bool DebugWrite { get; set; } = Globals.Build == EFModel.Debug;
public bool StringWrite { get; set; } = true;
public bool FileWrite { get; set; } = false;
public string LogFilePath { get; private set; }
private readonly object loggerLock = new object();
private readonly ConcurrentQueue<string> messageQueue = new ConcurrentQueue<string>();
private readonly StringBuilder sbMessages = new StringBuilder();
public Logger()
{
}
public Logger(string logFilePath)
{
FileWrite = true;
LogFilePath = logFilePath;
CreateDirectoryFromFilePath(LogFilePath);
}
protected void OnMessageAdded(string message) => MessageAdded?.Invoke(message);
private void ProcessMessageQueue()
{
lock (loggerLock)
{
while (messageQueue.TryDequeue(out string message))
{
if (DebugWrite)
Debug.Write(message);
if (StringWrite && sbMessages != null)
sbMessages.Append(message);
if (FileWrite && !string.IsNullOrEmpty(LogFilePath))
{
try
{
File.AppendAllText(LogFilePath, message, Encoding.UTF8);
}
catch (Exception e)
{
Debug.WriteLine(e);
}
}
OnMessageAdded(message);
}
}
}
public void Write(string message)
{
if (message != null)
{
message = string.Format(MessageFormat, DateTime.Now, message);
messageQueue.Enqueue(message);
if (AsyncWrite)
Task.Run(() => ProcessMessageQueue());
else
ProcessMessageQueue();
}
}
public void Write(string format, params object[] args) => Write(string.Format(format, args));
public void WriteLine(string message) => Write(message + Environment.NewLine);
public void WriteLine(string format, params object[] args) => WriteLine(string.Format(format, args));
public void WriteException(string exception, string message = "Exception") => WriteLine($"{message}:{Environment.NewLine}{exception}");
public void WriteException(Exception exception, string message = "Exception") => WriteException(exception.ToString(), message);
public void Clear()
{
lock (loggerLock)
{
if (sbMessages != null)
sbMessages.Clear();
}
}
public override string ToString()
{
lock (loggerLock)
{
if (sbMessages != null && sbMessages.Length > 0)
return sbMessages.ToString();
return string.Empty;
}
}
public static void CreateDirectoryFromFilePath(string filePath)
{
if (!string.IsNullOrEmpty(filePath))
{
string directoryPath = Path.GetDirectoryName(filePath);
CreateDirectoryFromDirectoryPath(directoryPath);
}
}
public static void CreateDirectoryFromDirectoryPath(string directoryPath)
{
if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
{
try
{
Directory.CreateDirectory(directoryPath);
}
catch (Exception e)
{
DebugHelper.WriteException(e);
DarkMessageBoxHelper.Show(Properties.Resources.CouldNotCreateDirectory + "\r\n\r\n" + e, "FModel - " + Properties.Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
public static string GetOperatingSystemProductName(bool includeBit = false)
{
string productName = null;
try
{
productName = GetRegistryValue(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", RegistryHive.LocalMachine);
}
catch
{
//hello world :)
}
if (string.IsNullOrEmpty(productName))
productName = Environment.OSVersion.VersionString;
if (includeBit)
{
string bit;
if (Environment.Is64BitOperatingSystem)
bit = "64";
else
bit = "32";
productName = $"{productName} ({bit}-bit)";
}
return productName;
}
public static string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
{
using (RegistryKey rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path))
{
if (rk != null)
return rk.GetValue(name, null) as string;
}
return null;
}
}
}

File diff suppressed because it is too large Load Diff

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