Update/net7 (#290)

* file-scoped namespace & net7.0

* Workflow
This commit is contained in:
GMatrixGames 2022-06-11 20:07:59 -04:00 committed by GitHub
parent 85158296a9
commit e21a3be55b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
144 changed files with 14135 additions and 14472 deletions

View File

@ -21,16 +21,17 @@ jobs:
- name: Fetch Submodules Recursively
run: git submodule update --init --recursive
- name: .NET 6 Setup
uses: actions/setup-dotnet@v1
- name: .NET 7 Setup
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
dotnet-version: '7.0.x'
include-prerelease: true
- name: .NET Restore
run: dotnet restore FModel
- name: .NET Publish
run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net6.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }}
run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net7.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }}
- name: ZIP File
uses: papeloto/action-zip@v1

View File

@ -16,146 +16,150 @@ using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
namespace FModel
namespace FModel;
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App
[DllImport("kernel32.dll")]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("winbrand.dll", CharSet = CharSet.Unicode)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
static extern string BrandingFormatString(string format);
protected override void OnStartup(StartupEventArgs e)
{
[DllImport("kernel32.dll")]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("winbrand.dll", CharSet = CharSet.Unicode)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
static extern string BrandingFormatString(string format);
protected override void OnStartup(StartupEventArgs e)
{
#if DEBUG
AttachConsole(-1);
AttachConsole(-1);
#endif
base.OnStartup(e);
base.OnStartup(e);
try
{
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
}
catch
{
UserSettings.Default = new UserSettings();
}
var createMe = false;
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
{
UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
}
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
{
createMe = true;
UserSettings.Default.RawDataDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.PropertiesDirectory))
{
createMe = true;
UserSettings.Default.PropertiesDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.TextureDirectory))
{
createMe = true;
UserSettings.Default.TextureDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.AudioDirectory))
{
createMe = true;
UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.ModelDirectory))
{
createMe = true;
UserSettings.Default.ModelDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"));
if (createMe) Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports"));
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.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 AppExit(object sender, ExitEventArgs e)
try
{
Log.Information("");
Log.CloseAndFlush();
UserSettings.Save();
Environment.Exit(0);
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
}
private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
catch
{
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("Reset Settings", EErrorKind.ResetSettings),
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)
{
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
UserSettings.Default = new UserSettings();
ApplicationService.ApplicationView.Restart();
}
e.Handled = true;
UserSettings.Default = new UserSettings();
}
private string GetOperatingSystemProductName()
var createMe = false;
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
{
var productName = string.Empty;
try
{
productName = BrandingFormatString("%WINDOWS_LONG%");
}
catch
{
// ignored
}
if (string.IsNullOrEmpty(productName))
productName = Environment.OSVersion.VersionString;
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
}
public static string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
{
using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path);
if (rk != null)
return rk.GetValue(name, null) as string;
return string.Empty;
createMe = true;
UserSettings.Default.RawDataDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.PropertiesDirectory))
{
createMe = true;
UserSettings.Default.PropertiesDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.TextureDirectory))
{
createMe = true;
UserSettings.Default.TextureDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.AudioDirectory))
{
createMe = true;
UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.ModelDirectory))
{
createMe = true;
UserSettings.Default.ModelDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"));
if (createMe) Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports"));
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.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 AppExit(object sender, ExitEventArgs e)
{
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("Reset Settings", EErrorKind.ResetSettings),
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)
{
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
UserSettings.Default = new UserSettings();
ApplicationService.ApplicationView.Restart();
}
e.Handled = true;
}
private string GetOperatingSystemProductName()
{
var productName = string.Empty;
try
{
productName = BrandingFormatString("%WINDOWS_LONG%");
}
catch
{
// ignored
}
if (string.IsNullOrEmpty(productName))
productName = Environment.OSVersion.VersionString;
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
}
public static 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;
}
}

View File

@ -1,27 +1,26 @@
using System.Reflection;
using CUE4Parse.UE4.Objects.Core.Misc;
namespace FModel
namespace FModel;
public static class Constants
{
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 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 ISSUE_LINK = "https://github.com/iAmAsval/FModel/issues/new/choose";
public const string DONATE_LINK = "https://fmodel.app/donate";
public const string DISCORD_LINK = "https://fmodel.app/discord";
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 _FN_LIVE_TRIGGER = "fortnite-live.manifest";
public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest";
public const string ISSUE_LINK = "https://github.com/iAmAsval/FModel/issues/new/choose";
public const string DONATE_LINK = "https://fmodel.app/donate";
public const string DISCORD_LINK = "https://fmodel.app/discord";
public const string _NO_PRESET_TRIGGER = "Hand Made";
}
public const string _FN_LIVE_TRIGGER = "fortnite-live.manifest";
public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest";
public const string _NO_PRESET_TRIGGER = "Hand Made";
}

View File

@ -4,40 +4,39 @@ using CUE4Parse.UE4.Objects.UObject;
using FModel.Creator.Bases.FN;
using SkiaSharp;
namespace FModel.Creator.Bases.BB
namespace FModel.Creator.Bases.BB;
public class BaseBreakersIcon : BaseIcon
{
public class BaseBreakersIcon : BaseIcon
public BaseBreakersIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
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", "UnlockPortraitGuideImage"))
Preview = Utils.GetBitmap(iconTextureAssetData);
if (Object.TryGetValue(out FText displayName, "DisplayName", "RegionDisplayName", "ZoneName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "RegionShortName", "ZoneDescription"))
Description = description.Text;
}
public override SKBitmap[] Draw()
{
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 new []{ret};
}
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", "UnlockPortraitGuideImage"))
Preview = Utils.GetBitmap(iconTextureAssetData);
if (Object.TryGetValue(out FText displayName, "DisplayName", "RegionDisplayName", "ZoneName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "RegionShortName", "ZoneDescription"))
Description = description.Text;
}
public override SKBitmap[] Draw()
{
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 new[] { ret };
}
}

View File

@ -8,134 +8,133 @@ 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;
namespace FModel.Creator.Bases.FN;
public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style)
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 :)
{
Width = 1024;
Height = _headerHeight;
Margin = 0;
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));
}
}
public override void ParseForInfo()
if (Object.TryGetValue(out FStructFallback[] completionRewards, "BundleCompletionRewards"))
{
_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 completionReward in completionRewards)
{
foreach (var quest in quests)
if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") ||
!completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue;
foreach (var reward in rewards)
{
if (!quest.TryGetValue(out FSoftObjectPath questDefinition, "QuestDefinition")) continue;
if (!reward.TryGetValue(out int quantity, "Quantity") ||
!reward.TryGetValue(out string templateId, "TemplateId") ||
!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue;
BaseQuest q;
var path = questDefinition.AssetPathName.Text;
do
if (!itemDefinition.AssetPathName.IsNone &&
!itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") &&
!itemDefinition.AssetPathName.Text.Contains("/Items/Quests"))
{
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)
_quests.Add(new BaseQuest(completionCount, itemDefinition, Style));
}
else if (!string.IsNullOrWhiteSpace(templateId))
{
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));
}
_quests.Add(new BaseQuest(completionCount, quantity, templateId, Style));
}
}
}
Height += 256 * _quests.Count;
}
public override SKBitmap[] Draw()
Height += 256 * _quests.Count;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
DrawDisplayName(c);
DrawQuests(c);
return new[] { 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)
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
DrawDisplayName(c);
DrawQuests(c);
return new []{ret};
_headerPaint.TextSize -= 1;
}
private readonly SKPaint _headerPaint = new()
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)
{
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;
}
quest.DrawQuest(c, y);
y += quest.Height;
}
}
}
}

View File

@ -11,231 +11,145 @@ using FModel.ViewModels.ApiEndpoints.Models;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseCommunity : BaseIcon
{
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)
{
private readonly CommunityDesign _design;
private string _rarityName;
private string _source;
private string _season;
private bool _lowerDrawn;
Margin = 0;
_lowerDrawn = false;
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
}
public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style)
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export))
_rarityName = export.Name;
else
_rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription();
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 SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
if (_design == null)
{
Margin = 0;
_lowerDrawn = false;
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
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);
}
public override void ParseForInfo()
return new[] { ret };
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (_design == null) return;
if (_design.DrawSource)
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export))
_rarityName = export.Name;
else
_rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription();
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();
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();
}
public override SKBitmap[] Draw()
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);
var triggers = _design.GameplayTags.DrawCustomOnly ? new[] { "Cosmetics.UserFacingFlags." } : new[] { "Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender." };
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(triggers));
}
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);
switch (number)
{
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 new []{ret};
case 10:
s = "X";
break;
case > 18:
number += 2;
s = number.ToString();
break;
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
return $"C{number / 10 + 1} S{s[^1..]}";
}
private new void DrawBackground(SKCanvas c)
{
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
{
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);
var triggers = _design.GameplayTags.DrawCustomOnly ? new[] {"Cosmetics.UserFacingFlags."} : new[] {"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."};
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(triggers));
c.DrawBitmap(rarity.Background, 0, 0, ImagePaint);
c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint);
}
private string GetCosmeticSet(string setName, bool bShort)
else
{
return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName);
base.DrawBackground(c);
}
}
private string GetCosmeticSeason(string seasonNumber, bool bShort)
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))
{
if (!bShort) return base.GetCosmeticSeason(seasonNumber);
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
var number = int.Parse(s);
switch (number)
{
case 10:
s = "X";
break;
case > 18:
number += 2;
s = number.ToString();
break;
}
return $"C{number / 10 + 1} S{s[^1..]}";
c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint);
}
private new void DrawBackground(SKCanvas c)
else
{
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);
}
base.DrawTextBackground(c);
}
}
private new void DrawTextBackground(SKCanvas c)
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font))
{
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;
@ -247,30 +161,115 @@ namespace FModel.Creator.Bases.FN
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(text, DisplayNamePaint);
var shapedText = shaper.Shape(DisplayName, 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);
c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint);
}
private void DrawUserFacingFlags(SKCanvas c, bool customOnly)
else
{
if (UserFacingFlags == null || UserFacingFlags.Count < 1) return;
if (customOnly)
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)
{
c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint);
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
{
// add size to api
// draw
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.Count < 1) return;
if (customOnly)
{
c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint);
}
else
{
// add size to api
// draw
}
}
}

View File

@ -16,272 +16,269 @@ using CUE4Parse_Fortnite.Enums;
using FModel.Settings;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseIcon : UCreator
{
public class BaseIcon : UCreator
public SKBitmap SeriesBackground { get; protected set; }
protected string ShortDescription { get; set; }
protected string CosmeticSource { get; set; }
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
public SKBitmap SeriesBackground { get; protected set; }
protected string ShortDescription { get; set; }
protected string CosmeticSource { get; set; }
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
}
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style)
public void ParseForReward(bool isUsingDisplayAsset)
{
// rarity
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // 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", "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);
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
Preview = Utils.GetBitmap(materialInstancePreview);
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
Preview = Utils.GetBitmap(res);
// text
if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
Description = description.Text;
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
Description = string.Join('\n', descriptions.Select(x => x.Text));
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
ShortDescription = shortDescription.Text;
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
// Only works on non-cataba designs
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
{
Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) };
Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) };
}
public void ParseForReward(bool isUsingDisplayAsset)
Description = Utils.RemoveHtmlTags(Description);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
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)
{
// rarity
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
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;
}
}
// 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", "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);
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
Preview = Utils.GetBitmap(materialInstancePreview);
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
Preview = Utils.GetBitmap(res);
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
// text
if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
Description = description.Text;
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
Description = string.Join('\n', descriptions.Select(x => x.Text));
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
ShortDescription = shortDescription.Text;
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
Draw(c);
// Only works on non-cataba designs
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
return new[] { 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 = texture2D.Decode();
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)
{
Background = new[] {SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex)};
Border = new[] {SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex)};
}
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
continue;
Description = Utils.RemoveHtmlTags(Description);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
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;
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
}
}
}
public override SKBitmap[] Draw()
private void GetRarity(EFortRarity r)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
data.TryGetValue(out FLinearColor color1, "Color1") &&
data.TryGetValue(out FLinearColor color2, "Color2") &&
data.TryGetValue(out FLinearColor color3, "Color3"))
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
}
Draw(c);
protected string GetCosmeticSet(string setName)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
return string.Empty;
return new []{ret};
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);
switch (number)
{
case 10:
s = "X";
break;
case > 18:
number += 2;
s = number.ToString();
break;
}
private void GetSeries(FPackageIndex s)
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 Dictionary<string, SKBitmap>(userFacingFlags.Count);
foreach (var flag in userFacingFlags)
{
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
GetSeries(export);
}
protected void GetSeries(UObject uObject)
{
if (uObject is UTexture2D texture2D)
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
{
SeriesBackground = texture2D.Decode();
return;
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
}
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
else
{
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)
foreach (var category in tertiaryCategories)
{
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
continue;
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
}
}
}
private void GetRarity(EFortRarity r)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
data.TryGetValue(out FLinearColor color1, "Color1") &&
data.TryGetValue(out FLinearColor color2, "Color2") &&
data.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)};
Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)};
}
}
protected string GetCosmeticSet(string setName)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
return string.Empty;
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
return string.Empty;
var name = string.Empty;
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
name = displayName.Text;
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_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);
switch (number)
{
case 10:
s = "X";
break;
case > 18:
number += 2;
s = number.ToString();
break;
}
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 Dictionary<string, SKBitmap>(userFacingFlags.Count);
foreach (var flag in userFacingFlags)
{
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
{
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
}
else
{
foreach (var category in tertiaryCategories)
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
{
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
{
UserFacingFlags[flag] = Utils.GetBitmap(texture);
}
UserFacingFlags[flag] = Utils.GetBitmap(texture);
}
}
}
}
}
private void DrawUserFacingFlags(SKCanvas c)
private void DrawUserFacingFlags(SKCanvas c)
{
if (UserFacingFlags == null) return;
const int size = 25;
var x = Margin * (int) 2.5;
foreach (var flag in UserFacingFlags.Values.Where(flag => flag != null))
{
if (UserFacingFlags == null) return;
const int size = 25;
var x = Margin * (int) 2.5;
foreach (var flag in UserFacingFlags.Values)
{
if (flag == null) continue;
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
x += size;
}
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
x += size;
}
}
}
}

View File

@ -13,280 +13,279 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseIconStats : BaseIcon
{
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)
{
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 > 0)
{
_statistics.Add(new IconStat("Max Stack", v , 15));
}
else if (TryGetCurveTableStat(maxStackSize, out var s))
{
_statistics.Add(new IconStat("Max Stack", s , 15));
}
}
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 int bpc, "BulletsPerCartridge"))
{
var multiplier = bpc != 0f ? bpc : 1;
if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200));
}
if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"))
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 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, Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription()))
{
_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 SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
DrawDisplayName(c);
DrawStatistics(c);
return new []{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.TryFindCurve(rowName, out var rowValue) &&
rowValue is FSimpleCurve s && s.Keys.Length > 0)
{
statValue = s.Keys[0].Value;
return true;
}
statValue = 0F;
return false;
}
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;
}
}
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 class IconStat
public override void ParseForInfo()
{
private readonly string _statName;
private readonly object _value;
private readonly float _maxValue;
base.ParseForInfo();
DisplayName = DisplayName.ToUpperInvariant();
public IconStat(string statName, object value, float maxValue = 0)
if (Object.TryGetValue(out FName accoladeType, "AccoladeType") &&
accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase))
{
_statName = statName.ToUpperInvariant();
_value = value;
_maxValue = maxValue;
_screenLayer = false;
}
private readonly SKPaint _statPaint = new()
if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") &&
Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) &&
uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
{
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 - 40)
foreach (var location in poiLocations)
{
_statPaint.TextSize -= 1;
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 > 0)
{
_statistics.Add(new IconStat("Max Stack", v, 15));
}
else if (TryGetCurveTableStat(maxStackSize, out var s))
{
_statistics.Add(new IconStat("Max Stack", s, 15));
}
}
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 int bpc, "BulletsPerCartridge"))
{
var multiplier = bpc != 0f ? bpc : 1;
if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200));
}
if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"))
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200));
}
}
var shaper = new CustomSKShaper(_statPaint.Typeface);
shaper.Shape(_statName, _statPaint);
c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint);
if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50));
}
_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);
if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15));
}
_statPaint.Color = SKColors.White;
c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint);
if (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125));
}
if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return;
if (floatValue < 0)
floatValue = 0;
var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue);
c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint);
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, Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription()))
{
_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 SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
DrawDisplayName(c);
DrawStatistics(c);
return new[] { 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.TryFindCurve(rowName, out var rowValue) &&
rowValue is FSimpleCurve s && s.Keys.Length > 0)
{
statValue = s.Keys[0].Value;
return true;
}
statValue = 0F;
return false;
}
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 - 40)
{
_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;
if (floatValue < 0)
floatValue = 0;
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

@ -5,96 +5,95 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseItemAccessToken : UCreator
{
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)
{
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 SKBitmap[] Draw()
{
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 new []{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);
}
_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 SKBitmap[] Draw()
{
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 new[] { 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

@ -5,83 +5,82 @@ using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseMaterialInstance : BaseIcon
{
public class BaseMaterialInstance : BaseIcon
public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style)
{
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 not UMaterialInstanceConstant material) return;
texture_finding:
foreach (var textureParameter in material.TextureParameterValues) // get texture from base material
{
Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")};
Border = new[] {SKColor.Parse("9092AB")};
}
public override void ParseForInfo()
{
if (Object is not UMaterialInstanceConstant material) return;
texture_finding:
foreach (var textureParameter in material.TextureParameterValues) // get texture from base material
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture) || Preview != null) continue;
switch (textureParameter.ParameterInfo.Name.Text)
{
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var 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;
}
if (Preview == null)
goto texture_finding;
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;
}
case "SeriesTexture":
GetSeries(texture);
break;
case "TextureA":
case "TextureB":
case "OfferImage":
Preview = Utils.GetBitmap(texture);
break;
}
}
public override SKBitmap[] Draw()
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
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
Utils.TryGetPackageIndexExport(parent, out material);
else return;
switch (Style)
if (material == null) return;
}
if (Preview == null)
goto texture_finding;
foreach (var vectorParameter in material.VectorParameterValues)
{
if (vectorParameter.ParameterValue == null) continue;
switch (vectorParameter.ParameterInfo.Name.Text)
{
case EIconStyle.NoBackground:
DrawPreview(c);
case "Background_Color_A":
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
Border[0] = Background[0];
break;
default:
DrawBackground(c);
DrawPreview(c);
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;
}
return new []{ret};
}
}
}
public override SKBitmap[] Draw()
{
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 new[] { ret };
}
}

View File

@ -5,81 +5,80 @@ using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseMtxOffer : UCreator
{
public class BaseMtxOffer : UCreator
public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style)
{
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"))
{
Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")};
Border = new[] {SKColor.Parse("9092AB")};
Preview = Utils.GetBitmap(resource);
}
public override void ParseForInfo()
if (Object.TryGetValue(out FStructFallback gradient, "Gradient") &&
gradient.TryGetValue(out FLinearColor start, "Start") &&
gradient.TryGetValue(out FLinearColor stop, "Stop"))
{
if (Object.TryGetValue(out FStructFallback typeImage, "DetailsImage", "TileImage") &&
typeImage.TryGetValue(out FPackageIndex resource, "ResourceObject"))
{
Preview = Utils.GetBitmap(resource);
}
Background = new[] { SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex) };
}
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 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"))
if (Object.TryGetValue(out FStructFallback[] details, "DetailsAttributes"))
{
foreach (var detail in details)
{
foreach (var detail in details)
if (detail.TryGetValue(out FText detailName, "Name"))
{
if (detail.TryGetValue(out FText detailName, "Name"))
{
Description += $"\n- {detailName.Text.TrimEnd()}";
}
Description += $"\n- {detailName.Text.TrimEnd()}";
}
if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text))
{
Description += $" ({detailValue.Text})";
}
if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text))
{
Description += $" ({detailValue.Text})";
}
}
Description = Utils.RemoveHtmlTags(Description);
}
public override SKBitmap[] Draw()
{
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 new []{ret};
}
Description = Utils.RemoveHtmlTags(Description);
}
}
public override SKBitmap[] Draw()
{
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 new[] { ret };
}
}

View File

@ -2,39 +2,38 @@ using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseOfferDisplayData : UCreator
{
public class BaseOfferDisplayData : UCreator
private BaseMaterialInstance[] _offerImages;
public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style)
{
private BaseMaterialInstance[] _offerImages;
}
public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style)
public override void ParseForInfo()
{
if (!Object.TryGetValue(out UMaterialInterface[] presentations, "Presentations"))
return;
_offerImages = new BaseMaterialInstance[presentations.Length];
for (var i = 0; i < _offerImages.Length; i++)
{
}
public override void ParseForInfo()
{
if (!Object.TryGetValue(out UMaterialInterface[] presentations, "Presentations"))
return;
_offerImages = new BaseMaterialInstance[presentations.Length];
for (int i = 0; i < _offerImages.Length; i++)
{
var offerImage = new BaseMaterialInstance(presentations[i], Style);
offerImage.ParseForInfo();
_offerImages[i] = offerImage;
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap[_offerImages.Length];
for (int i = 0; i < ret.Length; i++)
{
ret[i] = _offerImages[i].Draw()[0];
}
return ret;
var offerImage = new BaseMaterialInstance(presentations[i], Style);
offerImage.ParseForInfo();
_offerImages[i] = offerImage;
}
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap[_offerImages.Length];
for (var i = 0; i < ret.Length; i++)
{
ret[i] = _offerImages[i].Draw()[0];
}
return ret;
}
}

View File

@ -6,73 +6,72 @@ using FModel.Services;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BasePlaylist : UCreator
{
public class BasePlaylist : UCreator
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private SKBitmap _missionIcon;
public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style)
{
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).ResizeWithRatio(1024, 512);
Width = Preview.Width;
Height = Preview.Height;
}
public override SKBitmap[] Draw()
{
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 new []{ret};
}
private void DrawMissionIcon(SKCanvas c)
{
if (_missionIcon == null) return;
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
}
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).ResizeWithRatio(1024, 512);
Width = Preview.Width;
Height = Preview.Height;
}
public override SKBitmap[] Draw()
{
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 new[] { ret };
}
private void DrawMissionIcon(SKCanvas c)
{
if (_missionIcon == null) return;
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
}
}

View File

@ -12,256 +12,255 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseQuest : BaseIcon
{
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)
{
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)
{
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, "EntryListIcon", "ToastIcon") &&
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out UObject iconObject))
{
Preview = iconObject switch
{
UTexture2D text => Utils.GetBitmap(text),
UMaterialInstanceConstant mat => Utils.GetBitmap(mat),
_ => Preview
};
}
}
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, $"{name}:{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 SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawQuest(c, 0);
return new []{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));
_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, "EntryListIcon", "ToastIcon") &&
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out UObject iconObject))
{
Preview = iconObject switch
{
UTexture2D text => Utils.GetBitmap(text),
UMaterialInstanceConstant mat => Utils.GetBitmap(mat),
_ => Preview
};
}
}
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, $"{name}:{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 SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawQuest(c, 0);
return new[] { 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

@ -8,158 +8,152 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class Page
{
public class Page
public int LevelsNeededForUnlock;
public int RewardsNeededForUnlock;
public Reward[] RewardEntryList;
}
public class BaseSeason : UCreator
{
private Reward _firstWinReward;
private Page[] _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)
{
public int LevelsNeededForUnlock;
public int RewardsNeededForUnlock;
public Reward[] RewardEntryList;
Width = 1024;
Height = _headerHeight + 50;
Margin = 0;
}
public class BaseSeason : UCreator
public override void ParseForInfo()
{
private Reward _firstWinReward;
private Page[] _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
_bookXpSchedule = Array.Empty<Page>();
public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style)
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&
seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards"))
{
Width = 1024;
Height = _headerHeight + 50;
Margin = 0;
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;
}
}
public override void ParseForInfo()
if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData"))
{
_bookXpSchedule = Array.Empty<Page>();
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 data in additionalSeasonData)
{
foreach (var reward in rewards)
if (!Utils.TryGetPackageIndexExport(data, out UObject packageIndex) ||
!packageIndex.TryGetValue(out FStructFallback[] pageList, "PageList")) continue;
var i = 0;
_bookXpSchedule = new Page[pageList.Length];
foreach (var page in pageList)
{
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
if (!page.TryGetValue(out int levelsNeededForUnlock, "LevelsNeededForUnlock") ||
!page.TryGetValue(out int rewardsNeededForUnlock, "RewardsNeededForUnlock") ||
!page.TryGetValue(out FPackageIndex[] rewardEntryList, "RewardEntryList"))
continue;
_firstWinReward = new Reward(uObject);
break;
}
}
if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData"))
{
foreach (var data in additionalSeasonData)
{
if (!Utils.TryGetPackageIndexExport(data, out UObject packageIndex) ||
!packageIndex.TryGetValue(out FStructFallback[] pageList, "PageList")) continue;
var i = 0;
_bookXpSchedule = new Page[pageList.Length];
foreach (var page in pageList)
var p = new Page
{
if (!page.TryGetValue(out int levelsNeededForUnlock, "LevelsNeededForUnlock") ||
!page.TryGetValue(out int rewardsNeededForUnlock, "RewardsNeededForUnlock") ||
!page.TryGetValue(out FPackageIndex[] rewardEntryList, "RewardEntryList"))
continue;
LevelsNeededForUnlock = levelsNeededForUnlock,
RewardsNeededForUnlock = rewardsNeededForUnlock,
RewardEntryList = new Reward[rewardEntryList.Length]
};
var p = new Page
{
LevelsNeededForUnlock = levelsNeededForUnlock,
RewardsNeededForUnlock = rewardsNeededForUnlock,
RewardEntryList = new Reward[rewardEntryList.Length]
};
for (var j = 0; j < p.RewardEntryList.Length; j++)
{
if (!Utils.TryGetPackageIndexExport(rewardEntryList[j], out packageIndex) ||
!packageIndex.TryGetValue(out FStructFallback battlePassOffer, "BattlePassOffer") ||
!battlePassOffer.TryGetValue(out FStructFallback rewardItem, "RewardItem") ||
!rewardItem.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
for (var j = 0; j < p.RewardEntryList.Length; j++)
{
if (!Utils.TryGetPackageIndexExport(rewardEntryList[j], out packageIndex) ||
!packageIndex.TryGetValue(out FStructFallback battlePassOffer, "BattlePassOffer") ||
!battlePassOffer.TryGetValue(out FStructFallback rewardItem, "RewardItem") ||
!rewardItem.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
p.RewardEntryList[j] = new Reward(uObject);
}
_bookXpSchedule[i++] = p;
p.RewardEntryList[j] = new Reward(uObject);
}
break;
_bookXpSchedule[i++] = p;
}
break;
}
Height += 100 * _bookXpSchedule.Sum(x => x.RewardEntryList.Length) / _bookXpSchedule.Length;
}
public override SKBitmap[] Draw()
Height += 100 * _bookXpSchedule.Sum(x => x.RewardEntryList.Length) / _bookXpSchedule.Length;
}
public override SKBitmap[] Draw()
{
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 new[] { 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")
};
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)
{
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 new []{ret};
_headerPaint.TextSize -= 1;
}
private const int _DEFAULT_AREA_SIZE = 80;
private readonly SKPaint _headerPaint = new()
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 page in _bookXpSchedule)
{
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)
foreach (var reward in page.RewardEntryList)
{
_headerPaint.TextSize -= 1;
reward.DrawSeason(c, x, y, _DEFAULT_AREA_SIZE);
x += _DEFAULT_AREA_SIZE + 20;
}
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 page in _bookXpSchedule)
{
foreach (var reward in page.RewardEntryList)
{
reward.DrawSeason(c, x, y, _DEFAULT_AREA_SIZE);
x += _DEFAULT_AREA_SIZE + 20;
}
y += _DEFAULT_AREA_SIZE + 20;
x = 20;
}
y += _DEFAULT_AREA_SIZE + 20;
x = 20;
}
}
}
}

View File

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

View File

@ -9,221 +9,220 @@ using FModel.Settings;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseTandem : BaseIcon
{
public class BaseTandem : BaseIcon
private string _generalDescription, _additionalDescription;
public BaseTandem(UObject uObject, EIconStyle style) : base(uObject, style)
{
private string _generalDescription, _additionalDescription;
public BaseTandem(UObject uObject, EIconStyle style) : base(uObject, style)
{
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/BattleRoyale/FeaturedItems/Outfit/T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy.T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy");
Margin = 0;
Width = 690;
Height = 1080;
}
public override void ParseForInfo()
{
base.ParseForInfo();
string sidePanel = string.Empty, entryList = string.Empty;
if (Object.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
sidePanel = sidePanelIcon.AssetPathName.Text;
if (Object.TryGetValue(out FSoftObjectPath entryListIcon, "EntryListIcon"))
entryList = entryListIcon.AssetPathName.Text;
// Overrides for generic "default" images Epic uses for Quest-only or unfinished NPCs
if (sidePanel.Contains("Clown") && entryList.Contains("Clown"))
Preview = null;
else if (sidePanel.Contains("Bane") && !Object.Name.Contains("Sorana"))
Preview = Utils.GetBitmap(entryList);
else if (!string.IsNullOrWhiteSpace(sidePanel) && !sidePanel.Contains("Clown"))
Preview = Utils.GetBitmap(sidePanel);
else if ((string.IsNullOrWhiteSpace(sidePanel) || sidePanel.Contains("Clown")) && !string.IsNullOrWhiteSpace(entryList))
Preview = Utils.GetBitmap(entryList);
if (Object.TryGetValue(out FText genDesc, "GeneralDescription"))
_generalDescription = genDesc.Text;
if (Object.TryGetValue(out FText addDesc, "AdditionalDescription"))
_additionalDescription = addDesc.Text;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawHaze(c);
// Korean is slightly smaller than other languages, so the font size is increased slightly
DrawName(c);
DrawGeneralDescription(c);
DrawAdditionalDescription(c);
return new []{ret};
}
private readonly SKPaint _panelPaint = new() { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = SKColor.Parse("#0045C7") };
private new void DrawBackground(SKCanvas c)
{
c.DrawBitmap(SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/npcleftside.png"))?.Stream).Resize(Width, Height), 0, 0, new SKPaint { IsAntialias = false, FilterQuality = SKFilterQuality.None, ImageFilter = SKImageFilter.CreateBlur(0, 25) });
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
_panelPaint.Color = SKColor.Parse("#002A8C");
rect1.MoveTo(29, 0);
rect1.LineTo(62, Height);
rect1.LineTo(Width, Height);
rect1.LineTo(Width, 0);
rect1.LineTo(29, 0);
rect1.Close();
c.DrawPath(rect1, _panelPaint);
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(29, 0), new SKPoint(Width, Height),
new[] { SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
_panelPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(348, 196), 300, new[] { SKColor.Parse("#0049CE"), SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
using var rect2 = new SKPath { FillType = SKPathFillType.EvenOdd };
rect2.MoveTo(10, 0);
rect2.LineTo(30, 0);
rect2.LineTo(63, Height);
rect2.LineTo(56, Height);
rect2.LineTo(10, 0);
rect2.Close();
c.DrawPath(rect2, _panelPaint);
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(10, 0), new SKPoint(62, Height),
new[] { SKColor.Parse("#0045C7") }, SKShaderTileMode.Clamp);
c.DrawPath(rect2, _panelPaint);
}
private new void DrawPreview(SKCanvas c)
{
var previewToUse = Preview ?? DefaultPreview;
if (Preview == null)
{
previewToUse = DefaultPreview;
ImagePaint.BlendMode = SKBlendMode.DstOut;
ImagePaint.Color = SKColor.Parse("#00175F");
}
var x = -125;
switch (previewToUse.Width)
{
case 512 when previewToUse.Height == 1024:
previewToUse = previewToUse.ResizeWithRatio(500, 1000);
x = 100;
break;
case 512 when previewToUse.Height == 512:
case 128 when previewToUse.Height == 128:
previewToUse = previewToUse.Resize(512);
x = 125;
break;
default:
previewToUse = previewToUse.Resize(1000, 1000);
break;
}
c.DrawBitmap(previewToUse, x, 30, ImagePaint);
}
private void DrawHaze(SKCanvas c)
{
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
rect1.MoveTo(29, 0);
rect1.LineTo(62, Height);
rect1.LineTo(Width, Height);
rect1.LineTo(Width, 0);
rect1.LineTo(29, 0);
rect1.Close();
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(343, 0), new SKPoint(343, Height),
new[] { SKColors.Transparent, SKColor.Parse("#001E70FF"), SKColor.Parse("#001E70").WithAlpha(200), SKColor.Parse("#001E70").WithAlpha(245), SKColor.Parse("#001E70") }, new[] { 0, (float) .1, (float) .65, (float) .85, 1 }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
}
private void DrawName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName)) return;
DisplayNamePaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 56,
_ => 42
};
DisplayNamePaint.TextScaleX = (float) 1.1;
DisplayNamePaint.Color = SKColors.White;
DisplayNamePaint.TextSkewX = (float) -.25;
DisplayNamePaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemDisplayName;
if (typeface == Utils.Typefaces.Default)
{
DisplayNamePaint.TextSize = 30;
}
DisplayNamePaint.Typeface = typeface;
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
c.DrawShapedText(shaper, DisplayName.ToUpper(), 97, 900, DisplayNamePaint);
}
private void DrawGeneralDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(_generalDescription)) return;
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 20,
_ => 17
};
DescriptionPaint.Color = SKColor.Parse("#00FFFB");
DescriptionPaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemGenDescription;
if (typeface == Utils.Typefaces.Default)
{
DescriptionPaint.TextSize = 21;
}
DescriptionPaint.Typeface = typeface;
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
c.DrawShapedText(shaper, _generalDescription.ToUpper(), 97, 930, DescriptionPaint);
}
private void DrawAdditionalDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(_additionalDescription)) return;
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 22,
_ => 18
};
DescriptionPaint.Color = SKColor.Parse("#89D8FF");
DescriptionPaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemAddDescription;
if (typeface == Utils.Typefaces.Default)
{
DescriptionPaint.TextSize = 20;
}
DescriptionPaint.Typeface = typeface;
Utils.DrawMultilineText(c, _additionalDescription, Width, 0, SKTextAlign.Left,
new SKRect(97, 960, Width - 10, Height), DescriptionPaint, out _);
}
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/BattleRoyale/FeaturedItems/Outfit/T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy.T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy");
Margin = 0;
Width = 690;
Height = 1080;
}
}
public override void ParseForInfo()
{
base.ParseForInfo();
string sidePanel = string.Empty, entryList = string.Empty;
if (Object.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
sidePanel = sidePanelIcon.AssetPathName.Text;
if (Object.TryGetValue(out FSoftObjectPath entryListIcon, "EntryListIcon"))
entryList = entryListIcon.AssetPathName.Text;
// Overrides for generic "default" images Epic uses for Quest-only or unfinished NPCs
if (sidePanel.Contains("Clown") && entryList.Contains("Clown"))
Preview = null;
else if (sidePanel.Contains("Bane") && !Object.Name.Contains("Sorana"))
Preview = Utils.GetBitmap(entryList);
else if (!string.IsNullOrWhiteSpace(sidePanel) && !sidePanel.Contains("Clown"))
Preview = Utils.GetBitmap(sidePanel);
else if ((string.IsNullOrWhiteSpace(sidePanel) || sidePanel.Contains("Clown")) && !string.IsNullOrWhiteSpace(entryList))
Preview = Utils.GetBitmap(entryList);
if (Object.TryGetValue(out FText genDesc, "GeneralDescription"))
_generalDescription = genDesc.Text;
if (Object.TryGetValue(out FText addDesc, "AdditionalDescription"))
_additionalDescription = addDesc.Text;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawHaze(c);
// Korean is slightly smaller than other languages, so the font size is increased slightly
DrawName(c);
DrawGeneralDescription(c);
DrawAdditionalDescription(c);
return new[] { ret };
}
private readonly SKPaint _panelPaint = new() { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = SKColor.Parse("#0045C7") };
private new void DrawBackground(SKCanvas c)
{
c.DrawBitmap(SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/npcleftside.png"))?.Stream).Resize(Width, Height), 0, 0, new SKPaint { IsAntialias = false, FilterQuality = SKFilterQuality.None, ImageFilter = SKImageFilter.CreateBlur(0, 25) });
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
_panelPaint.Color = SKColor.Parse("#002A8C");
rect1.MoveTo(29, 0);
rect1.LineTo(62, Height);
rect1.LineTo(Width, Height);
rect1.LineTo(Width, 0);
rect1.LineTo(29, 0);
rect1.Close();
c.DrawPath(rect1, _panelPaint);
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(29, 0), new SKPoint(Width, Height),
new[] { SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
_panelPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(348, 196), 300, new[] { SKColor.Parse("#0049CE"), SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
using var rect2 = new SKPath { FillType = SKPathFillType.EvenOdd };
rect2.MoveTo(10, 0);
rect2.LineTo(30, 0);
rect2.LineTo(63, Height);
rect2.LineTo(56, Height);
rect2.LineTo(10, 0);
rect2.Close();
c.DrawPath(rect2, _panelPaint);
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(10, 0), new SKPoint(62, Height),
new[] { SKColor.Parse("#0045C7") }, SKShaderTileMode.Clamp);
c.DrawPath(rect2, _panelPaint);
}
private new void DrawPreview(SKCanvas c)
{
var previewToUse = Preview ?? DefaultPreview;
if (Preview == null)
{
previewToUse = DefaultPreview;
ImagePaint.BlendMode = SKBlendMode.DstOut;
ImagePaint.Color = SKColor.Parse("#00175F");
}
var x = -125;
switch (previewToUse.Width)
{
case 512 when previewToUse.Height == 1024:
previewToUse = previewToUse.ResizeWithRatio(500, 1000);
x = 100;
break;
case 512 when previewToUse.Height == 512:
case 128 when previewToUse.Height == 128:
previewToUse = previewToUse.Resize(512);
x = 125;
break;
default:
previewToUse = previewToUse.Resize(1000, 1000);
break;
}
c.DrawBitmap(previewToUse, x, 30, ImagePaint);
}
private void DrawHaze(SKCanvas c)
{
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
rect1.MoveTo(29, 0);
rect1.LineTo(62, Height);
rect1.LineTo(Width, Height);
rect1.LineTo(Width, 0);
rect1.LineTo(29, 0);
rect1.Close();
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(343, 0), new SKPoint(343, Height),
new[] { SKColors.Transparent, SKColor.Parse("#001E70FF"), SKColor.Parse("#001E70").WithAlpha(200), SKColor.Parse("#001E70").WithAlpha(245), SKColor.Parse("#001E70") }, new[] { 0, (float) .1, (float) .65, (float) .85, 1 }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
}
private void DrawName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName)) return;
DisplayNamePaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 56,
_ => 42
};
DisplayNamePaint.TextScaleX = (float) 1.1;
DisplayNamePaint.Color = SKColors.White;
DisplayNamePaint.TextSkewX = (float) -.25;
DisplayNamePaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemDisplayName;
if (typeface == Utils.Typefaces.Default)
{
DisplayNamePaint.TextSize = 30;
}
DisplayNamePaint.Typeface = typeface;
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
c.DrawShapedText(shaper, DisplayName.ToUpper(), 97, 900, DisplayNamePaint);
}
private void DrawGeneralDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(_generalDescription)) return;
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 20,
_ => 17
};
DescriptionPaint.Color = SKColor.Parse("#00FFFB");
DescriptionPaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemGenDescription;
if (typeface == Utils.Typefaces.Default)
{
DescriptionPaint.TextSize = 21;
}
DescriptionPaint.Typeface = typeface;
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
c.DrawShapedText(shaper, _generalDescription.ToUpper(), 97, 930, DescriptionPaint);
}
private void DrawAdditionalDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(_additionalDescription)) return;
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 22,
_ => 18
};
DescriptionPaint.Color = SKColor.Parse("#89D8FF");
DescriptionPaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemAddDescription;
if (typeface == Utils.Typefaces.Default)
{
DescriptionPaint.TextSize = 20;
}
DescriptionPaint.Typeface = typeface;
Utils.DrawMultilineText(c, _additionalDescription, Width, 0, SKTextAlign.Left,
new SKRect(97, 960, Width - 10, Height), DescriptionPaint, out _);
}
}

View File

@ -9,185 +9,184 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseUserControl : UCreator
{
public class BaseUserControl : UCreator
private List<Options> _optionValues = new();
private readonly SKPaint _displayNamePaint = new()
{
private List<Options> _optionValues = 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
};
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 SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawInformation(c);
return new []{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 BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 512;
Height = 128;
Margin = 32;
}
public class Options
public override void ParseForInfo()
{
private const int _SPACE = 5;
private const int _HEIGHT = 30;
private readonly SKPaint _optionPaint = new()
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName"))
DisplayName = optionDisplayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FText optionDescription, "OptionDescription"))
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 20,
Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left
};
Description = optionDescription.Text;
public string Option;
public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150);
if (string.IsNullOrWhiteSpace(Description)) return;
Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count;
Height += (int) _descriptionPaint.TextSize;
}
public void Draw(SKCanvas c, int margin, int width, ref float top)
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues"))
{
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;
_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 SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawInformation(c);
return new[] { 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

@ -5,148 +5,147 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class Reward
{
public class Reward
private string _rewardQuantity;
private BaseIcon _theReward;
public bool HasReward() => _theReward != null;
public Reward()
{
private string _rewardQuantity;
private BaseIcon _theReward;
_rewardQuantity = "x0";
}
public bool HasReward() => _theReward != null;
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
{
}
public Reward()
public Reward(int quantity, string assetName) : this()
{
_rewardQuantity = $"x{quantity:###,###,###}".Trim();
if (assetName.Contains(':'))
{
_rewardQuantity = "x0";
}
var parts = assetName.Split(':');
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
{
}
public Reward(int quantity, string assetName) : this()
{
_rewardQuantity = $"x{quantity:###,###,###}".Trim();
if (assetName.Contains(':'))
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
{
var parts = assetName.Split(':');
if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p))
return;
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
_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/Athena/UI/Frontend/Art/T_UI_BP_BattleStar_L.T_UI_BP_BattleStar_L");
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-XPUncommon-L.T-FNBR-XPUncommon-L");
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))
{
if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p))
return;
_theReward = new BaseIcon(p, EIconStyle.Default);
_theReward = new BaseIcon(d, 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/Athena/UI/Frontend/Art/T_UI_BP_BattleStar_L.T_UI_BP_BattleStar_L");
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-XPUncommon-L.T-FNBR-XPUncommon-L");
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;
}
break;
}
}
}

View File

@ -4,45 +4,44 @@ using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.SB
namespace FModel.Creator.Bases.SB;
public class BaseDivision : UCreator
{
public class BaseDivision : UCreator
public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style)
{
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 SKBitmap[] Draw()
{
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 new []{ret};
}
}
}
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 SKBitmap[] Draw()
{
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 new[] { ret };
}
}

View File

@ -4,47 +4,46 @@ using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.i18N;
using SkiaSharp;
namespace FModel.Creator.Bases.SB
namespace FModel.Creator.Bases.SB;
public class BaseGameModeInfo : UCreator
{
public class BaseGameModeInfo : UCreator
private SKBitmap _icon;
public BaseGameModeInfo(UObject uObject, EIconStyle style) : base(uObject, style)
{
private SKBitmap _icon;
public BaseGameModeInfo(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 738;
Height = 1024;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description"))
Description = description.Text;
if (Object.TryGetValue(out UMaterialInstanceConstant portrait, "Portrait"))
Preview = Utils.GetBitmap(portrait);
if (Object.TryGetValue(out UTexture2D icon, "Icon"))
_icon = Utils.GetBitmap(icon).Resize(25);
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawIcon(c);
return new []{ret};
}
private void DrawIcon(SKCanvas c)
{
if (_icon == null) return;
c.DrawBitmap(_icon, new SKPoint(5, 5), ImagePaint);
}
Width = 738;
Height = 1024;
}
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description"))
Description = description.Text;
if (Object.TryGetValue(out UMaterialInstanceConstant portrait, "Portrait"))
Preview = Utils.GetBitmap(portrait);
if (Object.TryGetValue(out UTexture2D icon, "Icon"))
_icon = Utils.GetBitmap(icon).Resize(25);
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawIcon(c);
return new[] { ret };
}
private void DrawIcon(SKCanvas c)
{
if (_icon == null) return;
c.DrawBitmap(_icon, new SKPoint(5, 5), ImagePaint);
}
}

View File

@ -3,53 +3,52 @@ using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.SB
namespace FModel.Creator.Bases.SB;
public class BaseLeague : UCreator
{
public class BaseLeague : UCreator
private int _promotionXp, _xpLostPerMatch;
public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style)
{
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 SKBitmap[] Draw()
{
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 new []{ret};
}
_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 SKBitmap[] Draw()
{
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 new[] { ret };
}
}

View File

@ -7,92 +7,91 @@ using CUE4Parse.UE4.Objects.UObject;
using FModel.Creator.Bases.FN;
using SkiaSharp;
namespace FModel.Creator.Bases.SB
namespace FModel.Creator.Bases.SB;
public class BaseSpellIcon : BaseIcon
{
public class BaseSpellIcon : BaseIcon
private SKBitmap _seriesBackground2;
private readonly SKPaint _overlayPaint = new()
{
private SKBitmap _seriesBackground2;
FilterQuality = SKFilterQuality.High,
IsAntialias = true,
Color = SKColors.Transparent.WithAlpha(75)
};
private readonly SKPaint _overlayPaint = new()
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("GCosmeticCard") ? 1536 : 512;
Height = Object.ExportType.StartsWith("GCosmeticCard") ? 450 : 512;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FName rarity, "Rarity"))
GetRarity(rarity);
if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture", "OfferTexture", "PortraitTexture"))
Preview = Utils.GetBitmap(preview);
else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture", "OfferTexture", "PortraitTexture"))
Preview = Utils.GetBitmap(icon);
if (Object.TryGetValue(out FText displayName, "DisplayName", "Title", "Name"))
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 SKBitmap[] Draw()
{
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 new[] { 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
{
FilterQuality = SKFilterQuality.High,
IsAntialias = true,
Color = SKColors.Transparent.WithAlpha(75)
};
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(radi, radi), radi * 2 / 5 * 4,
Background, SKShaderTileMode.Clamp)
});
}
public BaseSpellIcon(UObject uObject, EIconStyle style) : base(uObject, style)
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))
{
Background = new[] {SKColor.Parse("FFFFFF"), SKColor.Parse("636363")};
Border = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF")};
Width = Object.ExportType.StartsWith("GCosmeticCard") ? 1536 : 512;
Height = Object.ExportType.StartsWith("GCosmeticCard") ? 450 : 512;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FName rarity, "Rarity"))
GetRarity(rarity);
if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture", "OfferTexture", "PortraitTexture"))
Preview = Utils.GetBitmap(preview);
else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture", "OfferTexture", "PortraitTexture"))
Preview = Utils.GetBitmap(icon);
if (Object.TryGetValue(out FText displayName, "DisplayName", "Title", "Name"))
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 SKBitmap[] Draw()
{
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 new []{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
if (row.TryGetValue(out FLinearColor[] colors, "Colors"))
{
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)};
}
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

@ -6,225 +6,224 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases
namespace FModel.Creator.Bases;
public abstract class UCreator
{
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 SKBitmap[] Draw();
protected UCreator(UObject uObject, EIconStyle style)
{
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; }
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;
}
public abstract void ParseForInfo();
public abstract SKBitmap[] Draw();
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
};
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;
}
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;
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
// border
c.DrawRect(new SKRect(0, 0, Width, Height),
new SKPaint
{
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;
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Border, SKShaderTileMode.Clamp)
});
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 (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
{
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);
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, _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;
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;
}
}
#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;
}
}
}
}
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

@ -6,247 +6,246 @@ using FModel.Creator.Bases.BB;
using FModel.Creator.Bases.FN;
using FModel.Creator.Bases.SB;
namespace FModel.Creator
namespace FModel.Creator;
public class CreatorPackage : IDisposable
{
public class CreatorPackage : IDisposable
private UObject _object;
private EIconStyle _style;
public CreatorPackage(UObject uObject, EIconStyle style)
{
private UObject _object;
private EIconStyle _style;
_object = uObject;
_style = style;
}
public CreatorPackage(UObject uObject, EIconStyle 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)
{
_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 "FortCreativeWeaponMeleeItemDefinition":
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 "AthenaCharmItemDefinition":
case "FortPlaysetItemDefinition":
case "FortGiftBoxItemDefinition":
case "FortOutpostItemDefinition":
case "FortVehicleItemDefinition":
case "FortCardPackItemDefinition":
case "FortDefenderItemDefinition":
case "FortCurrencyItemDefinition":
case "FortResourceItemDefinition":
case "FortBackpackItemDefinition":
case "FortEventQuestMapDataAsset":
case "FortWeaponModItemDefinition":
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 "FortTandemCharacterData":
creator = new BaseTandem(_object, _style);
return true;
case "FortTrapItemDefinition":
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}", StringComparison.OrdinalIgnoreCase) ||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
_object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)):
creator = new BaseMaterialInstance(_object, _style);
return true;
case "AthenaItemShopOfferDisplayData":
creator = new BaseOfferDisplayData(_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 "FortQuestItemDefinition_Athena":
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 "PlaylistUserTintedIconIntEnum":
case "PlaylistUserOptionPrimaryAsset":
case "PlaylistUserOptionCollisionProfileEnum":
creator = new BaseUserControl(_object, _style);
return true;
// Battle Breakers
case "WExpGenericAccountItemDefinition":
case "WExpGearAccountItemDefinition":
case "WExpHQWorkerLodgesDefinition":
case "WExpPersonalEventDefinition":
case "WExpUpgradePotionDefinition":
case "WExpAccountRewardDefinition":
case "WExpHQBlacksmithDefinition":
case "WExpHQSecretShopDefinition":
case "WExpHQMonsterPitDefinition":
case "WExpHQHeroTowerDefinition":
case "WExpVoucherItemDefinition":
case "WExpTreasureMapDefinition":
case "WExpHammerChestDefinition":
case "WExpHQWorkshopDefinition":
case "WExpUnlockableDefinition":
case "WExpHQSmelterDefinition":
case "WExpContainerDefinition":
case "WExpCharacterDefinition":
case "WExpHQMarketDefinition":
case "WExpGiftboxDefinition":
case "WExpStandInDefinition":
case "WExpRegionDefinition":
case "WExpHQMineDefinition":
case "WExpXpBookDefinition":
case "WExpTokenDefinition":
case "WExpItemDefinition":
case "WExpZoneDefinition":
creator = new BaseBreakersIcon(_object, EIconStyle.Default);
return true;
// Spellbreak
case "GTargetedTeleportActiveSkill":
case "GChronomasterV2ActiveSkill":
case "GShadowstepActiveSkill":
case "GGatewayActiveSkill":
case "GStealthActiveSkill":
case "GFeatherActiveSkill":
case "GCosmeticDropTrail":
case "GFlightActiveSkill":
case "GCosmeticRunTrail":
case "GCosmeticArtifact":
case "GCosmeticTriumph":
case "GWolfsbloodSkill":
case "GDashActiveSkill":
case "GCharacterPerk":
case "GCosmeticTitle":
case "GCosmeticBadge":
case "GRMTStoreOffer":
case "GCosmeticEmote":
case "GCosmeticCard":
case "GCosmeticSkin":
case "GStoreOffer":
case "GAccolade":
case "GRuneItem":
case "GQuest":
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;
// Fortnite
case "FortCreativeWeaponMeleeItemDefinition":
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 "AthenaCharmItemDefinition":
case "FortPlaysetItemDefinition":
case "FortGiftBoxItemDefinition":
case "FortOutpostItemDefinition":
case "FortVehicleItemDefinition":
case "FortCardPackItemDefinition":
case "FortDefenderItemDefinition":
case "FortCurrencyItemDefinition":
case "FortResourceItemDefinition":
case "FortBackpackItemDefinition":
case "FortEventQuestMapDataAsset":
case "FortWeaponModItemDefinition":
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 "FortTandemCharacterData":
creator = new BaseTandem(_object, _style);
return true;
case "FortTrapItemDefinition":
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}", StringComparison.OrdinalIgnoreCase) ||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
_object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)):
creator = new BaseMaterialInstance(_object, _style);
return true;
case "AthenaItemShopOfferDisplayData":
creator = new BaseOfferDisplayData(_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 "FortQuestItemDefinition_Athena":
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 "PlaylistUserTintedIconIntEnum":
case "PlaylistUserOptionPrimaryAsset":
case "PlaylistUserOptionCollisionProfileEnum":
creator = new BaseUserControl(_object, _style);
return true;
// Battle Breakers
case "WExpGenericAccountItemDefinition":
case "WExpGearAccountItemDefinition":
case "WExpHQWorkerLodgesDefinition":
case "WExpPersonalEventDefinition":
case "WExpUpgradePotionDefinition":
case "WExpAccountRewardDefinition":
case "WExpHQBlacksmithDefinition":
case "WExpHQSecretShopDefinition":
case "WExpHQMonsterPitDefinition":
case "WExpHQHeroTowerDefinition":
case "WExpVoucherItemDefinition":
case "WExpTreasureMapDefinition":
case "WExpHammerChestDefinition":
case "WExpHQWorkshopDefinition":
case "WExpUnlockableDefinition":
case "WExpHQSmelterDefinition":
case "WExpContainerDefinition":
case "WExpCharacterDefinition":
case "WExpHQMarketDefinition":
case "WExpGiftboxDefinition":
case "WExpStandInDefinition":
case "WExpRegionDefinition":
case "WExpHQMineDefinition":
case "WExpXpBookDefinition":
case "WExpTokenDefinition":
case "WExpItemDefinition":
case "WExpZoneDefinition":
creator = new BaseBreakersIcon(_object, EIconStyle.Default);
return true;
// Spellbreak
case "GTargetedTeleportActiveSkill":
case "GChronomasterV2ActiveSkill":
case "GShadowstepActiveSkill":
case "GGatewayActiveSkill":
case "GStealthActiveSkill":
case "GFeatherActiveSkill":
case "GCosmeticDropTrail":
case "GFlightActiveSkill":
case "GCosmeticRunTrail":
case "GCosmeticArtifact":
case "GCosmeticTriumph":
case "GWolfsbloodSkill":
case "GDashActiveSkill":
case "GCharacterPerk":
case "GCosmeticTitle":
case "GCosmeticBadge":
case "GRMTStoreOffer":
case "GCosmeticEmote":
case "GCosmeticCard":
case "GCosmeticSkin":
case "GStoreOffer":
case "GAccolade":
case "GRuneItem":
case "GQuest":
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

@ -6,274 +6,273 @@ using FModel.Settings;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator
namespace FModel.Creator;
public class Typefaces
{
public class Typefaces
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new("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";
// Spellbreak
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";
// 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 readonly SKTypeface TandemDisplayName;
public readonly SKTypeface TandemGenDescription;
public readonly SKTypeface TandemAddDescription;
public Typefaces(CUE4ParseViewModel viewModel)
{
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf");
private const string _EXT = ".ufont";
byte[] data;
_viewModel = viewModel;
var language = UserSettings.Default.AssetLanguage;
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
// 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";
// Spellbreak
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";
// 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 readonly SKTypeface TandemDisplayName;
public readonly SKTypeface TandemGenDescription;
public readonly SKTypeface TandemAddDescription;
public Typefaces(CUE4ParseViewModel viewModel)
switch (viewModel.Game)
{
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:
{
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 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 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 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 +
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 => string.Empty,
ELanguage.Japanese => string.Empty,
ELanguage.Arabic => string.Empty,
ELanguage.TraditionalChinese => string.Empty,
ELanguage.Chinese => string.Empty,
_ => _BURBANK_SMALL_BOLD
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(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;
var tandemDisplayNamePath = _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,
_ => _BURBANK_BIG_REGULAR_BLACK
};
if (viewModel.Provider.TrySaveAsset(tandemDisplayNamePath + _EXT, out data))
{
var m = new MemoryStream(data) { Position = 0 };
TandemDisplayName = SKTypeface.FromStream(m);
}
else TandemDisplayName = Default;
var tandemGeneralDescPath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NIS_JYAU,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_SMALL_BLACK
};
if (viewModel.Provider.TrySaveAsset(tandemGeneralDescPath + _EXT, out data))
{
var m = new MemoryStream(data) { Position = 0 };
TandemGenDescription = SKTypeface.FromStream(m);
}
else TandemGenDescription = Default;
var tandemAdditionalDescPath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NIS_JYAU,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_SMALL_BOLD
};
if (viewModel.Provider.TrySaveAsset(tandemAdditionalDescPath + _EXT, out data))
{
var m = new MemoryStream(data) { Position = 0 };
TandemAddDescription = SKTypeface.FromStream(m);
}
else TandemAddDescription = Default;
break;
}
case FGame.WorldExplorers:
if (viewModel.Provider.TrySaveAsset(bundleNamePath + _EXT, out data))
{
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;
var m = new MemoryStream(data) { Position = 0 };
Bundle = SKTypeface.FromStream(m);
}
case FGame.g3:
else Bundle = BundleNumber;
var tandemDisplayNamePath = _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,
_ => _BURBANK_BIG_REGULAR_BLACK
};
if (viewModel.Provider.TrySaveAsset(tandemDisplayNamePath + _EXT, out data))
{
if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD + _EXT, out data))
{
var m = new MemoryStream(data) { Position = 0 };
DisplayName = SKTypeface.FromStream(m);
}
else DisplayName = Default;
if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD + _EXT, out data))
{
var m = new MemoryStream(data) { Position = 0 };
Description = SKTypeface.FromStream(m);
}
else Description = Default;
break;
var m = new MemoryStream(data) { Position = 0 };
TandemDisplayName = SKTypeface.FromStream(m);
}
else TandemDisplayName = Default;
var tandemGeneralDescPath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NIS_JYAU,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_SMALL_BLACK
};
if (viewModel.Provider.TrySaveAsset(tandemGeneralDescPath + _EXT, out data))
{
var m = new MemoryStream(data) { Position = 0 };
TandemGenDescription = SKTypeface.FromStream(m);
}
else TandemGenDescription = Default;
var tandemAdditionalDescPath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NIS_JYAU,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_SMALL_BOLD
};
if (viewModel.Provider.TrySaveAsset(tandemAdditionalDescPath + _EXT, out data))
{
var m = new MemoryStream(data) { Position = 0 };
TandemAddDescription = SKTypeface.FromStream(m);
}
else TandemAddDescription = Default;
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.g3:
{
if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD + _EXT, out data))
{
var m = new MemoryStream(data) { Position = 0 };
DisplayName = SKTypeface.FromStream(m);
}
else DisplayName = Default;
if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD + _EXT, out data))
{
var m = new MemoryStream(data) { Position = 0 };
Description = SKTypeface.FromStream(m);
}
else Description = Default;
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);
}
}
}
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

@ -4,7 +4,6 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
@ -19,382 +18,377 @@ using FModel.ViewModels;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator
namespace FModel.Creator;
public static class Utils
{
public static class Utils
private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private static readonly Regex _htmlRegex = new("<.*?>");
public static Typefaces Typefaces;
public static string RemoveHtmlTags(string s)
{
private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private static readonly Regex _htmlRegex = new("<.*?>");
public static Typefaces Typefaces;
public static string RemoveHtmlTags(string s)
var match = _htmlRegex.Match(s);
while (match.Success)
{
var match = _htmlRegex.Match(s);
while (match.Success)
{
s = s.Replace(match.Value, string.Empty);
match = match.NextMatch();
}
return s;
s = s.Replace(match.Value, string.Empty);
match = match.NextMatch();
}
public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview)
return s;
}
public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview)
{
if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
{
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);
preview = GetBitmap(sidePanelIcon);
return preview != null;
}
public static SKBitmap GetBitmap(FPackageIndex packageIndex)
var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}";
if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition
{
while (true)
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 GetBitmap(FPackageIndex packageIndex)
{
while (true)
{
if (!TryGetPackageIndexExport(packageIndex, out UObject export)) return null;
switch (export)
{
if (!TryGetPackageIndexExport(packageIndex, out UObject export)) return null;
switch (export)
case UTexture2D texture:
return GetBitmap(texture);
case UMaterialInstanceConstant material:
return GetBitmap(material);
default:
{
case UTexture2D texture:
return GetBitmap(texture);
case UMaterialInstanceConstant material:
return GetBitmap(material);
default:
if (export.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage);
if (export.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
if (export.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage"))
{
if (export.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage);
if (export.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
if (export.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage"))
{
packageIndex = smallPreview;
continue;
}
return null;
packageIndex = smallPreview;
continue;
}
return null;
}
}
}
public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
{
if (material == null) return null;
foreach (var textureParameter in material.TextureParameterValues)
{
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture)) continue;
switch (textureParameter.ParameterInfo.Name.Text)
{
case "MainTex":
case "TextureA":
case "TextureB":
case "OfferImage":
case "KeyArtTexture":
case "NPC-Portrait":
{
return GetBitmap(texture);
}
}
}
return null;
}
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 : texture.Decode(UserSettings.Default.OverridedPlatform);
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height)
{
var ratioX = width / me.Width;
var ratioY = height / me.Height;
var ratio = ratioX < ratioY ? ratioX : ratioY;
return me.Resize(Convert.ToInt32(me.Width * ratio), Convert.ToInt32(me.Height * ratio));
}
public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size);
public static SKBitmap Resize(this SKBitmap me, int width, int 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 void ClearToTransparent(this SKBitmap me, SKColor colorToDelete) {
var colors = me.Pixels;
for (var n = 0; n < colors.Length; n++) {
if (colors[n] != colorToDelete) continue;
colors[n] = SKColors.Transparent;
}
me.Pixels = colors;
}
public static bool TryGetPackageIndexExport<T>(FPackageIndex packageIndex, out T export) where T : UObject
{
return packageIndex.TryLoad(out export);
}
// fullpath must be either without any extension or with the export objectname
public static bool TryLoadObject<T>(string fullPath, out T export) where T : UObject
{
return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export);
}
public static IEnumerable<UObject> LoadExports(string fullPath)
{
return _applicationView.CUE4Parse.Provider.LoadObjectExports(fullPath);
}
public static float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f)
{
var max = maxFont;
var min = 0f;
var last = -1f;
float value;
while (true)
{
value = min + ((max - min) / 2);
using (SKFont ft = new SKFont(typeface, value))
using (SKPaint paint = new SKPaint(ft))
{
if (paint.MeasureText(text) > sectorSize)
{
last = value;
max = value;
}
else
{
min = value;
if (Math.Abs(last - value) <= degreeOfCertainty)
return last;
last = value;
}
}
}
}
public static string GetLocalizedResource(string @namespace, string key, string defaultValue)
{
return _applicationView.CUE4Parse.Provider.GetLocalizedString(@namespace, 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)
{
var fontSize = GetMaxFontSize(area.Width, paint.Typeface, line);
if (paint.TextSize > fontSize) // if the text is not fitting in the line decrease the font size (CKJ languages)
{
paint.TextSize = fontSize;
lineHeight = paint.TextSize * 1.2f;
}
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
}
#region Chinese, Korean and Japanese text split
// https://github.com/YoungjaeKim/mikan.sharp/blob/master/MikanSharp/Mikan/Mikan.cs
static string joshi = @"(でなければ|について|かしら|くらい|けれど|なのか|ばかり|ながら|ことよ|こそ|こと|さえ|しか|した|たり|だけ|だに|だの|つつ|ても|てよ|でも|とも|から|など|なり|ので|のに|ほど|まで|もの|やら|より|って|で|と|な|に|ね|の|も|は|ば|へ|や|わ|を|か|が|さ|し|ぞ|て)";
static string keywords = @"(\&nbsp;|[a-zA-Z0-9]+\.[a-z]{2,}|[一-龠々〆ヵヶゝ]+|[ぁ-んゝ]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[---]+)";
static string periods = @"([\.\,。、!\!\?]+)$";
static string bracketsBegin = @"([〈《「『「((\[【〔〚〖〘❮❬❪❨(<{❲❰{❴])";
static string bracketsEnd = @"([〉》」』」)\]】〕〗〙〛}>\)❩❫❭❯❱❳❵}])";
public static string[] SplitCKJText(string str)
{
var line1 = Regex.Split(str, keywords).ToList();
var line2 = line1.SelectMany((o, _) => Regex.Split(o, joshi)).ToList();
var line3 = line2.SelectMany((o, _) => Regex.Split(o, bracketsBegin)).ToList();
var line4 = line3.SelectMany((o, _) => Regex.Split(o, bracketsEnd)).ToList();
var words = line4.Where(o => !string.IsNullOrEmpty(o)).ToList();
var prevType = string.Empty;
var prevWord = string.Empty;
List<string> result = new List<string>();
words.ForEach(word =>
{
var token = Regex.IsMatch(word, periods) || Regex.IsMatch(word, joshi);
if (Regex.IsMatch(word, bracketsBegin))
{
prevType = "braketBegin";
prevWord = word;
return;
}
if (Regex.IsMatch(word, bracketsEnd))
{
result[result.Count - 1] += word;
prevType = "braketEnd";
prevWord = word;
return;
}
if (prevType == "braketBegin")
{
word = prevWord + word;
prevWord = string.Empty;
prevType = string.Empty;
}
// すでに文字が入っている上で助詞が続く場合は結合する
if (result.Count > 0 && token && prevType == string.Empty)
{
result[result.Count - 1] += word;
prevType = "keyword";
prevWord = word;
return;
}
// 単語のあとの文字がひらがななら結合する
if (result.Count > 1 && token || (prevType == "keyword" && Regex.IsMatch(word, @"[ぁ-んゝ]+")))
{
result[result.Count - 1] += word;
prevType = string.Empty;
prevWord = word;
return;
}
result.Add(word);
prevType = "keyword";
prevWord = word;
});
return result.ToArray();
}
#endregion
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;
bool isCJK = false;
var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (words.Length <= 1 && UserSettings.Default.AssetLanguage is ELanguage.Japanese or ELanguage.Korean or ELanguage.Chinese or ELanguage.TraditionalChinese)
{
words = SplitCKJText(line);
isCJK = true;
}
var lineResult = new StringBuilder();
foreach (var word in words)
{
var wordWidth = paint.MeasureText(word);
var wordWithSpaceWidth = wordWidth + spaceWidth;
var wordWithSpace = isCJK ? word : 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;
}
}
}
public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
{
if (material == null) return null;
foreach (var textureParameter in material.TextureParameterValues)
{
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture)) continue;
switch (textureParameter.ParameterInfo.Name.Text)
{
case "MainTex":
case "TextureA":
case "TextureB":
case "OfferImage":
case "KeyArtTexture":
case "NPC-Portrait":
{
return GetBitmap(texture);
}
}
}
return null;
}
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 : texture.Decode(UserSettings.Default.OverridedPlatform);
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height)
{
var ratioX = width / me.Width;
var ratioY = height / me.Height;
var ratio = ratioX < ratioY ? ratioX : ratioY;
return me.Resize(Convert.ToInt32(me.Width * ratio), Convert.ToInt32(me.Height * ratio));
}
public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size);
public static SKBitmap Resize(this SKBitmap me, int width, int 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 bool TryGetPackageIndexExport<T>(FPackageIndex packageIndex, out T export) where T : UObject
{
return packageIndex.TryLoad(out export);
}
// fullpath must be either without any extension or with the export objectname
public static bool TryLoadObject<T>(string fullPath, out T export) where T : UObject
{
return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export);
}
public static IEnumerable<UObject> LoadExports(string fullPath)
{
return _applicationView.CUE4Parse.Provider.LoadObjectExports(fullPath);
}
public static float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f)
{
var max = maxFont;
var min = 0f;
var last = -1f;
float value;
while (true)
{
value = min + ((max - min) / 2);
using (SKFont ft = new SKFont(typeface, value))
using (SKPaint paint = new SKPaint(ft))
{
if (paint.MeasureText(text) > sectorSize)
{
last = value;
max = value;
}
else
{
min = value;
if (Math.Abs(last - value) <= degreeOfCertainty)
return last;
last = value;
}
}
}
}
public static string GetLocalizedResource(string @namespace, string key, string defaultValue)
{
return _applicationView.CUE4Parse.Provider.GetLocalizedString(@namespace, 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)
{
var fontSize = GetMaxFontSize(area.Width, paint.Typeface, line);
if (paint.TextSize > fontSize) // if the text is not fitting in the line decrease the font size (CKJ languages)
{
paint.TextSize = fontSize;
lineHeight = paint.TextSize * 1.2f;
}
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
}
#region Chinese, Korean and Japanese text split
// https://github.com/YoungjaeKim/mikan.sharp/blob/master/MikanSharp/Mikan/Mikan.cs
static string joshi = @"(でなければ|について|かしら|くらい|けれど|なのか|ばかり|ながら|ことよ|こそ|こと|さえ|しか|した|たり|だけ|だに|だの|つつ|ても|てよ|でも|とも|から|など|なり|ので|のに|ほど|まで|もの|やら|より|って|で|と|な|に|ね|の|も|は|ば|へ|や|わ|を|か|が|さ|し|ぞ|て)";
static string keywords = @"(\&nbsp;|[a-zA-Z0-9]+\.[a-z]{2,}|[一-龠々〆ヵヶゝ]+|[ぁ-んゝ]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[---]+)";
static string periods = @"([\.\,。、!\!\?]+)$";
static string bracketsBegin = @"([〈《「『「((\[【〔〚〖〘❮❬❪❨(<{❲❰{❴])";
static string bracketsEnd = @"([〉》」』」)\]】〕〗〙〛}>\)❩❫❭❯❱❳❵}])";
public static string[] SplitCKJText(string str)
{
var line1 = Regex.Split(str, keywords).ToList();
var line2 = line1.SelectMany((o, _) => Regex.Split(o, joshi)).ToList();
var line3 = line2.SelectMany((o, _) => Regex.Split(o, bracketsBegin)).ToList();
var line4 = line3.SelectMany((o, _) => Regex.Split(o, bracketsEnd)).ToList();
var words = line4.Where(o => !string.IsNullOrEmpty(o)).ToList();
var prevType = string.Empty;
var prevWord = string.Empty;
List<string> result = new List<string>();
words.ForEach(word =>
{
var token = Regex.IsMatch(word, periods) || Regex.IsMatch(word, joshi);
if (Regex.IsMatch(word, bracketsBegin))
{
prevType = "braketBegin";
prevWord = word;
return;
}
if (Regex.IsMatch(word, bracketsEnd))
{
result[result.Count - 1] += word;
prevType = "braketEnd";
prevWord = word;
return;
}
if (prevType == "braketBegin")
{
word = prevWord + word;
prevWord = string.Empty;
prevType = string.Empty;
}
// すでに文字が入っている上で助詞が続く場合は結合する
if (result.Count > 0 && token && prevType == string.Empty)
{
result[result.Count - 1] += word;
prevType = "keyword";
prevWord = word;
return;
}
// 単語のあとの文字がひらがななら結合する
if (result.Count > 1 && token || (prevType == "keyword" && Regex.IsMatch(word, @"[ぁ-んゝ]+")))
{
result[result.Count - 1] += word;
prevType = string.Empty;
prevWord = word;
return;
}
result.Add(word);
prevType = "keyword";
prevWord = word;
});
return result.ToArray();
}
#endregion
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 isCJK = false;
var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (words.Length <= 1 && UserSettings.Default.AssetLanguage is ELanguage.Japanese or ELanguage.Korean or ELanguage.Chinese or ELanguage.TraditionalChinese)
{
words = SplitCKJText(line);
isCJK = true;
}
var lineResult = new StringBuilder();
foreach (var word in words)
{
var wordWidth = paint.MeasureText(word);
var wordWithSpaceWidth = wordWidth + spaceWidth;
var wordWithSpace = isCJK ? word : 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,142 +1,141 @@
using System.ComponentModel;
namespace FModel
namespace FModel;
public enum EBuildKind
{
public enum EBuildKind
{
Debug,
Release,
Unknown
}
public enum EErrorKind
{
Ignore,
Restart,
ResetSettings
}
public enum SettingsOut
{
Restart,
ReloadLocres,
CheckForUpdates,
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 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,
[Description("Days Gone")]
BendGame,
[Description("PLAYERUNKNOWN'S BATTLEGROUNDS")]
TslGame,
[Description("Splitgate")]
PortalWars,
[Description("GTA: The Trilogy - Definitive Edition")]
Gameface,
[Description("Sea of Thieves")]
Athena
}
public enum ELoadingMode
{
[Description("Single")]
Single,
[Description("Multiple")]
Multiple,
[Description("All")]
All,
[Description("All (New)")]
AllButNew,
[Description("All (Modified)")]
AllButModified
}
public enum EUpdateMode
{
[Description("Stable")]
Stable,
[Description("Beta")]
Beta
}
public enum ECompressedAudio
{
[Description("Play the decompressed data")]
PlayDecompressed,
[Description("Play the compressed data (might not always be a valid audio data)")]
PlayCompressed
}
public enum EIconStyle
{
[Description("Default")]
Default,
[Description("No Background")]
NoBackground,
[Description("No Text")]
NoText,
[Description("Flat")]
Flat,
[Description("Cataba")]
Cataba,
// [Description("Community")]
// CommunityMade
}
Debug,
Release,
Unknown
}
public enum EErrorKind
{
Ignore,
Restart,
ResetSettings
}
public enum SettingsOut
{
Restart,
ReloadLocres,
CheckForUpdates,
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 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,
[Description("Days Gone")]
BendGame,
[Description("PLAYERUNKNOWN'S BATTLEGROUNDS")]
TslGame,
[Description("Splitgate")]
PortalWars,
[Description("GTA: The Trilogy - Definitive Edition")]
Gameface,
[Description("Sea of Thieves")]
Athena
}
public enum ELoadingMode
{
[Description("Single")]
Single,
[Description("Multiple")]
Multiple,
[Description("All")]
All,
[Description("All (New)")]
AllButNew,
[Description("All (Modified)")]
AllButModified
}
public enum EUpdateMode
{
[Description("Stable")]
Stable,
[Description("Beta")]
Beta
}
public enum ECompressedAudio
{
[Description("Play the decompressed data")]
PlayDecompressed,
[Description("Play the compressed data (might not always be a valid audio data)")]
PlayCompressed
}
public enum EIconStyle
{
[Description("Default")]
Default,
[Description("No Background")]
NoBackground,
[Description("No Text")]
NoText,
[Description("Flat")]
Flat,
[Description("Cataba")]
Cataba,
// [Description("Community")]
// CommunityMade
}

View File

@ -4,47 +4,46 @@ using System.Xml;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
namespace FModel.Extensions
namespace FModel.Extensions;
public static class AvalonExtensions
{
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");
private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IHighlightingDefinition LoadHighlighter(string resourceName)
{
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");
private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd");
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)]
private static IHighlightingDefinition LoadHighlighter(string resourceName)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IHighlightingDefinition HighlighterSelector(string ext)
{
switch (ext)
{
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 "changelog":
return _changelogHighlighter;
case "bat":
case "txt":
case "po":
return null;
default:
return _jsonHighlighter;
}
case "ini":
case "csv":
return _iniHighlighter;
case "xml":
return _xmlHighlighter;
case "h":
case "cpp":
return _cppHighlighter;
case "changelog":
return _changelogHighlighter;
case "bat":
case "txt":
case "po":
return null;
default:
return _jsonHighlighter;
}
}
}

View File

@ -1,5 +1,4 @@
using SkiaSharp;
using System;
using System.Drawing;
using System.Drawing.Imaging;
@ -8,191 +7,192 @@ using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
namespace FModel.Extensions
namespace FModel.Extensions;
public static class ClipboardExtensions
{
public static class ClipboardExtensions
public static void SetImage(byte[] pngBytes, string fileName = null)
{
public static void SetImage(byte[] pngBytes, string fileName = null)
Clipboard.Clear();
var data = new DataObject();
using var pngMs = new MemoryStream(pngBytes);
using var image = Image.FromStream(pngMs);
// As standard bitmap, without transparency support
data.SetData(DataFormats.Bitmap, image, true);
// As PNG. Gimp will prefer this over the other two
data.SetData("PNG", pngMs, false);
// As DIB. This is (wrongly) accepted as ARGB by many applications
using var dibMemStream = new MemoryStream(ConvertToDib(image));
data.SetData(DataFormats.Dib, dibMemStream, false);
// Optional fileName
if (!string.IsNullOrEmpty(fileName))
{
Clipboard.Clear();
var data = new DataObject();
using var pngMs = new MemoryStream(pngBytes);
using var image = Image.FromStream(pngMs);
// As standard bitmap, without transparency support
data.SetData(DataFormats.Bitmap, image, true);
// As PNG. Gimp will prefer this over the other two
data.SetData("PNG", pngMs, false);
// As DIB. This is (wrongly) accepted as ARGB by many applications
using var dibMemStream = new MemoryStream(ConvertToDib(image));
data.SetData(DataFormats.Dib, dibMemStream, false);
// Optional fileName
if (!string.IsNullOrEmpty(fileName))
var htmlFragment = GenerateHTMLFragment($"<img src=\"{fileName}\"/>");
data.SetData(DataFormats.Html, htmlFragment);
}
// The 'copy=true' argument means the MemoryStreams can be safely disposed after the operation
Clipboard.SetDataObject(data, true);
}
public static byte[] ConvertToDib(Image image)
{
byte[] bm32bData;
var width = image.Width;
var height = image.Height;
// Ensure image is 32bppARGB by painting it on a new 32bppARGB image.
using (var bm32b = new Bitmap(width, height, PixelFormat.Format32bppPArgb))
{
using (var gr = Graphics.FromImage(bm32b))
{
var htmlFragment = GenerateHTMLFragment($"<img src=\"{fileName}\"/>");
data.SetData(DataFormats.Html, htmlFragment);
gr.DrawImage(image, new Rectangle(0, 0, width, height));
}
// The 'copy=true' argument means the MemoryStreams can be safely disposed after the operation
Clipboard.SetDataObject(data, true);
// Bitmap format has its lines reversed.
bm32b.RotateFlip(RotateFlipType.Rotate180FlipX);
bm32bData = GetRawBytes(bm32b);
}
public static byte[] ConvertToDib(Image image)
// BITMAPINFOHEADER struct for DIB.
const int hdrSize = 0x28;
var fullImage = new byte[hdrSize + 12 + bm32bData.Length];
//Int32 biSize;
WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize);
//Int32 biWidth;
WriteIntToByteArray(fullImage, 0x04, 4, true, (uint) width);
//Int32 biHeight;
WriteIntToByteArray(fullImage, 0x08, 4, true, (uint) height);
//Int16 biPlanes;
WriteIntToByteArray(fullImage, 0x0C, 2, true, 1);
//Int16 biBitCount;
WriteIntToByteArray(fullImage, 0x0E, 2, true, 32);
//BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS;
WriteIntToByteArray(fullImage, 0x10, 4, true, 3);
//Int32 biSizeImage;
WriteIntToByteArray(fullImage, 0x14, 4, true, (uint) bm32bData.Length);
// These are all 0. Since .net clears new arrays, don't bother writing them.
//Int32 biXPelsPerMeter = 0;
//Int32 biYPelsPerMeter = 0;
//Int32 biClrUsed = 0;
//Int32 biClrImportant = 0;
// The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values.
WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000);
WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00);
WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF);
Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint) bm32bData.Length);
return fullImage;
}
private static byte[] ConvertToDib(byte[] pngBytes = null)
{
byte[] bm32bData;
int width, height;
using (var skBmp = SKBitmap.Decode(pngBytes))
{
byte[] bm32bData;
var width = image.Width;
var height = image.Height;
// Ensure image is 32bppARGB by painting it on a new 32bppARGB image.
using (var bm32b = new Bitmap(width, height, PixelFormat.Format32bppPArgb))
{
using (var gr = Graphics.FromImage(bm32b))
{
gr.DrawImage(image, new Rectangle(0, 0, width, height));
}
// Bitmap format has its lines reversed.
bm32b.RotateFlip(RotateFlipType.Rotate180FlipX);
bm32bData = GetRawBytes(bm32b);
}
// BITMAPINFOHEADER struct for DIB.
const int hdrSize = 0x28;
var fullImage = new byte[hdrSize + 12 + bm32bData.Length];
//Int32 biSize;
WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize);
//Int32 biWidth;
WriteIntToByteArray(fullImage, 0x04, 4, true, (uint)width);
//Int32 biHeight;
WriteIntToByteArray(fullImage, 0x08, 4, true, (uint)height);
//Int16 biPlanes;
WriteIntToByteArray(fullImage, 0x0C, 2, true, 1);
//Int16 biBitCount;
WriteIntToByteArray(fullImage, 0x0E, 2, true, 32);
//BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS;
WriteIntToByteArray(fullImage, 0x10, 4, true, 3);
//Int32 biSizeImage;
WriteIntToByteArray(fullImage, 0x14, 4, true, (uint)bm32bData.Length);
// These are all 0. Since .net clears new arrays, don't bother writing them.
//Int32 biXPelsPerMeter = 0;
//Int32 biYPelsPerMeter = 0;
//Int32 biClrUsed = 0;
//Int32 biClrImportant = 0;
// The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values.
WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000);
WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00);
WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF);
Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint)bm32bData.Length);
return fullImage;
width = skBmp.Width;
height = skBmp.Height;
using var rotated = new SKBitmap(new SKImageInfo(width, height, skBmp.ColorType));
using var canvas = new SKCanvas(rotated);
canvas.Scale(1, -1, 0, height / 2.0f);
canvas.DrawBitmap(skBmp, SKPoint.Empty);
canvas.Flush();
bm32bData = rotated.Bytes;
}
private static byte[] ConvertToDib(byte[] pngBytes = null)
// BITMAPINFOHEADER struct for DIB.
const int hdrSize = 0x28;
var fullImage = new byte[hdrSize + 12 + bm32bData.Length];
//Int32 biSize;
WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize);
//Int32 biWidth;
WriteIntToByteArray(fullImage, 0x04, 4, true, (uint) width);
//Int32 biHeight;
WriteIntToByteArray(fullImage, 0x08, 4, true, (uint) height);
//Int16 biPlanes;
WriteIntToByteArray(fullImage, 0x0C, 2, true, 1);
//Int16 biBitCount;
WriteIntToByteArray(fullImage, 0x0E, 2, true, 32);
//BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS;
WriteIntToByteArray(fullImage, 0x10, 4, true, 3);
//Int32 biSizeImage;
WriteIntToByteArray(fullImage, 0x14, 4, true, (uint) bm32bData.Length);
// These are all 0. Since .net clears new arrays, don't bother writing them.
//Int32 biXPelsPerMeter = 0;
//Int32 biYPelsPerMeter = 0;
//Int32 biClrUsed = 0;
//Int32 biClrImportant = 0;
// The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values.
WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000);
WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00);
WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF);
Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint) bm32bData.Length);
return fullImage;
}
public static unsafe byte[] GetRawBytes(Bitmap bmp)
{
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
var bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);
var bytes = (uint) (Math.Abs(bmpData.Stride) * bmp.Height);
var buffer = new byte[bytes];
fixed (byte* pBuffer = buffer)
{
byte[] bm32bData;
int width, height;
using (var skBmp = SKBitmap.Decode(pngBytes))
{
width = skBmp.Width;
height = skBmp.Height;
using var rotated = new SKBitmap(new SKImageInfo(width, height, skBmp.ColorType));
using var canvas = new SKCanvas(rotated);
canvas.Scale(1, -1, 0, height / 2.0f);
canvas.DrawBitmap(skBmp, SKPoint.Empty);
canvas.Flush();
bm32bData = rotated.Bytes;
}
// BITMAPINFOHEADER struct for DIB.
const int hdrSize = 0x28;
var fullImage = new byte[hdrSize + 12 + bm32bData.Length];
//Int32 biSize;
WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize);
//Int32 biWidth;
WriteIntToByteArray(fullImage, 0x04, 4, true, (uint)width);
//Int32 biHeight;
WriteIntToByteArray(fullImage, 0x08, 4, true, (uint)height);
//Int16 biPlanes;
WriteIntToByteArray(fullImage, 0x0C, 2, true, 1);
//Int16 biBitCount;
WriteIntToByteArray(fullImage, 0x0E, 2, true, 32);
//BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS;
WriteIntToByteArray(fullImage, 0x10, 4, true, 3);
//Int32 biSizeImage;
WriteIntToByteArray(fullImage, 0x14, 4, true, (uint)bm32bData.Length);
// These are all 0. Since .net clears new arrays, don't bother writing them.
//Int32 biXPelsPerMeter = 0;
//Int32 biYPelsPerMeter = 0;
//Int32 biClrUsed = 0;
//Int32 biClrImportant = 0;
// The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values.
WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000);
WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00);
WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF);
Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint)bm32bData.Length);
return fullImage;
Unsafe.CopyBlockUnaligned(pBuffer, bmpData.Scan0.ToPointer(), bytes);
}
public static unsafe byte[] GetRawBytes(Bitmap bmp)
bmp.UnlockBits(bmpData);
return buffer;
}
private static void WriteIntToByteArray(byte[] data, int startIndex, int bytes, bool littleEndian, uint value)
{
var lastByte = bytes - 1;
if (data.Length < startIndex + bytes)
{
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
var bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);
var bytes = (uint)(Math.Abs(bmpData.Stride) * bmp.Height);
var buffer = new byte[bytes];
fixed (byte* pBuffer = buffer)
{
Unsafe.CopyBlockUnaligned(pBuffer, bmpData.Scan0.ToPointer(), bytes);
}
bmp.UnlockBits(bmpData);
return buffer;
throw new ArgumentOutOfRangeException(nameof(startIndex), "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + ".");
}
private static void WriteIntToByteArray(byte[] data, int startIndex, int bytes, bool littleEndian, uint value)
for (var index = 0; index < bytes; index++)
{
var lastByte = bytes - 1;
if (data.Length < startIndex + bytes)
{
throw new ArgumentOutOfRangeException(nameof(startIndex), "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + ".");
}
for (var index = 0; index < bytes; index++)
{
var offs = startIndex + (littleEndian ? index : lastByte - index);
data[offs] = (byte)(value >> 8 * index & 0xFF);
}
}
private static string GenerateHTMLFragment(string html)
{
var sb = new StringBuilder();
const string header = "Version:0.9\r\nStartHTML:<<<<<<<<<1\r\nEndHTML:<<<<<<<<<2\r\nStartFragment:<<<<<<<<<3\r\nEndFragment:<<<<<<<<<4\r\n";
const string startHTML = "<html>\r\n<body>\r\n";
const string startFragment = "<!--StartFragment-->";
const string endFragment = "<!--EndFragment-->";
const string endHTML = "\r\n</body>\r\n</html>";
sb.Append(header);
var startHTMLLength = header.Length;
var startFragmentLength = startHTMLLength + startHTML.Length + startFragment.Length;
var endFragmentLength = startFragmentLength + Encoding.UTF8.GetByteCount(html);
var endHTMLLength = endFragmentLength + endFragment.Length + endHTML.Length;
sb.Replace("<<<<<<<<<1", startHTMLLength.ToString("D10"));
sb.Replace("<<<<<<<<<2", endHTMLLength.ToString("D10"));
sb.Replace("<<<<<<<<<3", startFragmentLength.ToString("D10"));
sb.Replace("<<<<<<<<<4", endFragmentLength.ToString("D10"));
sb.Append(startHTML);
sb.Append(startFragment);
sb.Append(html);
sb.Append(endFragment);
sb.Append(endHTML);
return sb.ToString();
var offs = startIndex + (littleEndian ? index : lastByte - index);
data[offs] = (byte) (value >> 8 * index & 0xFF);
}
}
private static string GenerateHTMLFragment(string html)
{
var sb = new StringBuilder();
const string header = "Version:0.9\r\nStartHTML:<<<<<<<<<1\r\nEndHTML:<<<<<<<<<2\r\nStartFragment:<<<<<<<<<3\r\nEndFragment:<<<<<<<<<4\r\n";
const string startHTML = "<html>\r\n<body>\r\n";
const string startFragment = "<!--StartFragment-->";
const string endFragment = "<!--EndFragment-->";
const string endHTML = "\r\n</body>\r\n</html>";
sb.Append(header);
var startHTMLLength = header.Length;
var startFragmentLength = startHTMLLength + startHTML.Length + startFragment.Length;
var endFragmentLength = startFragmentLength + Encoding.UTF8.GetByteCount(html);
var endHTMLLength = endFragmentLength + endFragment.Length + endHTML.Length;
sb.Replace("<<<<<<<<<1", startHTMLLength.ToString("D10"));
sb.Replace("<<<<<<<<<2", endHTMLLength.ToString("D10"));
sb.Replace("<<<<<<<<<3", startFragmentLength.ToString("D10"));
sb.Replace("<<<<<<<<<4", endFragmentLength.ToString("D10"));
sb.Append(startHTML);
sb.Append(startFragment);
sb.Append(html);
sb.Append(endFragment);
sb.Append(endHTML);
return sb.ToString();
}
}

View File

@ -2,36 +2,35 @@
using System.Linq;
using System.Runtime.CompilerServices;
namespace FModel.Extensions
namespace FModel.Extensions;
public static class CollectionExtensions
{
public static class CollectionExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Next<T>(this IList<T> collection, T value)
{
[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];
}
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 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, 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];
}
[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

@ -1,84 +1,46 @@
using System;
using System.ComponentModel;
using System.Resources;
using System.Runtime.CompilerServices;
using FModel.Properties;
namespace FModel.Extensions
namespace FModel.Extensions;
public static class EnumExtensions
{
public static class EnumExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetDescription(this Enum value)
{
[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) where T : struct
{
if (!Enum.TryParse(value, true, out T ret))
return defaultValue;
return 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);
}
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 T ToEnum<T>(this string value, T defaultValue) where T : struct => !Enum.TryParse(value, true, out T ret) ? defaultValue : 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

@ -2,30 +2,29 @@
using System.IO;
using System.Runtime.CompilerServices;
namespace FModel.Extensions
namespace FModel.Extensions;
public enum Endianness
{
public enum Endianness
{
LittleEndian,
BigEndian,
}
LittleEndian,
BigEndian
}
public static class StreamExtensions
public static class StreamExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadUInt32(this Stream s, Endianness endian = Endianness.LittleEndian)
{
[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
{
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")
};
}
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

@ -2,133 +2,132 @@
using System.IO;
using System.Runtime.CompilerServices;
namespace FModel.Extensions
namespace FModel.Extensions;
public static class StringExtensions
{
public static class StringExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetReadableSize(double size)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetReadableSize(double size)
if (size == 0) return "0 B";
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
var order = 0;
while (size >= 1024 && order < sizes.Length - 1)
{
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 $"{size:# ###.##} {sizes[order]}".TrimStart();
order++;
size /= 1024;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBefore(this string s, char delimiter)
return $"{size:# ###.##} {sizes[order]}".TrimStart();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBefore(this string s, char delimiter)
{
var index = s.IndexOf(delimiter);
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[..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[..index];
}
[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[..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)
{
var index = s.LastIndexOf(delimiter, comparisonType);
return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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)
{
var index = s.IndexOf(delimiter);
return index == -1 ? s : s[..index];
lineNum++;
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
return lineNum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBefore(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
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)
{
var index = s.IndexOf(delimiter, comparisonType);
return index == -1 ? s : s[..index];
lineNum++;
if (line.Equals(" {"))
index--;
if (index == -1)
return lineNum + 1;
}
[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[..index];
}
[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[..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)
{
var index = s.LastIndexOf(delimiter, comparisonType);
return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
return 1;
}
}

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationIcon>FModel.ico</ApplicationIcon>
<Version>4.2.2</Version>

View File

@ -2,32 +2,31 @@
using System.Threading;
using System.Threading.Tasks.Dataflow;
namespace FModel.Framework
namespace FModel.Framework;
public class AsyncQueue<T> : IAsyncEnumerable<T>
{
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)
{
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
{
await _semaphore.WaitAsync(token);
try
while (Count > 0)
{
while (Count > 0)
{
token.ThrowIfCancellationRequested();
yield return await _buffer.ReceiveAsync(token);
}
}
finally
{
_semaphore.Release();
token.ThrowIfCancellationRequested();
yield return await _buffer.ReceiveAsync(token);
}
}
finally
{
_semaphore.Release();
}
}
}
}

View File

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

View File

@ -4,93 +4,87 @@ using SkiaSharp;
using SkiaSharp.HarfBuzz;
using Buffer = HarfBuzzSharp.Buffer;
namespace FModel.Framework
namespace FModel.Framework;
public class CustomSKShaper : SKShaper
{
public class CustomSKShaper : SKShaper
private const int _FONT_SIZE_SCALE = 512;
private readonly Font _font;
public CustomSKShaper(SKTypeface typeface) : base(typeface)
{
private const int _FONT_SIZE_SCALE = 512;
private readonly Font _font;
private readonly Buffer _buffer;
using var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob();
using var face = new Face(blob, index);
face.Index = index;
face.UnitsPerEm = Typeface.UnitsPerEm;
public CustomSKShaper(SKTypeface typeface) : base(typeface)
_font = new Font(face);
_font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE);
_font.SetFunctionsOpenType();
}
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++)
{
using (var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob())
using (var face = new Face(blob, index))
{
face.Index = index;
face.UnitsPerEm = Typeface.UnitsPerEm;
// move the cursor
xOffset += pos[i].XAdvance * textSizeX;
yOffset += pos[i].YAdvance * textSizeY;
_font = new Font(face);
_font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE);
_font.SetFunctionsOpenType();
}
_buffer = new Buffer();
codepoints[i] = info[i].Codepoint;
clusters[i] = info[i].Cluster;
points[i] = new SKPoint(xOffset + pos[i].XOffset * textSizeX, yOffset - pos[i].YOffset * textSizeY);
}
public new Result Shape(Buffer buffer, float xOffset, float yOffset, SKPaint paint)
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)
{
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);
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.");
}
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);
}
buffer.GuessSegmentProperties();
return Shape(buffer, xOffset, yOffset, paint);
}
}

View File

@ -4,114 +4,113 @@ using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace FModel.Framework
namespace FModel.Framework;
public class FullyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
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()
{
/// <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)
{
}
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)
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
base.ClearItems();
}
private void ObserveAll()
if (e.Action is NotifyCollectionChangedAction.Add or
NotifyCollectionChangedAction.Replace)
{
foreach (var item in Items)
foreach (T item in e.NewItems)
item.PropertyChanged += ChildPropertyChanged;
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var typedSender = (T) sender;
var i = Items.IndexOf(typedSender);
base.OnCollectionChanged(e);
}
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
OnItemPropertyChanged(i, 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 (var 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>
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
/// <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)
{
/// <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

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

View File

@ -4,40 +4,39 @@ using Newtonsoft.Json.Serialization;
using RestSharp;
using RestSharp.Serialization;
namespace FModel.Framework
namespace FModel.Framework;
public class JsonNetSerializer : IRestSerializer
{
public class JsonNetSerializer : IRestSerializer
public static readonly JsonSerializerSettings SerializerSettings = new()
{
public static readonly JsonSerializerSettings SerializerSettings = new()
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
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;
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

@ -1,34 +1,46 @@
using System.Collections.Generic;
namespace FModel.Framework
namespace FModel.Framework;
public class NavigationList<T> : List<T>
{
public class NavigationList<T> : List<T>
private int _currentIndex;
public int CurrentIndex
{
private int _currentIndex = 0;
public int CurrentIndex
get
{
get
if (_currentIndex > Count - 1)
{
if (_currentIndex > Count - 1) { _currentIndex = Count - 1; }
if (_currentIndex < 0) { _currentIndex = 0; }
return _currentIndex;
_currentIndex = Count - 1;
}
set { _currentIndex = value; }
}
public T MoveNext
{
get { _currentIndex++; return this[CurrentIndex]; }
}
if (_currentIndex < 0)
{
_currentIndex = 0;
}
public T MovePrevious
{
get { _currentIndex--; return this[CurrentIndex]; }
return _currentIndex;
}
set => _currentIndex = value;
}
public T Current
public T MoveNext
{
get
{
get { return this[CurrentIndex]; }
_currentIndex++;
return this[CurrentIndex];
}
}
}
public T MovePrevious
{
get
{
_currentIndex--;
return this[CurrentIndex];
}
}
public T Current => this[CurrentIndex];
}

View File

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

View File

@ -6,71 +6,70 @@ using System.Linq;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
namespace FModel.Framework
namespace FModel.Framework;
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErrorInfo
{
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErrorInfo
private readonly Dictionary<string, IList<string>> _validationErrors = new();
public string this[string propertyName]
{
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)
get
{
if (string.IsNullOrEmpty(propertyName))
return _validationErrors.SelectMany(kvp => kvp.Value);
return Error;
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;
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

@ -1,50 +1,49 @@
using System;
namespace FModel.Framework
namespace FModel.Framework;
public abstract class ViewModelCommand<TContextViewModel> : Command where TContextViewModel : ViewModel
{
public abstract class ViewModelCommand<TContextViewModel> : Command where TContextViewModel : ViewModel
private WeakReference _parent;
public TContextViewModel ContextViewModel
{
private WeakReference _parent;
public TContextViewModel ContextViewModel
get
{
get
{
if (_parent is {IsAlive: true})
return (TContextViewModel) _parent.Target;
if (_parent is { IsAlive: true })
return (TContextViewModel) _parent.Target;
return null;
}
private set
{
if (ContextViewModel == value)
return;
_parent = value != null ? new WeakReference(value) : null;
}
return null;
}
protected ViewModelCommand(TContextViewModel contextViewModel)
private set
{
ContextViewModel = contextViewModel /*?? throw new ArgumentNullException(nameof(contextViewModel))*/;
}
if (ContextViewModel == value)
return;
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;
_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

@ -3,86 +3,85 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
namespace FModel
namespace FModel;
public static class Helper
{
public static class Helper
[StructLayout(LayoutKind.Explicit)]
private struct NanUnion
{
[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))
{
[FieldOffset(0)]
internal double DoubleValue;
[FieldOffset(0)]
internal readonly ulong UlongValue;
action();
}
public static void OpenWindow<T>(string windowName, Action action) where T : Window
else
{
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;
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;
}
}

File diff suppressed because one or more lines are too long

View File

@ -10,144 +10,143 @@ using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
using Serilog;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class AesManagerViewModel : ViewModel
{
public class AesManagerViewModel : ViewModel
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
public FullyObservableCollection<FileItem> AesKeys { get; private set; } // holds all aes keys even the main one
public ICollectionView AesKeysView { get; private set; } // holds all aes key ordered by name for the ui
public bool HasChange { get; set; }
private AesResponse _keysFromSettings;
private HashSet<FGuid> _uniqueGuids;
private readonly CUE4ParseViewModel _cue4Parse;
private readonly FileItem _mainKey = new("Main Static Key", 0) { Guid = Constants.ZERO_GUID }; // just so main key gets refreshed in the ui
public AesManagerViewModel(CUE4ParseViewModel cue4Parse)
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
_cue4Parse = cue4Parse;
HasChange = false;
}
public FullyObservableCollection<FileItem> AesKeys { get; private set; } // holds all aes keys even the main one
public ICollectionView AesKeysView { get; private set; } // holds all aes key ordered by name for the ui
public bool HasChange { get; set; }
private AesResponse _keysFromSettings;
private HashSet<FGuid> _uniqueGuids;
private readonly CUE4ParseViewModel _cue4Parse;
private readonly FileItem _mainKey = new("Main Static Key", 0) {Guid = Constants.ZERO_GUID}; // just so main key gets refreshed in the ui
public AesManagerViewModel(CUE4ParseViewModel cue4Parse)
public async Task InitAes()
{
await _threadWorkerView.Begin(_ =>
{
_cue4Parse = cue4Parse;
HasChange = false;
}
public async Task InitAes()
{
await _threadWorkerView.Begin(_ =>
if (_cue4Parse.Game == FGame.Unknown &&
UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings))
{
if (_cue4Parse.Game == FGame.Unknown &&
UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings))
{
_keysFromSettings = settings.AesKeys;
}
else
{
_keysFromSettings = UserSettings.Default.AesKeys[_cue4Parse.Game];
}
_keysFromSettings ??= new AesResponse
{
MainKey = string.Empty,
DynamicKeys = null
};
_mainKey.Key = FixKey(_keysFromSettings.MainKey);
AesKeys = new FullyObservableCollection<FileItem>(EnumerateAesKeys());
AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged;
AesKeysView = new ListCollectionView(AesKeys) {SortDescriptions = {new SortDescription("Name", ListSortDirection.Ascending)}};
});
}
private void AesKeysOnItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
{
if (e.PropertyName != "Key" || sender is not FullyObservableCollection<FileItem> collection)
return;
var key = FixKey(collection[e.CollectionIndex].Key);
if (e.CollectionIndex == 0)
{
if (!HasChange)
HasChange = FixKey(_keysFromSettings.MainKey) != key;
_keysFromSettings.MainKey = key;
}
else if (!_keysFromSettings.HasDynamicKeys)
{
HasChange = true;
_keysFromSettings.DynamicKeys = new List<DynamicKey>
{
new()
{
Key = key,
FileName = collection[e.CollectionIndex].Name,
Guid = collection[e.CollectionIndex].Guid.ToString()
}
};
}
else if (_keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == collection[e.CollectionIndex].Guid.ToString()) is { } d)
{
if (!HasChange)
HasChange = FixKey(d.Key) != key;
d.Key = key;
_keysFromSettings = settings.AesKeys;
}
else
{
HasChange = true;
_keysFromSettings.DynamicKeys.Add(new DynamicKey
_keysFromSettings = UserSettings.Default.AesKeys[_cue4Parse.Game];
}
_keysFromSettings ??= new AesResponse
{
MainKey = string.Empty,
DynamicKeys = null
};
_mainKey.Key = FixKey(_keysFromSettings.MainKey);
AesKeys = new FullyObservableCollection<FileItem>(EnumerateAesKeys());
AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged;
AesKeysView = new ListCollectionView(AesKeys) { SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending) } };
});
}
private void AesKeysOnItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
{
if (e.PropertyName != "Key" || sender is not FullyObservableCollection<FileItem> collection)
return;
var key = FixKey(collection[e.CollectionIndex].Key);
if (e.CollectionIndex == 0)
{
if (!HasChange)
HasChange = FixKey(_keysFromSettings.MainKey) != key;
_keysFromSettings.MainKey = key;
}
else if (!_keysFromSettings.HasDynamicKeys)
{
HasChange = true;
_keysFromSettings.DynamicKeys = new List<DynamicKey>
{
new()
{
Key = key,
FileName = collection[e.CollectionIndex].Name,
Guid = collection[e.CollectionIndex].Guid.ToString()
});
}
}
public async Task UpdateProvider(bool isLaunch)
{
if (!isLaunch && !HasChange) return;
_cue4Parse.ClearProvider();
await _cue4Parse.LoadVfs(AesKeys);
if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings;
else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings;
Log.Information("{@Json}", UserSettings.Default);
}
private string FixKey(string key)
{
if (string.IsNullOrEmpty(key))
return string.Empty;
if (key.StartsWith("0x"))
key = key[2..];
return "0x" + key.ToUpper().Trim();
}
private IEnumerable<FileItem> EnumerateAesKeys()
{
yield return _mainKey;
_uniqueGuids = new HashSet<FGuid> {Constants.ZERO_GUID};
var hasDynamicKeys = _keysFromSettings.HasDynamicKeys;
foreach (var file in _cue4Parse.GameDirectory.DirectoryFiles)
{
if (file.Guid == Constants.ZERO_GUID || !_uniqueGuids.Add(file.Guid))
continue;
var k = string.Empty;
if (hasDynamicKeys && _keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == file.Guid.ToString()) is { } dynamicKey)
{
k = dynamicKey.Key;
}
};
}
else if (_keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == collection[e.CollectionIndex].Guid.ToString()) is { } d)
{
if (!HasChange)
HasChange = FixKey(d.Key) != key;
file.Key = FixKey(k);
yield return file;
}
d.Key = key;
}
else
{
HasChange = true;
_keysFromSettings.DynamicKeys.Add(new DynamicKey
{
Key = key,
FileName = collection[e.CollectionIndex].Name,
Guid = collection[e.CollectionIndex].Guid.ToString()
});
}
}
}
public async Task UpdateProvider(bool isLaunch)
{
if (!isLaunch && !HasChange) return;
_cue4Parse.ClearProvider();
await _cue4Parse.LoadVfs(AesKeys);
if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings;
else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings;
Log.Information("{@Json}", UserSettings.Default);
}
private string FixKey(string key)
{
if (string.IsNullOrEmpty(key))
return string.Empty;
if (key.StartsWith("0x"))
key = key[2..];
return "0x" + key.ToUpper().Trim();
}
private IEnumerable<FileItem> EnumerateAesKeys()
{
yield return _mainKey;
_uniqueGuids = new HashSet<FGuid> { Constants.ZERO_GUID };
var hasDynamicKeys = _keysFromSettings.HasDynamicKeys;
foreach (var file in _cue4Parse.GameDirectory.DirectoryFiles)
{
if (file.Guid == Constants.ZERO_GUID || !_uniqueGuids.Add(file.Guid))
continue;
var k = string.Empty;
if (hasDynamicKeys && _keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == file.Guid.ToString()) is { } dynamicKey)
{
k = dynamicKey.Key;
}
file.Key = FixKey(k);
yield return file;
}
}
}

View File

@ -2,29 +2,28 @@
using FModel.ViewModels.ApiEndpoints;
using RestSharp;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class ApiEndpointViewModel
{
public class ApiEndpointViewModel
private readonly IRestClient _client = new RestClient
{
private readonly IRestClient _client = new RestClient
{
UserAgent = $"FModel/{Constants.APP_VERSION}",
Timeout = 3 * 1000
}.UseSerializer<JsonNetSerializer>();
UserAgent = $"FModel/{Constants.APP_VERSION}",
Timeout = 3 * 1000
}.UseSerializer<JsonNetSerializer>();
public FortniteApiEndpoint FortniteApi { get; }
public ValorantApiEndpoint ValorantApi { get; }
public BenbotApiEndpoint BenbotApi { get; }
public EpicApiEndpoint EpicApi { get; }
public FModelApi FModelApi { get; }
public FortniteApiEndpoint FortniteApi { get; }
public ValorantApiEndpoint ValorantApi { get; }
public BenbotApiEndpoint BenbotApi { get; }
public EpicApiEndpoint EpicApi { get; }
public FModelApi FModelApi { get; }
public ApiEndpointViewModel()
{
FortniteApi = new FortniteApiEndpoint(_client);
ValorantApi = new ValorantApiEndpoint(_client);
BenbotApi = new BenbotApiEndpoint(_client);
EpicApi = new EpicApiEndpoint(_client);
FModelApi = new FModelApi(_client);
}
public ApiEndpointViewModel()
{
FortniteApi = new FortniteApiEndpoint(_client);
ValorantApi = new ValorantApiEndpoint(_client);
BenbotApi = new BenbotApiEndpoint(_client);
EpicApi = new EpicApiEndpoint(_client);
FModelApi = new FModelApi(_client);
}
}

View File

@ -1,14 +1,13 @@
using RestSharp;
namespace FModel.ViewModels.ApiEndpoints
{
public abstract class AbstractApiProvider
{
protected readonly IRestClient _client;
namespace FModel.ViewModels.ApiEndpoints;
public AbstractApiProvider(IRestClient client)
{
_client = client;
}
public abstract class AbstractApiProvider
{
protected readonly IRestClient _client;
protected AbstractApiProvider(IRestClient client)
{
_client = client;
}
}

View File

@ -6,73 +6,72 @@ using FModel.ViewModels.ApiEndpoints.Models;
using RestSharp;
using Serilog;
namespace FModel.ViewModels.ApiEndpoints
namespace FModel.ViewModels.ApiEndpoints;
public class BenbotApiEndpoint : AbstractApiProvider
{
public class BenbotApiEndpoint : AbstractApiProvider
public BenbotApiEndpoint(IRestClient client) : base(client)
{
public BenbotApiEndpoint(IRestClient client) : base(client)
{
}
}
public async Task<AesResponse> GetAesKeysAsync(CancellationToken token)
public async Task<AesResponse> GetAesKeysAsync(CancellationToken token)
{
var request = new RestRequest("https://benbot.app/api/v2/aes", Method.GET)
{
var request = new RestRequest("https://benbot.app/api/v2/aes", Method.GET)
{
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
var response = await _client.ExecuteAsync<AesResponse>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
var response = await _client.ExecuteAsync<AesResponse>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public AesResponse GetAesKeys(CancellationToken token)
{
return GetAesKeysAsync(token).GetAwaiter().GetResult();
}
public AesResponse GetAesKeys(CancellationToken token)
{
return GetAesKeysAsync(token).GetAwaiter().GetResult();
}
public async Task<MappingsResponse[]> GetMappingsAsync(CancellationToken token)
public async Task<MappingsResponse[]> GetMappingsAsync(CancellationToken token)
{
var request = new RestRequest("https://benbot.app/api/v1/mappings", Method.GET)
{
var request = new RestRequest("https://benbot.app/api/v1/mappings", Method.GET)
{
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
var response = await _client.ExecuteAsync<MappingsResponse[]>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
var response = await _client.ExecuteAsync<MappingsResponse[]>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public MappingsResponse[] GetMappings(CancellationToken token)
{
return GetMappingsAsync(token).GetAwaiter().GetResult();
}
public MappingsResponse[] GetMappings(CancellationToken token)
{
return GetMappingsAsync(token).GetAwaiter().GetResult();
}
public async Task<Dictionary<string, Dictionary<string, string>>> GetHotfixesAsync(CancellationToken token, string language = "en-US")
public async Task<Dictionary<string, Dictionary<string, string>>> GetHotfixesAsync(CancellationToken token, string language = "en-US")
{
var request = new RestRequest("https://benbot.app/api/v1/hotfixes", Method.GET)
{
var request = new RestRequest("https://benbot.app/api/v1/hotfixes", Method.GET)
{
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
request.AddParameter("lang", language);
var response = await _client.ExecuteAsync<Dictionary<string, Dictionary<string, string>>>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int)response.StatusCode, request.Resource);
return response.Data;
}
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
request.AddParameter("lang", language);
var response = await _client.ExecuteAsync<Dictionary<string, Dictionary<string, string>>>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public Dictionary<string, Dictionary<string, string>> GetHotfixes(CancellationToken token, string language = "en-US")
{
return GetHotfixesAsync(token, language).GetAwaiter().GetResult();
}
public Dictionary<string, Dictionary<string, string>> GetHotfixes(CancellationToken token, string language = "en-US")
{
return GetHotfixesAsync(token, language).GetAwaiter().GetResult();
}
public async Task DownloadFileAsync(string fileLink, string installationPath)
{
var request = new RestRequest(fileLink, Method.GET);
var data = _client.DownloadData(request);
await File.WriteAllBytesAsync(installationPath, data);
}
public async Task DownloadFileAsync(string fileLink, string installationPath)
{
var request = new RestRequest(fileLink, Method.GET);
var data = _client.DownloadData(request);
await File.WriteAllBytesAsync(installationPath, data);
}
public void DownloadFile(string fileLink, string installationPath)
{
DownloadFileAsync(fileLink, installationPath).GetAwaiter().GetResult();
}
public void DownloadFile(string fileLink, string installationPath)
{
DownloadFileAsync(fileLink, installationPath).GetAwaiter().GetResult();
}
}

View File

@ -7,55 +7,54 @@ using FModel.ViewModels.ApiEndpoints.Models;
using RestSharp;
using Serilog;
namespace FModel.ViewModels.ApiEndpoints
namespace FModel.ViewModels.ApiEndpoints;
public class EpicApiEndpoint : AbstractApiProvider
{
public class EpicApiEndpoint : AbstractApiProvider
private const string _OAUTH_URL = "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token";
private const string _BASIC_TOKEN = "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE=";
private const string _LAUNCHER_ASSETS = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live";
public EpicApiEndpoint(IRestClient client) : base(client)
{
private const string _OAUTH_URL = "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token";
private const string _BASIC_TOKEN = "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE=";
private const string _LAUNCHER_ASSETS = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live";
}
public EpicApiEndpoint(IRestClient client) : base(client)
public async Task<ManifestInfo> GetManifestAsync(CancellationToken token)
{
if (IsExpired())
{
}
public async Task<ManifestInfo> GetManifestAsync(CancellationToken token)
{
if (IsExpired())
var auth = await GetAuthAsync(token);
if (auth != null)
{
var auth = await GetAuthAsync(token);
if (auth != null)
{
UserSettings.Default.LastAuthResponse = auth;
}
UserSettings.Default.LastAuthResponse = auth;
}
var request = new RestRequest(_LAUNCHER_ASSETS, Method.GET);
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return new ManifestInfo(response.Content);
}
public ManifestInfo GetManifest(CancellationToken token)
{
return GetManifestAsync(token).GetAwaiter().GetResult();
}
var request = new RestRequest(_LAUNCHER_ASSETS, Method.GET);
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return new ManifestInfo(response.Content);
}
private async Task<AuthResponse> GetAuthAsync(CancellationToken token)
{
var request = new RestRequest(_OAUTH_URL, Method.POST);
request.AddHeader("Authorization", _BASIC_TOKEN);
request.AddParameter("grant_type", "client_credentials");
var response = await _client.ExecuteAsync<AuthResponse>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public ManifestInfo GetManifest(CancellationToken token)
{
return GetManifestAsync(token).GetAwaiter().GetResult();
}
private bool IsExpired()
{
if (string.IsNullOrEmpty(UserSettings.Default.LastAuthResponse.AccessToken)) return true;
return DateTime.Now.Subtract(TimeSpan.FromHours(1)) >= UserSettings.Default.LastAuthResponse.ExpiresAt;
}
private async Task<AuthResponse> GetAuthAsync(CancellationToken token)
{
var request = new RestRequest(_OAUTH_URL, Method.POST);
request.AddHeader("Authorization", _BASIC_TOKEN);
request.AddParameter("grant_type", "client_credentials");
var response = await _client.ExecuteAsync<AuthResponse>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
private bool IsExpired()
{
if (string.IsNullOrEmpty(UserSettings.Default.LastAuthResponse.AccessToken)) return true;
return DateTime.Now.Subtract(TimeSpan.FromHours(1)) >= UserSettings.Default.LastAuthResponse.ExpiresAt;
}
}

View File

@ -17,169 +17,168 @@ using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
namespace FModel.ViewModels.ApiEndpoints
namespace FModel.ViewModels.ApiEndpoints;
public class FModelApi : AbstractApiProvider
{
public class FModelApi : AbstractApiProvider
private News _news;
private Info _infos;
private Backup[] _backups;
private Game _game;
private readonly IDictionary<string, CommunityDesign> _communityDesigns = new Dictionary<string, CommunityDesign>();
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public FModelApi(IRestClient client) : base(client)
{
private News _news;
private Info _infos;
private Backup[] _backups;
private Game _game;
private readonly IDictionary<string, CommunityDesign> _communityDesigns = new Dictionary<string, CommunityDesign>();
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
}
public FModelApi(IRestClient client) : base(client)
{
}
public async Task<News> GetNewsAsync(CancellationToken token)
{
var request = new RestRequest($"https://api.fmodel.app/v1/news/{Constants.APP_VERSION}", Method.GET);
var response = await _client.ExecuteAsync<News>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public async Task<News> GetNewsAsync(CancellationToken token)
{
var request = new RestRequest($"https://api.fmodel.app/v1/news/{Constants.APP_VERSION}", Method.GET);
var response = await _client.ExecuteAsync<News>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public News GetNews(CancellationToken token)
{
return _news ??= GetNewsAsync(token).GetAwaiter().GetResult();
}
public News GetNews(CancellationToken token)
{
return _news ??= GetNewsAsync(token).GetAwaiter().GetResult();
}
public async Task<Info> GetInfosAsync(CancellationToken token, EUpdateMode updateMode)
{
var request = new RestRequest($"https://api.fmodel.app/v1/infos/{updateMode}", Method.GET);
var response = await _client.ExecuteAsync<Info>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public async Task<Info> GetInfosAsync(CancellationToken token, EUpdateMode updateMode)
{
var request = new RestRequest($"https://api.fmodel.app/v1/infos/{updateMode}", Method.GET);
var response = await _client.ExecuteAsync<Info>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public Info GetInfos(CancellationToken token, EUpdateMode updateMode)
{
return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult();
}
public Info GetInfos(CancellationToken token, EUpdateMode updateMode)
{
return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult();
}
public async Task<Backup[]> GetBackupsAsync(CancellationToken token, string gameName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/backups/{gameName}", Method.GET);
var response = await _client.ExecuteAsync<Backup[]>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public async Task<Backup[]> GetBackupsAsync(CancellationToken token, string gameName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/backups/{gameName}", Method.GET);
var response = await _client.ExecuteAsync<Backup[]>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public Backup[] GetBackups(CancellationToken token, string gameName)
{
return _backups ??= GetBackupsAsync(token, gameName).GetAwaiter().GetResult();
}
public Backup[] GetBackups(CancellationToken token, string gameName)
{
return _backups ??= GetBackupsAsync(token, gameName).GetAwaiter().GetResult();
}
public async Task<Game> GetGamesAsync(CancellationToken token, string gameName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/games/{gameName}", Method.GET);
var response = await _client.ExecuteAsync<Game>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public async Task<Game> GetGamesAsync(CancellationToken token, string gameName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/games/{gameName}", Method.GET);
var response = await _client.ExecuteAsync<Game>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public Game GetGames(CancellationToken token, string gameName)
{
return _game ??= GetGamesAsync(token, gameName).GetAwaiter().GetResult();
}
public Game GetGames(CancellationToken token, string gameName)
{
return _game ??= GetGamesAsync(token, gameName).GetAwaiter().GetResult();
}
public async Task<CommunityDesign> GetDesignAsync(string designName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/designs/{designName}", Method.GET);
var response = await _client.ExecuteAsync<Community>(request).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data != null ? new CommunityDesign(response.Data) : null;
}
public async Task<CommunityDesign> GetDesignAsync(string designName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/designs/{designName}", Method.GET);
var response = await _client.ExecuteAsync<Community>(request).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data != null ? new CommunityDesign(response.Data) : null;
}
public CommunityDesign GetDesign(string designName)
{
if (_communityDesigns.TryGetValue(designName, out var communityDesign) && communityDesign != null)
return communityDesign;
communityDesign = GetDesignAsync(designName).GetAwaiter().GetResult();
_communityDesigns[designName] = communityDesign;
public CommunityDesign GetDesign(string designName)
{
if (_communityDesigns.TryGetValue(designName, out var communityDesign) && communityDesign != null)
return communityDesign;
}
public void CheckForUpdates(EUpdateMode updateMode)
{
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}");
}
communityDesign = GetDesignAsync(designName).GetAwaiter().GetResult();
_communityDesigns[designName] = communityDesign;
return communityDesign;
}
private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
public void CheckForUpdates(EUpdateMode updateMode)
{
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}");
}
private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
{
_infos = JsonConvert.DeserializeObject<Info>(args.RemoteData);
if (_infos != null)
{
_infos = JsonConvert.DeserializeObject<Info>(args.RemoteData);
if (_infos != null)
args.UpdateInfo = new UpdateInfoEventArgs
{
args.UpdateInfo = new UpdateInfoEventArgs
{
CurrentVersion = _infos.Version,
ChangelogURL = _infos.ChangelogUrl,
DownloadURL = _infos.DownloadUrl
};
}
}
private void CheckForUpdateEvent(UpdateInfoEventArgs args)
{
if (args is {CurrentVersion: { }})
{
var currentVersion = new System.Version(args.CurrentVersion);
if (currentVersion == args.InstalledVersion)
{
if (UserSettings.Default.ShowChangelog)
ShowChangelog(args);
return;
}
var downgrade = currentVersion < args.InstalledVersion;
var messageBox = new MessageBoxModel
{
Text = $"The latest version of FModel {UserSettings.Default.UpdateMode} is {args.CurrentVersion}. You are using version {args.InstalledVersion}. Do you want to {(downgrade ? "downgrade" : "update")} the application now?",
Caption = $"{(downgrade ? "Downgrade" : "Update")} Available",
Icon = MessageBoxImage.Question,
Buttons = MessageBoxButtons.YesNo(),
IsSoundEnabled = false
};
MessageBox.Show(messageBox);
if (messageBox.Result != MessageBoxResult.Yes) return;
try
{
if (AutoUpdater.DownloadUpdate(args))
{
UserSettings.Default.ShowChangelog = true;
Application.Current.Shutdown();
}
}
catch (Exception exception)
{
UserSettings.Default.ShowChangelog = false;
MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
else
{
MessageBox.Show(
"There is a problem reaching the update server, please check your internet connection or try again later.",
"Update Check Failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ShowChangelog(UpdateInfoEventArgs args)
{
var request = new RestRequest(args.ChangelogURL, Method.GET);
var response = _client.Execute(request);
if (string.IsNullOrEmpty(response.Content)) return;
_applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}");
_applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog");
_applicationView.CUE4Parse.TabControl.SelectedTab.SetDocumentText(response.Content, false);
UserSettings.Default.ShowChangelog = false;
CurrentVersion = _infos.Version,
ChangelogURL = _infos.ChangelogUrl,
DownloadURL = _infos.DownloadUrl
};
}
}
}
private void CheckForUpdateEvent(UpdateInfoEventArgs args)
{
if (args is { CurrentVersion: { } })
{
var currentVersion = new System.Version(args.CurrentVersion);
if (currentVersion == args.InstalledVersion)
{
if (UserSettings.Default.ShowChangelog)
ShowChangelog(args);
return;
}
var downgrade = currentVersion < args.InstalledVersion;
var messageBox = new MessageBoxModel
{
Text = $"The latest version of FModel {UserSettings.Default.UpdateMode} is {args.CurrentVersion}. You are using version {args.InstalledVersion}. Do you want to {(downgrade ? "downgrade" : "update")} the application now?",
Caption = $"{(downgrade ? "Downgrade" : "Update")} Available",
Icon = MessageBoxImage.Question,
Buttons = MessageBoxButtons.YesNo(),
IsSoundEnabled = false
};
MessageBox.Show(messageBox);
if (messageBox.Result != MessageBoxResult.Yes) return;
try
{
if (AutoUpdater.DownloadUpdate(args))
{
UserSettings.Default.ShowChangelog = true;
Application.Current.Shutdown();
}
}
catch (Exception exception)
{
UserSettings.Default.ShowChangelog = false;
MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
else
{
MessageBox.Show(
"There is a problem reaching the update server, please check your internet connection or try again later.",
"Update Check Failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ShowChangelog(UpdateInfoEventArgs args)
{
var request = new RestRequest(args.ChangelogURL, Method.GET);
var response = _client.Execute(request);
if (string.IsNullOrEmpty(response.Content)) return;
_applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}");
_applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog");
_applicationView.CUE4Parse.TabControl.SelectedTab.SetDocumentText(response.Content, false);
UserSettings.Default.ShowChangelog = false;
}
}

View File

@ -3,31 +3,30 @@ using FModel.ViewModels.ApiEndpoints.Models;
using RestSharp;
using System.Threading.Tasks;
namespace FModel.ViewModels.ApiEndpoints
namespace FModel.ViewModels.ApiEndpoints;
public class FortniteApiEndpoint : AbstractApiProvider
{
public class FortniteApiEndpoint : AbstractApiProvider
public FortniteApiEndpoint(IRestClient client) : base(client)
{
public FortniteApiEndpoint(IRestClient client) : base(client)
{
}
}
public async Task<PlaylistResponse> GetPlaylistAsync(string playlistId)
{
var request = new RestRequest($"https://fortnite-api.com/v1/playlists/{playlistId}", Method.GET);
var response = await _client.ExecuteAsync<PlaylistResponse>(request).ConfigureAwait(false);
return response.Data;
}
public async Task<PlaylistResponse> GetPlaylistAsync(string playlistId)
{
var request = new RestRequest($"https://fortnite-api.com/v1/playlists/{playlistId}", Method.GET);
var response = await _client.ExecuteAsync<PlaylistResponse>(request).ConfigureAwait(false);
return response.Data;
}
public PlaylistResponse GetPlaylist(string playlistId)
{
return GetPlaylistAsync(playlistId).GetAwaiter().GetResult();
}
public PlaylistResponse GetPlaylist(string playlistId)
{
return GetPlaylistAsync(playlistId).GetAwaiter().GetResult();
}
public bool TryGetBytes(Uri link, out byte[] data)
{
var request = new RestRequest(link, Method.GET);
data = _client.DownloadData(request);
return data != null;
}
public bool TryGetBytes(Uri link, out byte[] data)
{
var request = new RestRequest(link, Method.GET);
data = _client.DownloadData(request);
return data != null;
}
}

View File

@ -2,23 +2,22 @@
using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;
namespace FModel.ViewModels.ApiEndpoints.Models
namespace FModel.ViewModels.ApiEndpoints.Models;
[DebuggerDisplay("{" + nameof(Version) + "}")]
public class AesResponse
{
[DebuggerDisplay("{" + nameof(Version) + "}")]
public class AesResponse
{
[J("version")] public string Version { get; private set; }
[J("mainKey")] public string MainKey { get; set; }
[J("dynamicKeys")] public List<DynamicKey> DynamicKeys { get; set; }
[J("version")] public string Version { get; private set; }
[J("mainKey")] public string MainKey { get; set; }
[J("dynamicKeys")] public List<DynamicKey> DynamicKeys { get; set; }
public bool HasDynamicKeys => DynamicKeys is {Count: > 0};
}
public bool HasDynamicKeys => DynamicKeys is { Count: > 0 };
}
[DebuggerDisplay("{" + nameof(Key) + "}")]
public class DynamicKey
{
[J("fileName")] public string FileName { get; set; }
[J("guid")] public string Guid { get; set; }
[J("key")] public string Key { get; set; }
}
[DebuggerDisplay("{" + nameof(Key) + "}")]
public class DynamicKey
{
[J("fileName")] public string FileName { get; set; }
[J("guid")] public string Guid { get; set; }
[J("key")] public string Key { get; set; }
}

View File

@ -2,12 +2,11 @@
using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;
namespace FModel.ViewModels.ApiEndpoints.Models
namespace FModel.ViewModels.ApiEndpoints.Models;
[DebuggerDisplay("{" + nameof(AccessToken) + "}")]
public class AuthResponse
{
[DebuggerDisplay("{" + nameof(AccessToken) + "}")]
public class AuthResponse
{
[J("access_token")] public string AccessToken { get; set; }
[J("expires_at")] public DateTime ExpiresAt { get; set; }
}
[J("access_token")] public string AccessToken { get; set; }
[J("expires_at")] public DateTime ExpiresAt { get; set; }
}

View File

@ -6,193 +6,193 @@ using FModel.Extensions;
using SkiaSharp;
using J = Newtonsoft.Json.JsonPropertyAttribute;
namespace FModel.ViewModels.ApiEndpoints.Models
namespace FModel.ViewModels.ApiEndpoints.Models;
[DebuggerDisplay("{" + nameof(Messages) + "}")]
public class News
{
[DebuggerDisplay("{" + nameof(Messages) + "}")]
public class News
{
[J] public string[] Messages { get; private set; }
[J] public string[] Colors { get; private set; }
[J] public string[] NewLines { get; private set; }
}
[J] public string[] Messages { get; private set; }
[J] public string[] Colors { get; private set; }
[J] public string[] NewLines { get; private set; }
}
[DebuggerDisplay("{" + nameof(FileName) + "}")]
public class Backup
{
[J] public string GameName { get; private set; }
[J] public string FileName { get; private set; }
[J] public string DownloadUrl { get; private set; }
[J] public long FileSize { get; private set; }
}
[DebuggerDisplay("{" + nameof(DisplayName) + "}")]
public class Game
{
[J] public string DisplayName { get; private set; }
[J] public Dictionary<string, Version> Versions { get; private set; }
}
[DebuggerDisplay("{" + nameof(GameEnum) + "}")]
public class Version
{
[J("game")] public string GameEnum { get; private set; }
[J] public int UeVer { get; private set; }
[J] public Dictionary<string, int> CustomVersions { get; private set; }
[J] public Dictionary<string, bool> Options { get; private set; }
}
[DebuggerDisplay("{" + nameof(FileName) + "}")]
public class Backup
{
[J] public string GameName { get; private set; }
[J] public string FileName { get; private set; }
[J] public string DownloadUrl { get; private set; }
[J] public long FileSize { get; private set; }
}
[DebuggerDisplay("{" + nameof(Mode) + "}")]
public class Info
{
[J] public string Mode { get; private set; }
[J] public string Version { get; private set; }
[J] public string DownloadUrl { get; private set; }
[J] public string ChangelogUrl { get; private set; }
[J] public string CommunityDesign { get; private set; }
[J] public string CommunityPreview { get; private set; }
}
[DebuggerDisplay("{" + nameof(DisplayName) + "}")]
public class Game
{
[J] public string DisplayName { get; private set; }
[J] public Dictionary<string, Version> Versions { get; private set; }
}
[DebuggerDisplay("{" + nameof(Name) + "}")]
public class Community
{
[J] public string Name { get; private set; }
[J] public bool DrawSource { get; private set; }
[J] public bool DrawSeason { get; private set; }
[J] public bool DrawSeasonShort { get; private set; }
[J] public bool DrawSet { get; private set; }
[J] public bool DrawSetShort { get; private set; }
[J] public IDictionary<string, Font> Fonts { get; private set; }
[J] public GameplayTag GameplayTags { get; private set; }
[J] public IDictionary<string, Rarity> Rarities { get; private set; }
}
[DebuggerDisplay("{" + nameof(GameEnum) + "}")]
public class Version
{
[J("game")] public string GameEnum { get; private set; }
[J] public int UeVer { get; private set; }
[J] public Dictionary<string, int> CustomVersions { get; private set; }
[J] public Dictionary<string, bool> Options { get; private set; }
}
public class Font
{
[J] public IDictionary<string, string> Typeface { get; private set; }
[J] public float FontSize { get; private set; }
[J] public float FontScale { get; private set; }
[J] public string FontColor { get; private set; }
[J] public float SkewValue { get; private set; }
[J] public byte ShadowValue { get; private set; }
[J] public int MaxLineCount { get; private set; }
[J] public string Alignment { get; private set; }
[J] public int X { get; private set; }
[J] public int Y { get; private set; }
}
[DebuggerDisplay("{" + nameof(Mode) + "}")]
public class Info
{
[J] public string Mode { get; private set; }
[J] public string Version { get; private set; }
[J] public string DownloadUrl { get; private set; }
[J] public string ChangelogUrl { get; private set; }
[J] public string CommunityDesign { get; private set; }
[J] public string CommunityPreview { get; private set; }
}
public class FontDesign
{
[J] public IDictionary<ELanguage, string> Typeface { get; set; }
[J] public float FontSize { get; set; }
[J] public float FontScale { get; set; }
[J] public SKColor FontColor { get; set; }
[J] public float SkewValue { get; set; }
[J] public byte ShadowValue { get; set; }
[J] public int MaxLineCount { get; set; }
[J] public SKTextAlign Alignment { get; set; }
[J] public int X { get; set; }
[J] public int Y { get; set; }
}
[DebuggerDisplay("{" + nameof(Name) + "}")]
public class Community
{
[J] public string Name { get; private set; }
[J] public bool DrawSource { get; private set; }
[J] public bool DrawSeason { get; private set; }
[J] public bool DrawSeasonShort { get; private set; }
[J] public bool DrawSet { get; private set; }
[J] public bool DrawSetShort { get; private set; }
[J] public IDictionary<string, Font> Fonts { get; private set; }
[J] public GameplayTag GameplayTags { get; private set; }
[J] public IDictionary<string, Rarity> Rarities { get; private set; }
}
public class GameplayTag
{
[J] public int X { get; private set; }
[J] public int Y { get; private set; }
[J] public bool DrawCustomOnly { get; private set; }
[J] public string Custom { get; private set; }
[J] public IDictionary<string, string> Tags { get; private set; }
}
public class Font
{
[J] public IDictionary<string, string> Typeface { get; private set; }
[J] public float FontSize { get; private set; }
[J] public float FontScale { get; private set; }
[J] public string FontColor { get; private set; }
[J] public float SkewValue { get; private set; }
[J] public byte ShadowValue { get; private set; }
[J] public int MaxLineCount { get; private set; }
[J] public string Alignment { get; private set; }
[J] public int X { get; private set; }
[J] public int Y { get; private set; }
}
public class GameplayTagDesign
{
[J] public int X { get; set; }
[J] public int Y { get; set; }
[J] public bool DrawCustomOnly { get; set; }
[J] public SKBitmap Custom { get; set; }
[J] public IDictionary<string, SKBitmap> Tags { get; set; }
}
public class FontDesign
{
[J] public IDictionary<ELanguage, string> Typeface { get; set; }
[J] public float FontSize { get; set; }
[J] public float FontScale { get; set; }
[J] public SKColor FontColor { get; set; }
[J] public float SkewValue { get; set; }
[J] public byte ShadowValue { get; set; }
[J] public int MaxLineCount { get; set; }
[J] public SKTextAlign Alignment { get; set; }
[J] public int X { get; set; }
[J] public int Y { get; set; }
}
public class Rarity
{
[J] public string Background { get; private set; }
[J] public string Upper { get; private set; }
[J] public string Lower { get; private set; }
}
public class GameplayTag
{
[J] public int X { get; private set; }
[J] public int Y { get; private set; }
[J] public bool DrawCustomOnly { get; private set; }
[J] public string Custom { get; private set; }
[J] public IDictionary<string, string> Tags { get; private set; }
}
public class RarityDesign
{
[J] public SKBitmap Background { get; set; }
[J] public SKBitmap Upper { get; set; }
[J] public SKBitmap Lower { get; set; }
}
public class GameplayTagDesign
{
[J] public int X { get; set; }
[J] public int Y { get; set; }
[J] public bool DrawCustomOnly { get; set; }
[J] public SKBitmap Custom { get; set; }
[J] public IDictionary<string, SKBitmap> Tags { get; set; }
}
public class CommunityDesign
{
public bool DrawSource { get; }
public bool DrawSeason { get; }
public bool DrawSeasonShort { get; }
public bool DrawSet { get; }
public bool DrawSetShort { get; }
public IDictionary<string, FontDesign> Fonts { get; }
public GameplayTagDesign GameplayTags { get; }
public IDictionary<string, RarityDesign> Rarities { get; }
public class Rarity
{
[J] public string Background { get; private set; }
[J] public string Upper { get; private set; }
[J] public string Lower { get; private set; }
}
public CommunityDesign(Community response)
public class RarityDesign
{
[J] public SKBitmap Background { get; set; }
[J] public SKBitmap Upper { get; set; }
[J] public SKBitmap Lower { get; set; }
}
public class CommunityDesign
{
public bool DrawSource { get; }
public bool DrawSeason { get; }
public bool DrawSeasonShort { get; }
public bool DrawSet { get; }
public bool DrawSetShort { get; }
public IDictionary<string, FontDesign> Fonts { get; }
public GameplayTagDesign GameplayTags { get; }
public IDictionary<string, RarityDesign> Rarities { get; }
public CommunityDesign(Community response)
{
DrawSource = response.DrawSource;
DrawSeason = response.DrawSeason;
DrawSeasonShort = response.DrawSeasonShort;
DrawSet = response.DrawSet;
DrawSetShort = response.DrawSetShort;
Fonts = new Dictionary<string, FontDesign>();
foreach (var (k, font) in response.Fonts)
{
DrawSource = response.DrawSource;
DrawSeason = response.DrawSeason;
DrawSeasonShort = response.DrawSeasonShort;
DrawSet = response.DrawSet;
DrawSetShort = response.DrawSetShort;
Fonts = new Dictionary<string, FontDesign>();
foreach (var (k, font) in response.Fonts)
var typeface = new Dictionary<ELanguage, string>();
foreach (var (key, value) in font.Typeface)
{
var typeface = new Dictionary<ELanguage, string>();
foreach (var (key, value) in font.Typeface)
{
typeface[key.ToEnum(ELanguage.English)] = value;
}
Fonts[k] = new FontDesign
{
Typeface = typeface,
FontSize = font.FontSize,
FontScale = font.FontScale,
FontColor = SKColor.Parse(font.FontColor),
SkewValue = font.SkewValue,
ShadowValue = font.ShadowValue,
MaxLineCount = font.MaxLineCount,
Alignment = font.Alignment.ToEnum(SKTextAlign.Center),
X = font.X,
Y = font.Y
};
typeface[key.ToEnum(ELanguage.English)] = value;
}
var tags = new Dictionary<string, SKBitmap>();
foreach (var (key, value) in response.GameplayTags.Tags)
Fonts[k] = new FontDesign
{
tags[key] = Utils.GetB64Bitmap(value);
}
GameplayTags = new GameplayTagDesign
{
X = response.GameplayTags.X,
Y = response.GameplayTags.Y,
DrawCustomOnly = response.GameplayTags.DrawCustomOnly,
Custom = Utils.GetB64Bitmap(response.GameplayTags.Custom),
Tags = tags
Typeface = typeface,
FontSize = font.FontSize,
FontScale = font.FontScale,
FontColor = SKColor.Parse(font.FontColor),
SkewValue = font.SkewValue,
ShadowValue = font.ShadowValue,
MaxLineCount = font.MaxLineCount,
Alignment = font.Alignment.ToEnum(SKTextAlign.Center),
X = font.X,
Y = font.Y
};
}
Rarities = new Dictionary<string, RarityDesign>();
foreach (var (key, value) in response.Rarities)
var tags = new Dictionary<string, SKBitmap>();
foreach (var (key, value) in response.GameplayTags.Tags)
{
tags[key] = Utils.GetB64Bitmap(value);
}
GameplayTags = new GameplayTagDesign
{
X = response.GameplayTags.X,
Y = response.GameplayTags.Y,
DrawCustomOnly = response.GameplayTags.DrawCustomOnly,
Custom = Utils.GetB64Bitmap(response.GameplayTags.Custom),
Tags = tags
};
Rarities = new Dictionary<string, RarityDesign>();
foreach (var (key, value) in response.Rarities)
{
Rarities[key] = new RarityDesign
{
Rarities[key] = new RarityDesign
{
Background = Utils.GetB64Bitmap(value.Background),
Upper = Utils.GetB64Bitmap(value.Upper),
Lower = Utils.GetB64Bitmap(value.Lower)
};
}
Background = Utils.GetB64Bitmap(value.Background),
Upper = Utils.GetB64Bitmap(value.Upper),
Lower = Utils.GetB64Bitmap(value.Lower)
};
}
}
}

View File

@ -1,23 +1,22 @@
using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;
namespace FModel.ViewModels.ApiEndpoints.Models
{
[DebuggerDisplay("{" + nameof(FileName) + "}")]
public class MappingsResponse
{
[J] public string Url { get; private set; }
[J] public string FileName { get; private set; }
[J] public string Hash { get; private set; }
[J] public long Length { get; private set; }
[J] public string Uploaded { get; private set; }
[J] public Meta Meta { get; private set; }
}
namespace FModel.ViewModels.ApiEndpoints.Models;
[DebuggerDisplay("{" + nameof(CompressionMethod) + "}")]
public class Meta
{
[J] public string Version { get; private set; }
[J] public string CompressionMethod { get; private set; }
}
[DebuggerDisplay("{" + nameof(FileName) + "}")]
public class MappingsResponse
{
[J] public string Url { get; private set; }
[J] public string FileName { get; private set; }
[J] public string Hash { get; private set; }
[J] public long Length { get; private set; }
[J] public string Uploaded { get; private set; }
[J] public Meta Meta { get; private set; }
}
[DebuggerDisplay("{" + nameof(CompressionMethod) + "}")]
public class Meta
{
[J] public string Version { get; private set; }
[J] public string CompressionMethod { get; private set; }
}

View File

@ -3,34 +3,33 @@ using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;
using I = Newtonsoft.Json.JsonIgnoreAttribute;
namespace FModel.ViewModels.ApiEndpoints.Models
namespace FModel.ViewModels.ApiEndpoints.Models;
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
public class PlaylistResponse
{
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
public class PlaylistResponse
{
[J] public int Status { get; private set; }
[J] public Playlist Data { get; private set; }
[J] public string Error { get; private set; }
[J] public int Status { get; private set; }
[J] public Playlist Data { get; private set; }
[J] public string Error { get; private set; }
public bool IsSuccess => Status == 200;
public bool HasError => Error != null;
public bool IsSuccess => Status == 200;
public bool HasError => Error != null;
private object DebuggerDisplay => IsSuccess ? Data : $"Error: {Status} | {Error}";
}
private object DebuggerDisplay => IsSuccess ? Data : $"Error: {Status} | {Error}";
}
[DebuggerDisplay("{" + nameof(Id) + "}")]
public class Playlist
{
[J] public string Id { get; private set; }
[J] public PlaylistImages Images { get; private set; }
}
[DebuggerDisplay("{" + nameof(Id) + "}")]
public class Playlist
{
[J] public string Id { get; private set; }
[J] public PlaylistImages Images { get; private set; }
}
public class PlaylistImages
{
[J] public Uri Showcase { get; private set; }
[J] public Uri MissionIcon { get; private set; }
public class PlaylistImages
{
[J] public Uri Showcase { get; private set; }
[J] public Uri MissionIcon { get; private set; }
[I] public bool HasShowcase => Showcase != null;
[I] public bool HasMissionIcon => MissionIcon != null;
}
[I] public bool HasShowcase => Showcase != null;
[I] public bool HasMissionIcon => MissionIcon != null;
}

View File

@ -1,12 +1,8 @@
using CUE4Parse.UE4.Exceptions;
using CUE4Parse.UE4.Readers;
using FModel.Settings;
using Ionic.Zlib;
using RestSharp;
using System;
using System.Collections.Generic;
using System.IO;
@ -18,295 +14,300 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FModel.ViewModels.ApiEndpoints
namespace FModel.ViewModels.ApiEndpoints;
public class ValorantApiEndpoint : AbstractApiProvider
{
public class ValorantApiEndpoint : AbstractApiProvider
private const string _URL = "https://fmodel.fortnite-api.com/valorant/v2/manifest";
public ValorantApiEndpoint(IRestClient client) : base(client)
{
private const string _URL = "https://fmodel.fortnite-api.com/valorant/v2/manifest";
public ValorantApiEndpoint(IRestClient client) : base(client) { }
public async Task<VManifest> GetManifestAsync(CancellationToken token)
{
var request = new RestRequest(_URL, Method.GET);
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
return new VManifest(response.RawBytes);
}
public VManifest GetManifest(CancellationToken token) => GetManifestAsync(token).GetAwaiter().GetResult();
}
public class VManifest
public async Task<VManifest> GetManifestAsync(CancellationToken token)
{
private readonly HttpClient _client;
public readonly VHeader Header;
public readonly VChunk[] Chunks;
public readonly VPak[] Paks;
var request = new RestRequest(_URL, Method.GET);
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
return new VManifest(response.RawBytes);
}
public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data)) { }
public VManifest GetManifest(CancellationToken token) => GetManifestAsync(token).GetAwaiter().GetResult();
}
private VManifest(FArchive Ar)
public class VManifest
{
private readonly HttpClient _client;
public readonly VHeader Header;
public readonly VChunk[] Chunks;
public readonly VPak[] Paks;
public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data))
{
}
private VManifest(FArchive Ar)
{
using (Ar)
{
using (Ar)
{
Header = new VHeader(Ar);
var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize);
var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
if (uncompressedBuffer.Length != Header.UncompressedSize)
throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}");
Header = new VHeader(Ar);
var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize);
var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
if (uncompressedBuffer.Length != Header.UncompressedSize)
throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}");
using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
Chunks = manifest.ReadArray<VChunk>((int) Header.ChunkCount);
Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest));
using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
Chunks = manifest.ReadArray<VChunk>((int) Header.ChunkCount);
Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest));
if (manifest.Position != manifest.Length)
throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}");
}
_client = new HttpClient(new HttpClientHandler
{
UseProxy = false,
UseCookies = false,
AutomaticDecompression = DecompressionMethods.All,
CheckCertificateRevocationList = false,
PreAuthenticate = false,
MaxConnectionsPerServer = 1337,
UseDefaultCredentials = false,
AllowAutoRedirect = false
});
if (manifest.Position != manifest.Length)
throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}");
}
public async ValueTask PrefetchChunk(VChunk chunk, CancellationToken cancellationToken)
_client = new HttpClient(new HttpClientHandler
{
UseProxy = false,
UseCookies = false,
AutomaticDecompression = DecompressionMethods.All,
CheckCertificateRevocationList = false,
PreAuthenticate = false,
MaxConnectionsPerServer = 1337,
UseDefaultCredentials = false,
AllowAutoRedirect = false
});
}
public async ValueTask PrefetchChunk(VChunk chunk, CancellationToken cancellationToken)
{
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
if (File.Exists(chunkPath)) return;
using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
await using var fileStream = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await response.Content.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
}
public async Task<byte[]> GetChunkBytes(VChunk chunk, CancellationToken cancellationToken)
{
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
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, cancellationToken).ConfigureAwait(false);
}
else
{
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
if (File.Exists(chunkPath)) return;
using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
await using var fileStream = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await response.Content.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
}
public async Task<byte[]> GetChunkBytes(VChunk chunk, CancellationToken cancellationToken)
{
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
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, cancellationToken).ConfigureAwait(false);
chunkBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await response.Content.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
else
{
using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
chunkBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await response.Content.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
else
{
chunkBytes = null; // Maybe add logging?
}
chunkBytes = null; // Maybe add logging?
}
return chunkBytes;
}
public Stream GetPakStream(int index) => new VPakStream(this, index);
return chunkBytes;
}
public readonly struct VHeader
public Stream GetPakStream(int index) => new VPakStream(this, index);
}
public readonly struct VHeader
{
private 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 VHeader(FArchive Ar)
{
private const uint _MAGIC = 0xC3D088F7u;
Magic = Ar.Read<uint>();
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;
if (Magic != _MAGIC)
throw new ParserException(Ar, "Invalid manifest magic");
public VHeader(FArchive Ar)
{
Magic = Ar.Read<uint>();
if (Magic != _MAGIC)
throw new ParserException(Ar, "Invalid manifest magic");
HeaderSize = Ar.Read<uint>();
ManifestId = Ar.Read<ulong>();
UncompressedSize = Ar.Read<uint>();
CompressedSize = Ar.Read<uint>();
ChunkCount = Ar.Read<uint>();
PakCount = Ar.Read<uint>();
var gameVersionLength = Ar.ReadByte();
GameVersion = gameVersionLength == 0 ? null : Encoding.ASCII.GetString(Ar.ReadBytes(gameVersionLength));
Ar.Position = HeaderSize;
}
HeaderSize = Ar.Read<uint>();
ManifestId = Ar.Read<ulong>();
UncompressedSize = Ar.Read<uint>();
CompressedSize = Ar.Read<uint>();
ChunkCount = Ar.Read<uint>();
PakCount = Ar.Read<uint>();
var gameVersionLength = Ar.ReadByte();
GameVersion = gameVersionLength == 0 ? null : Encoding.ASCII.GetString(Ar.ReadBytes(gameVersionLength));
Ar.Position = HeaderSize;
}
}
public readonly struct VPak
public readonly struct VPak
{
public readonly ulong Id;
public readonly uint Size;
public readonly uint[] ChunkIndices;
public readonly string Name;
public VPak(FArchive Ar)
{
public readonly ulong Id;
public readonly uint Size;
public readonly uint[] ChunkIndices;
public readonly string Name;
public VPak(FArchive Ar)
{
Id = Ar.Read<ulong>();
Size = Ar.Read<uint>();
ChunkIndices = Ar.ReadArray<uint>(Ar.Read<int>());
Name = Encoding.ASCII.GetString(Ar.ReadBytes(Ar.ReadByte()));
}
public string GetFullName() => $"ValorantLive/ShooterGame/Content/Paks/{Name}";
Id = Ar.Read<ulong>();
Size = Ar.Read<uint>();
ChunkIndices = Ar.ReadArray<uint>(Ar.Read<int>());
Name = Encoding.ASCII.GetString(Ar.ReadBytes(Ar.ReadByte()));
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct VChunk
public string GetFullName() => $"ValorantLive/ShooterGame/Content/Paks/{Name}";
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct VChunk
{
public readonly ulong Id;
public readonly uint Size;
public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}";
}
public class VPakStream : Stream, ICloneable
{
private readonly VManifest _manifest;
private readonly int _pakIndex;
private readonly VChunk[] _chunks;
public VPakStream(VManifest manifest, int pakIndex, long position = 0L)
{
public readonly ulong Id;
public readonly uint Size;
_manifest = manifest;
_pakIndex = pakIndex;
_position = position;
public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}";
var pak = manifest.Paks[pakIndex];
_chunks = new VChunk[pak.ChunkIndices.Length];
for (var i = 0; i < _chunks.Length; i++)
{
_chunks[i] = manifest.Chunks[pak.ChunkIndices[i]];
}
Length = pak.Size;
}
public class VPakStream : Stream, ICloneable
public object Clone() => new VPakStream(_manifest, _pakIndex, _position);
public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
private readonly VManifest _manifest;
private readonly int _pakIndex;
private readonly VChunk[] _chunks;
var (i, startPos) = GetChunkIndex(_position);
if (i == -1) return 0;
public VPakStream(VManifest manifest, int pakIndex, long position = 0L)
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
var bytesRead = 0;
while (true)
{
_manifest = manifest;
_pakIndex = pakIndex;
_position = position;
var chunk = _chunks[i];
var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false);
var chunkBytes = chunk.Size - startPos;
var bytesLeft = count - bytesRead;
var pak = manifest.Paks[pakIndex];
_chunks = new VChunk[pak.ChunkIndices.Length];
for (var i = 0; i < _chunks.Length; i++)
if (bytesLeft <= chunkBytes)
{
_chunks[i] = manifest.Chunks[pak.ChunkIndices[i]];
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint) bytesLeft);
bytesRead += bytesLeft;
break;
}
Length = pak.Size;
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes);
bytesRead += (int) chunkBytes;
startPos = 0u;
if (++i == _chunks.Length) break;
}
public object Clone() => new VPakStream(_manifest, _pakIndex, _position);
public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
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 async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
{
var tasks = new List<Task>();
var s = new SemaphoreSlim(concurrentDownloads);
while (count > 0)
{
await s.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);
s.Dispose();
async Task PrefetchChunkAsync(VChunk chunk)
{
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
s.Release(); // This is intended
}
}
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);
}
private long _position;
public override long Position
{
get => _position;
set
{
if (value >= Length || value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
_position = value;
}
}
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(nameof(offset))
};
return _position;
}
public override long Length { get; }
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => false;
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();
_position += bytesRead;
return bytesRead;
}
private async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
{
var tasks = new List<Task>();
var s = new SemaphoreSlim(concurrentDownloads);
while (count > 0)
{
await s.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);
s.Dispose();
async Task PrefetchChunkAsync(VChunk chunk)
{
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
s.Release(); // This is intended
}
}
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);
}
private long _position;
public override long Position
{
get => _position;
set
{
if (value >= Length || value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
_position = value;
}
}
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(nameof(offset))
};
return _position;
}
public override long Length { get; }
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => false;
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

@ -5,202 +5,200 @@ using FModel.Settings;
using FModel.ViewModels.Commands;
using FModel.Views;
using FModel.Views.Resources.Controls;
using Ionic.Zip;
using Oodle.NET;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
using OodleCUE4 = CUE4Parse.Compression.Oodle;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class ApplicationViewModel : ViewModel
{
public class ApplicationViewModel : ViewModel
private EBuildKind _build;
public EBuildKind Build
{
private EBuildKind _build;
public EBuildKind Build
get => _build;
private set
{
get => _build;
private set
{
SetProperty(ref _build, value);
RaisePropertyChanged(nameof(TitleExtra));
}
}
private bool _isReady;
public bool IsReady
{
get => _isReady;
private set => SetProperty(ref _isReady, value);
}
private EStatusKind _status;
public EStatusKind Status
{
get => _status;
set
{
SetProperty(ref _status, value);
IsReady = Status != EStatusKind.Loading && Status != EStatusKind.Stopping;
}
}
public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this);
private RightClickMenuCommand _rightClickMenuCommand;
public MenuCommand MenuCommand => _menuCommand ??= new MenuCommand(this);
private MenuCommand _menuCommand;
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
private CopyCommand _copyCommand;
public string TitleExtra =>
$"{UserSettings.Default.UpdateMode} - {CUE4Parse.Game.GetDescription()} (" + // FModel {UpdateMode} - {FGame} ({UE}) ({Build})
$"{(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" +
$"{(Build != EBuildKind.Release ? $" ({Build})" : "")}";
public LoadingModesViewModel LoadingModes { get; }
public CustomDirectoriesViewModel CustomDirectories { get; }
public CUE4ParseViewModel CUE4Parse { get; }
public SettingsViewModel SettingsView { get; }
public AesManagerViewModel AesManager { get; }
public AudioPlayerViewModel AudioPlayer { get; }
public MapViewerViewModel MapViewer { get; }
public ModelViewerViewModel ModelViewer { get; }
private OodleCompressor _oodle;
public ApplicationViewModel()
{
Status = EStatusKind.Loading;
#if DEBUG
Build = EBuildKind.Debug;
#elif RELEASE
Build = EBuildKind.Release;
#else
Build = EBuildKind.Unknown;
#endif
LoadingModes = new LoadingModesViewModel();
AvoidEmptyGameDirectoryAndSetEGame(false);
CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory);
CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory);
SettingsView = new SettingsViewModel(CUE4Parse.Game);
AesManager = new AesManagerViewModel(CUE4Parse);
MapViewer = new MapViewerViewModel(CUE4Parse);
AudioPlayer = new AudioPlayerViewModel();
ModelViewer = new ModelViewerViewModel(CUE4Parse.Game);
Status = EStatusKind.Ready;
}
public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched)
{
var gameDirectory = UserSettings.Default.GameDirectory;
if (!string.IsNullOrEmpty(gameDirectory) && !bAlreadyLaunched) return;
var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory);
var result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
if (!result.HasValue || !result.Value) return;
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory;
if (!bAlreadyLaunched || gameDirectory.Equals(gameLauncherViewModel.SelectedDetectedGame.GameDirectory)) return;
RestartWithWarning();
}
public void RestartWithWarning()
{
MessageBox.Show("It looks like you just changed something.\nFModel will restart to apply your changes.", "Uh oh, a restart is needed", MessageBoxButton.OK, MessageBoxImage.Warning);
Restart();
}
public void Restart()
{
var path = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
if (path.EndsWith(".dll"))
{
new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"\"{Path.GetFullPath(Environment.GetCommandLineArgs()[0])}\"",
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
CreateNoWindow = true
}
}.Start();
}
else if (path.EndsWith(".exe"))
{
new Process
{
StartInfo = new ProcessStartInfo
{
FileName = path,
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
CreateNoWindow = true
}
}.Start();
}
Application.Current.Shutdown();
}
public async Task InitVgmStream()
{
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
if (File.Exists(vgmZipFilePath)) return;
await ApplicationService.ApiEndpointView.BenbotApi.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
if (new FileInfo(vgmZipFilePath).Length > 0)
{
var zip = ZipFile.Read(vgmZipFilePath);
var zipDir = vgmZipFilePath.SubstringBeforeLast("\\");
foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently);
}
else
{
FLogger.AppendError();
FLogger.AppendText("Could not download VgmStream", Constants.WHITE, true);
}
}
public async Task InitOodle()
{
var dataDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
var oodlePath = Path.Combine(dataDir.FullName, OodleCUE4.OODLE_DLL_NAME);
if (File.Exists(OodleCUE4.OODLE_DLL_NAME))
{
File.Move(OodleCUE4.OODLE_DLL_NAME, oodlePath, true);
}
else if (!File.Exists(oodlePath))
{
var result = await OodleCUE4.DownloadOodleDll(oodlePath);
if (!result) return;
}
if (File.Exists("oo2core_8_win64.dll"))
File.Delete("oo2core_8_win64.dll");
_oodle = new OodleCompressor(oodlePath);
unsafe
{
OodleCUE4.DecompressFunc = (bufferPtr, bufferSize, outputPtr, outputSize, a, b, c, d, e, f, g, h, i, threadModule) =>
_oodle.Decompress(new IntPtr(bufferPtr), bufferSize, new IntPtr(outputPtr), outputSize,
(OodleLZ_FuzzSafe)a, (OodleLZ_CheckCRC)b, (OodleLZ_Verbosity)c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase)threadModule);
}
SetProperty(ref _build, value);
RaisePropertyChanged(nameof(TitleExtra));
}
}
}
private bool _isReady;
public bool IsReady
{
get => _isReady;
private set => SetProperty(ref _isReady, value);
}
private EStatusKind _status;
public EStatusKind Status
{
get => _status;
set
{
SetProperty(ref _status, value);
IsReady = Status != EStatusKind.Loading && Status != EStatusKind.Stopping;
}
}
public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this);
private RightClickMenuCommand _rightClickMenuCommand;
public MenuCommand MenuCommand => _menuCommand ??= new MenuCommand(this);
private MenuCommand _menuCommand;
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
private CopyCommand _copyCommand;
public string TitleExtra =>
$"{UserSettings.Default.UpdateMode} - {CUE4Parse.Game.GetDescription()} (" + // FModel {UpdateMode} - {FGame} ({UE}) ({Build})
$"{(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" +
$"{(Build != EBuildKind.Release ? $" ({Build})" : "")}";
public LoadingModesViewModel LoadingModes { get; }
public CustomDirectoriesViewModel CustomDirectories { get; }
public CUE4ParseViewModel CUE4Parse { get; }
public SettingsViewModel SettingsView { get; }
public AesManagerViewModel AesManager { get; }
public AudioPlayerViewModel AudioPlayer { get; }
public MapViewerViewModel MapViewer { get; }
public ModelViewerViewModel ModelViewer { get; }
private OodleCompressor _oodle;
public ApplicationViewModel()
{
Status = EStatusKind.Loading;
#if DEBUG
Build = EBuildKind.Debug;
#elif RELEASE
Build = EBuildKind.Release;
#else
Build = EBuildKind.Unknown;
#endif
LoadingModes = new LoadingModesViewModel();
AvoidEmptyGameDirectoryAndSetEGame(false);
CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory);
CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory);
SettingsView = new SettingsViewModel(CUE4Parse.Game);
AesManager = new AesManagerViewModel(CUE4Parse);
MapViewer = new MapViewerViewModel(CUE4Parse);
AudioPlayer = new AudioPlayerViewModel();
ModelViewer = new ModelViewerViewModel(CUE4Parse.Game);
Status = EStatusKind.Ready;
}
public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched)
{
var gameDirectory = UserSettings.Default.GameDirectory;
if (!string.IsNullOrEmpty(gameDirectory) && !bAlreadyLaunched) return;
var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory);
var result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
if (!result.HasValue || !result.Value) return;
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory;
if (!bAlreadyLaunched || gameDirectory == gameLauncherViewModel.SelectedDetectedGame.GameDirectory) return;
RestartWithWarning();
}
public void RestartWithWarning()
{
MessageBox.Show("It looks like you just changed something.\nFModel will restart to apply your changes.", "Uh oh, a restart is needed", MessageBoxButton.OK, MessageBoxImage.Warning);
Restart();
}
public void Restart()
{
var path = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
if (path.EndsWith(".dll"))
{
new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"\"{Path.GetFullPath(Environment.GetCommandLineArgs()[0])}\"",
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
CreateNoWindow = true
}
}.Start();
}
else if (path.EndsWith(".exe"))
{
new Process
{
StartInfo = new ProcessStartInfo
{
FileName = path,
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
CreateNoWindow = true
}
}.Start();
}
Application.Current.Shutdown();
}
public async Task InitVgmStream()
{
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
if (File.Exists(vgmZipFilePath)) return;
await ApplicationService.ApiEndpointView.BenbotApi.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
if (new FileInfo(vgmZipFilePath).Length > 0)
{
var zip = ZipFile.Read(vgmZipFilePath);
var zipDir = vgmZipFilePath.SubstringBeforeLast("\\");
foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently);
}
else
{
FLogger.AppendError();
FLogger.AppendText("Could not download VgmStream", Constants.WHITE, true);
}
}
public async Task InitOodle()
{
var dataDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
var oodlePath = Path.Combine(dataDir.FullName, OodleCUE4.OODLE_DLL_NAME);
if (File.Exists(OodleCUE4.OODLE_DLL_NAME))
{
File.Move(OodleCUE4.OODLE_DLL_NAME, oodlePath, true);
}
else if (!File.Exists(oodlePath))
{
var result = await OodleCUE4.DownloadOodleDll(oodlePath);
if (!result) return;
}
if (File.Exists("oo2core_8_win64.dll"))
File.Delete("oo2core_8_win64.dll");
_oodle = new OodleCompressor(oodlePath);
unsafe
{
OodleCUE4.DecompressFunc = (bufferPtr, bufferSize, outputPtr, outputSize, a, b, c, d, e, f, g, h, i, threadModule) =>
_oodle.Decompress(new IntPtr(bufferPtr), bufferSize, new IntPtr(outputPtr), outputSize,
(OodleLZ_FuzzSafe) a, (OodleLZ_CheckCRC) b, (OodleLZ_Verbosity) c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase) threadModule);
}
}
}

View File

@ -10,162 +10,161 @@ using CUE4Parse.UE4.Vfs;
using FModel.Framework;
using FModel.Services;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class TreeItem : ViewModel
{
public class TreeItem : ViewModel
private string _header;
public string Header
{
private string _header;
public string Header
{
get => _header;
private set => SetProperty(ref _header, value);
}
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set => SetProperty(ref _isExpanded, value);
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
private string _package;
public string Package
{
get => _package;
private set => SetProperty(ref _package, value);
}
private string _mountPoint;
public string MountPoint
{
get => _mountPoint;
private set => SetProperty(ref _mountPoint, value);
}
private int _version;
public int Version
{
get => _version;
private set => SetProperty(ref _version, value);
}
public string PathAtThisPoint { get; }
public AssetsListViewModel AssetsList { get; }
public RangeObservableCollection<TreeItem> Folders { get; }
public ICollectionView FoldersView { get; }
public TreeItem(string header, string package, string mountPoint, int version, string pathHere)
{
Header = header;
Package = package;
MountPoint = mountPoint;
Version = version;
PathAtThisPoint = pathHere;
AssetsList = new AssetsListViewModel();
Folders = new RangeObservableCollection<TreeItem>();
FoldersView = new ListCollectionView(Folders) {SortDescriptions = {new SortDescription("Header", ListSortDirection.Ascending)}};
}
public override string ToString() => $"{Header} | {Folders.Count} Folders | {AssetsList.Assets.Count} Files";
get => _header;
private set => SetProperty(ref _header, value);
}
public class AssetsFolderViewModel
private bool _isExpanded;
public bool IsExpanded
{
public RangeObservableCollection<TreeItem> Folders { get; }
public ICollectionView FoldersView { get; }
get => _isExpanded;
set => SetProperty(ref _isExpanded, value);
}
public AssetsFolderViewModel()
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
private string _package;
public string Package
{
get => _package;
private set => SetProperty(ref _package, value);
}
private string _mountPoint;
public string MountPoint
{
get => _mountPoint;
private set => SetProperty(ref _mountPoint, value);
}
private int _version;
public int Version
{
get => _version;
private set => SetProperty(ref _version, value);
}
public string PathAtThisPoint { get; }
public AssetsListViewModel AssetsList { get; }
public RangeObservableCollection<TreeItem> Folders { get; }
public ICollectionView FoldersView { get; }
public TreeItem(string header, string package, string mountPoint, int version, string pathHere)
{
Header = header;
Package = package;
MountPoint = mountPoint;
Version = version;
PathAtThisPoint = pathHere;
AssetsList = new AssetsListViewModel();
Folders = new RangeObservableCollection<TreeItem>();
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
}
public override string ToString() => $"{Header} | {Folders.Count} Folders | {AssetsList.Assets.Count} Files";
}
public class AssetsFolderViewModel
{
public RangeObservableCollection<TreeItem> Folders { get; }
public ICollectionView FoldersView { get; }
public AssetsFolderViewModel()
{
Folders = new RangeObservableCollection<TreeItem>();
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
}
public void BulkPopulate(IReadOnlyCollection<VfsEntry> entries)
{
if (entries == null || entries.Count == 0)
return;
Application.Current.Dispatcher.Invoke(() =>
{
Folders = new RangeObservableCollection<TreeItem>();
FoldersView = new ListCollectionView(Folders) {SortDescriptions = {new SortDescription("Header", ListSortDirection.Ascending)}};
}
var treeItems = new RangeObservableCollection<TreeItem>();
treeItems.SetSuppressionState(true);
var items = new List<AssetItem>(entries.Count);
public void BulkPopulate(IReadOnlyCollection<VfsEntry> entries)
{
if (entries == null || entries.Count == 0)
return;
Application.Current.Dispatcher.Invoke(() =>
foreach (var entry in entries)
{
var treeItems = new RangeObservableCollection<TreeItem>();
treeItems.SetSuppressionState(true);
var items = new List<AssetItem>(entries.Count);
var item = new AssetItem(entry.Path, entry.IsEncrypted, entry.Offset, entry.Size, entry.Vfs.Name, entry.CompressionMethod);
items.Add(item);
foreach (var entry in entries)
{
var item = new AssetItem(entry.Path, entry.IsEncrypted, entry.Offset, entry.Size, entry.Vfs.Name, entry.CompressionMethod);
items.Add(item);
TreeItem lastNode = null;
var folders = item.FullPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
var builder = new StringBuilder(64);
var parentNode = treeItems;
for (var i = 0; i < folders.Length - 1; i++)
{
TreeItem lastNode = null;
var folders = item.FullPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
var builder = new StringBuilder(64);
var parentNode = treeItems;
var folder = folders[i];
builder.Append(folder).Append('/');
lastNode = FindByHeaderOrNull(parentNode, folder);
for (var i = 0; i < folders.Length - 1; i++)
static TreeItem FindByHeaderOrNull(IReadOnlyList<TreeItem> list, string header)
{
var folder = folders[i];
builder.Append(folder).Append('/');
lastNode = FindByHeaderOrNull(parentNode, folder);
static TreeItem FindByHeaderOrNull(IReadOnlyList<TreeItem> list, string header)
for (var i = 0; i < list.Count; i++)
{
for (var i = 0; i < list.Count; i++)
{
if (list[i].Header == header)
return list[i];
}
return null;
if (list[i].Header == header)
return list[i];
}
if (lastNode == null)
{
var nodePath = builder.ToString();
lastNode = new TreeItem(folder, item.Package, entry.Vfs.MountPoint, entry.Vfs.Ver.Value, nodePath[..^1]);
lastNode.Folders.SetSuppressionState(true);
lastNode.AssetsList.Assets.SetSuppressionState(true);
parentNode.Add(lastNode);
}
parentNode = lastNode.Folders;
return null;
}
lastNode?.AssetsList.Assets.Add(item);
if (lastNode == null)
{
var nodePath = builder.ToString();
lastNode = new TreeItem(folder, item.Package, entry.Vfs.MountPoint, entry.Vfs.Ver.Value, nodePath[..^1]);
lastNode.Folders.SetSuppressionState(true);
lastNode.AssetsList.Assets.SetSuppressionState(true);
parentNode.Add(lastNode);
}
parentNode = lastNode.Folders;
}
lastNode?.AssetsList.Assets.Add(item);
}
}
Folders.AddRange(treeItems);
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(items);
Folders.AddRange(treeItems);
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(items);
foreach (var folder in Folders)
InvokeOnCollectionChanged(folder);
foreach (var folder in Folders)
InvokeOnCollectionChanged(folder);
static void InvokeOnCollectionChanged(TreeItem item)
static void InvokeOnCollectionChanged(TreeItem item)
{
item.Folders.SetSuppressionState(false);
item.AssetsList.Assets.SetSuppressionState(false);
if (item.Folders.Count != 0)
{
item.Folders.SetSuppressionState(false);
item.AssetsList.Assets.SetSuppressionState(false);
item.Folders.InvokeOnCollectionChanged();
if (item.Folders.Count != 0)
{
item.Folders.SetSuppressionState(false);
item.Folders.InvokeOnCollectionChanged();
foreach (var folderItem in item.Folders)
InvokeOnCollectionChanged(folderItem);
}
if (item.AssetsList.Assets.Count != 0)
item.AssetsList.Assets.InvokeOnCollectionChanged();
foreach (var folderItem in item.Folders)
InvokeOnCollectionChanged(folderItem);
}
});
}
if (item.AssetsList.Assets.Count != 0)
item.AssetsList.Assets.InvokeOnCollectionChanged();
}
});
}
}
}

View File

@ -3,77 +3,76 @@ using System.Windows.Data;
using CUE4Parse.Compression;
using FModel.Framework;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class AssetItem : ViewModel
{
public class AssetItem : ViewModel
private string _fullPath;
public string FullPath
{
private string _fullPath;
public string FullPath
{
get => _fullPath;
private set => SetProperty(ref _fullPath, value);
}
private bool _isEncrypted;
public bool IsEncrypted
{
get => _isEncrypted;
private set => SetProperty(ref _isEncrypted, value);
}
private long _offset;
public long Offset
{
get => _offset;
private set => SetProperty(ref _offset, value);
}
private long _size;
public long Size
{
get => _size;
private set => SetProperty(ref _size, value);
}
private string _package;
public string Package
{
get => _package;
private set => SetProperty(ref _package, value);
}
private CompressionMethod _compression;
public CompressionMethod Compression
{
get => _compression;
private set => SetProperty(ref _compression, value);
}
public AssetItem(string fullPath, bool isEncrypted, long offset, long size, string package, CompressionMethod compression)
{
FullPath = fullPath;
IsEncrypted = isEncrypted;
Offset = offset;
Size = size;
Package = package;
Compression = compression;
}
public override string ToString() => FullPath;
get => _fullPath;
private set => SetProperty(ref _fullPath, value);
}
public class AssetsListViewModel
private bool _isEncrypted;
public bool IsEncrypted
{
public RangeObservableCollection<AssetItem> Assets { get; }
public ICollectionView AssetsView { get; }
get => _isEncrypted;
private set => SetProperty(ref _isEncrypted, value);
}
public AssetsListViewModel()
private long _offset;
public long Offset
{
get => _offset;
private set => SetProperty(ref _offset, value);
}
private long _size;
public long Size
{
get => _size;
private set => SetProperty(ref _size, value);
}
private string _package;
public string Package
{
get => _package;
private set => SetProperty(ref _package, value);
}
private CompressionMethod _compression;
public CompressionMethod Compression
{
get => _compression;
private set => SetProperty(ref _compression, value);
}
public AssetItem(string fullPath, bool isEncrypted, long offset, long size, string package, CompressionMethod compression)
{
FullPath = fullPath;
IsEncrypted = isEncrypted;
Offset = offset;
Size = size;
Package = package;
Compression = compression;
}
public override string ToString() => FullPath;
}
public class AssetsListViewModel
{
public RangeObservableCollection<AssetItem> Assets { get; }
public ICollectionView AssetsView { get; }
public AssetsListViewModel()
{
Assets = new RangeObservableCollection<AssetItem>();
AssetsView = new ListCollectionView(Assets)
{
Assets = new RangeObservableCollection<AssetItem>();
AssetsView = new ListCollectionView(Assets)
{
SortDescriptions = {new SortDescription("FullPath", ListSortDirection.Ascending)}
};
}
SortDescriptions = { new SortDescription("FullPath", ListSortDirection.Ascending) }
};
}
}

File diff suppressed because it is too large Load Diff

View File

@ -16,102 +16,101 @@ using K4os.Compression.LZ4;
using K4os.Compression.LZ4.Streams;
using Serilog;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class BackupManagerViewModel : ViewModel
{
public class BackupManagerViewModel : ViewModel
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private readonly string _gameName;
private Backup _selectedBackup;
public Backup SelectedBackup
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private readonly string _gameName;
get => _selectedBackup;
set => SetProperty(ref _selectedBackup, value);
}
private Backup _selectedBackup;
public Backup SelectedBackup
public ObservableCollection<Backup> Backups { get; }
public ICollectionView BackupsView { get; }
public BackupManagerViewModel(string gameName)
{
_gameName = gameName;
Backups = new ObservableCollection<Backup>();
BackupsView = new ListCollectionView(Backups) { SortDescriptions = { new SortDescription("FileName", ListSortDirection.Ascending) } };
}
public async Task Initialize()
{
await _threadWorkerView.Begin(cancellationToken =>
{
get => _selectedBackup;
set => SetProperty(ref _selectedBackup, value);
}
var backups = _apiEndpointView.FModelApi.GetBackups(cancellationToken, _gameName);
if (backups == null) return;
public ObservableCollection<Backup> Backups { get; }
public ICollectionView BackupsView { get; }
public BackupManagerViewModel(string gameName)
{
_gameName = gameName;
Backups = new ObservableCollection<Backup>();
BackupsView = new ListCollectionView(Backups) {SortDescriptions = {new SortDescription("FileName", ListSortDirection.Ascending)}};
}
public async Task Initialize()
{
await _threadWorkerView.Begin(cancellationToken =>
Application.Current.Dispatcher.Invoke(() =>
{
var backups = _apiEndpointView.FModelApi.GetBackups(cancellationToken, _gameName);
if (backups == null) return;
Application.Current.Dispatcher.Invoke(() =>
{
foreach (var backup in backups) Backups.Add(backup);
SelectedBackup = Backups.LastOrDefault();
});
foreach (var backup in backups) Backups.Add(backup);
SelectedBackup = Backups.LastOrDefault();
});
}
});
}
public async Task CreateBackup()
public async Task CreateBackup()
{
await _threadWorkerView.Begin(_ =>
{
await _threadWorkerView.Begin(_ =>
var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups");
var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp";
var fullPath = Path.Combine(backupFolder, fileName);
using var fileStream = new FileStream(fullPath, FileMode.Create);
using var compressedStream = LZ4Stream.Encode(fileStream, LZ4Level.L00_FAST);
using var writer = new BinaryWriter(compressedStream);
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
{
var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups");
var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp";
var fullPath = Path.Combine(backupFolder, fileName);
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") ||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
continue;
using var fileStream = new FileStream(fullPath, FileMode.Create);
using var compressedStream = LZ4Stream.Encode(fileStream, LZ4Level.L00_FAST);
using var writer = new BinaryWriter(compressedStream);
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
{
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") ||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
continue;
writer.Write((long) 0);
writer.Write((long) 0);
writer.Write(entry.Size);
writer.Write(entry.IsEncrypted);
writer.Write(0);
writer.Write($"/{entry.Path.ToLower()}");
writer.Write(0);
}
SaveCheck(fullPath, fileName, "created", "create");
});
}
public async Task Download()
{
if (SelectedBackup == null) return;
await _threadWorkerView.Begin(_ =>
{
var fullPath = Path.Combine(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"), SelectedBackup.FileName);
_apiEndpointView.BenbotApi.DownloadFile(SelectedBackup.DownloadUrl, fullPath);
SaveCheck(fullPath, SelectedBackup.FileName, "downloaded", "download");
});
}
private void SaveCheck(string fullPath, string fileName, string type1, string type2)
{
if (new FileInfo(fullPath).Length > 0)
{
Log.Information("{FileName} successfully {Type}", fileName, type1);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully {type1} '{fileName}'", Constants.WHITE, true);
}
else
{
Log.Error("{FileName} could not be {Type}", fileName, type1);
FLogger.AppendError();
FLogger.AppendText($"Could not {type2} '{fileName}'", Constants.WHITE, true);
writer.Write((long) 0);
writer.Write((long) 0);
writer.Write(entry.Size);
writer.Write(entry.IsEncrypted);
writer.Write(0);
writer.Write($"/{entry.Path.ToLower()}");
writer.Write(0);
}
SaveCheck(fullPath, fileName, "created", "create");
});
}
public async Task Download()
{
if (SelectedBackup == null) return;
await _threadWorkerView.Begin(_ =>
{
var fullPath = Path.Combine(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"), SelectedBackup.FileName);
_apiEndpointView.BenbotApi.DownloadFile(SelectedBackup.DownloadUrl, fullPath);
SaveCheck(fullPath, SelectedBackup.FileName, "downloaded", "download");
});
}
private void SaveCheck(string fullPath, string fileName, string type1, string type2)
{
if (new FileInfo(fullPath).Length > 0)
{
Log.Information("{FileName} successfully {Type}", fileName, type1);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully {type1} '{fileName}'", Constants.WHITE, true);
}
else
{
Log.Error("{FileName} could not be {Type}", fileName, type1);
FLogger.AppendError();
FLogger.AppendText($"Could not {type2} '{fileName}'", Constants.WHITE, true);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,34 +2,33 @@
using FModel.Framework;
using FModel.Views;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class AddEditDirectoryCommand : ViewModelCommand<CustomDirectoriesViewModel>
{
public class AddEditDirectoryCommand : ViewModelCommand<CustomDirectoriesViewModel>
public AddEditDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
{
public AddEditDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
{
}
}
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not CustomDirectory customDir)
customDir = new CustomDirectory();
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not CustomDirectory customDir)
customDir = new CustomDirectory();
Helper.OpenWindow<AdonisWindow>("Custom Directory", () =>
Helper.OpenWindow<AdonisWindow>("Custom Directory", () =>
{
var index = contextViewModel.GetIndex(customDir);
var input = new CustomDir(customDir);
var result = input.ShowDialog();
if (!result.HasValue || !result.Value || string.IsNullOrEmpty(customDir.Header) && string.IsNullOrEmpty(customDir.DirectoryPath))
return;
if (index > 1)
{
var index = contextViewModel.GetIndex(customDir);
var input = new CustomDir(customDir);
var result = input.ShowDialog();
if (!result.HasValue || !result.Value || string.IsNullOrEmpty(customDir.Header) && string.IsNullOrEmpty(customDir.DirectoryPath))
return;
if (index > 1)
{
contextViewModel.Edit(index, customDir);
}
else
contextViewModel.Add(customDir);
});
}
contextViewModel.Edit(index, customDir);
}
else
contextViewModel.Add(customDir);
});
}
}

View File

@ -1,16 +1,15 @@
using FModel.Framework;
namespace FModel.ViewModels.Commands
{
public class AddTabCommand : ViewModelCommand<TabControlViewModel>
{
public AddTabCommand(TabControlViewModel contextViewModel) : base(contextViewModel)
{
}
namespace FModel.ViewModels.Commands;
public override void Execute(TabControlViewModel contextViewModel, object parameter)
{
contextViewModel.AddTab();
}
public class AddTabCommand : ViewModelCommand<TabControlViewModel>
{
public AddTabCommand(TabControlViewModel contextViewModel) : base(contextViewModel)
{
}
public override void Execute(TabControlViewModel contextViewModel, object parameter)
{
contextViewModel.AddTab();
}
}

View File

@ -1,45 +1,44 @@
using FModel.Framework;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class AudioCommand : ViewModelCommand<AudioPlayerViewModel>
{
public class AudioCommand : ViewModelCommand<AudioPlayerViewModel>
public AudioCommand(AudioPlayerViewModel contextViewModel) : base(contextViewModel)
{
public AudioCommand(AudioPlayerViewModel contextViewModel) : base(contextViewModel)
{
}
}
public override void Execute(AudioPlayerViewModel contextViewModel, object parameter)
{
if (parameter is not string s)
return;
public override void Execute(AudioPlayerViewModel contextViewModel, object parameter)
{
if (parameter is not string s)
return;
switch (s)
{
case "Previous":
contextViewModel.Previous();
break;
case "PlayPause":
contextViewModel.PlayPauseOnStart();
break;
case "ForcePlayPause":
contextViewModel.PlayPauseOnForce();
break;
case "Stop":
contextViewModel.Stop();
break;
case "Next":
contextViewModel.Next();
break;
case "Remove":
contextViewModel.Remove();
break;
case "Save":
contextViewModel.Save();
break;
case "Save_Playlist":
contextViewModel.SavePlaylist();
break;
}
switch (s)
{
case "Previous":
contextViewModel.Previous();
break;
case "PlayPause":
contextViewModel.PlayPauseOnStart();
break;
case "ForcePlayPause":
contextViewModel.PlayPauseOnForce();
break;
case "Stop":
contextViewModel.Stop();
break;
case "Next":
contextViewModel.Next();
break;
case "Remove":
contextViewModel.Remove();
break;
case "Save":
contextViewModel.Save();
break;
case "Save_Playlist":
contextViewModel.SavePlaylist();
break;
}
}
}

View File

@ -5,43 +5,42 @@ using System.Windows;
using FModel.Extensions;
using FModel.Framework;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class CopyCommand : ViewModelCommand<ApplicationViewModel>
{
public class CopyCommand : ViewModelCommand<ApplicationViewModel>
public CopyCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
{
public CopyCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
}
public override void Execute(ApplicationViewModel contextViewModel, object parameter)
{
if (parameter is not object[] parameters || parameters[0] is not string trigger)
return;
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
if (!assetItems.Any()) return;
var sb = new StringBuilder();
switch (trigger)
{
case "File_Path":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath);
break;
case "File_Name":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/'));
break;
case "Directory_Path":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('/'));
break;
case "File_Path_No_Extension":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('.'));
break;
case "File_Name_No_Extension":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/').SubstringBeforeLast('.'));
break;
}
public override void Execute(ApplicationViewModel contextViewModel, object parameter)
{
if (parameter is not object[] parameters || parameters[0] is not string trigger)
return;
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
if (!assetItems.Any()) return;
var sb = new StringBuilder();
switch (trigger)
{
case "File_Path":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath);
break;
case "File_Name":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/'));
break;
case "Directory_Path":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('/'));
break;
case "File_Path_No_Extension":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('.'));
break;
case "File_Name_No_Extension":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/').SubstringBeforeLast('.'));
break;
}
Clipboard.SetText(sb.ToString().TrimEnd());
}
Clipboard.SetText(sb.ToString().TrimEnd());
}
}

View File

@ -1,21 +1,20 @@
using FModel.Framework;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class DeleteDirectoryCommand : ViewModelCommand<CustomDirectoriesViewModel>
{
public class DeleteDirectoryCommand : ViewModelCommand<CustomDirectoriesViewModel>
public DeleteDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
{
public DeleteDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
{
}
}
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not CustomDirectory customDir) return;
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not CustomDirectory customDir) return;
var index = contextViewModel.GetIndex(customDir);
if (index < 2) return;
var index = contextViewModel.GetIndex(customDir);
if (index < 2) return;
contextViewModel.Delete(index);
}
contextViewModel.Delete(index);
}
}

View File

@ -2,57 +2,56 @@
using FModel.Framework;
using FModel.Services;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class GoToCommand : ViewModelCommand<CustomDirectoriesViewModel>
{
public class GoToCommand : ViewModelCommand<CustomDirectoriesViewModel>
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public GoToCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
{
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
}
public GoToCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not string s || string.IsNullOrEmpty(s)) return;
JumpTo(s);
}
public TreeItem JumpTo(string directory)
{
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
var root = _applicationView.CUE4Parse.AssetsFolder.Folders;
if (root is not { Count: > 0 }) return null;
var i = 0;
var done = false;
var folders = directory.Split('/');
while (!done)
{
}
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not string s || string.IsNullOrEmpty(s)) return;
JumpTo(s);
}
public TreeItem JumpTo(string directory)
{
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
var root = _applicationView.CUE4Parse.AssetsFolder.Folders;
if (root is not {Count: > 0}) return null;
var i = 0;
var done = false;
var folders = directory.Split('/');
while (!done)
foreach (var folder in root)
{
foreach (var folder in root)
if (!folder.Header.Equals(folders[i], i == 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
continue;
folder.IsExpanded = true; // folder found = expand
// is this the last folder aka the one we want to jump in
if (i >= folders.Length - 1)
{
if (!folder.Header.Equals(folders[i], i == 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
continue;
folder.IsExpanded = true; // folder found = expand
// is this the last folder aka the one we want to jump in
if (i >= folders.Length - 1)
{
folder.IsSelected = true; // select it
return folder;
}
root = folder.Folders; // grab his subfolders
break;
folder.IsSelected = true; // select it
return folder;
}
i++;
done = i == folders.Length || root.Count == 0;
root = folder.Folders; // grab his subfolders
break;
}
return null;
i++;
done = i == folders.Length || root.Count == 0;
}
return null;
}
}

View File

@ -2,49 +2,47 @@
using FModel.Extensions;
using FModel.Framework;
using FModel.Views.Resources.Controls;
using System.IO;
using System.Windows;
using System.Windows.Media;
using FModel.Views.Resources.Converters;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class ImageCommand : ViewModelCommand<TabItem>
{
public class ImageCommand : ViewModelCommand<TabItem>
public ImageCommand(TabItem contextViewModel) : base(contextViewModel)
{
public ImageCommand(TabItem contextViewModel) : base(contextViewModel)
{
}
}
public override void Execute(TabItem contextViewModel, object parameter)
{
if (parameter == null || !contextViewModel.HasImage) return;
public override void Execute(TabItem contextViewModel, object parameter)
{
if (parameter == null || !contextViewModel.HasImage) return;
switch (parameter)
switch (parameter)
{
case "Open":
{
case "Open":
Helper.OpenWindow<AdonisWindow>(contextViewModel.SelectedImage.ExportName + " (Image)", () =>
{
Helper.OpenWindow<AdonisWindow>(contextViewModel.SelectedImage.ExportName + " (Image)", () =>
var popout = new ImagePopout
{
var popout = new ImagePopout
{
Title = contextViewModel.SelectedImage.ExportName + " (Image)",
Width = contextViewModel.SelectedImage.Image.Width,
Height = contextViewModel.SelectedImage.Image.Height,
WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
ImageCtrl = {Source = contextViewModel.SelectedImage.Image}
};
RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor));
popout.Show();
});
break;
}
case "Copy":
ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png");
break;
case "Save":
contextViewModel.SaveImage(false);
break;
Title = contextViewModel.SelectedImage.ExportName + " (Image)",
Width = contextViewModel.SelectedImage.Image.Width,
Height = contextViewModel.SelectedImage.Image.Height,
WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
ImageCtrl = { Source = contextViewModel.SelectedImage.Image }
};
RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor));
popout.Show();
});
break;
}
case "Copy":
ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png");
break;
case "Save":
contextViewModel.SaveImage(false);
break;
}
}
}
}

View File

@ -18,210 +18,209 @@ using FModel.Views.Resources.Controls;
using K4os.Compression.LZ4.Streams;
using Microsoft.Win32;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
/// <summary>
/// this will always load all files no matter the loading mode
/// however what this does is filtering what to show to the user
/// </summary>
public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
{
/// <summary>
/// this will always load all files no matter the loading mode
/// however what this does is filtering what to show to the user
/// </summary>
public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
private const uint _IS_LZ4 = 0x184D2204u;
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
public LoadCommand(LoadingModesViewModel contextViewModel) : base(contextViewModel)
{
private const uint _IS_LZ4 = 0x184D2204u;
}
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
public LoadCommand(LoadingModesViewModel contextViewModel) : base(contextViewModel)
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
{
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
if (_applicationView.CUE4Parse.Provider.Files.Count <= 0)
{
FLogger.AppendError();
FLogger.AppendText("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true);
return;
}
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
if (_applicationView.CUE4Parse.Game == FGame.FortniteGame &&
_applicationView.CUE4Parse.Provider.MappingsContainer == null)
{
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
if (_applicationView.CUE4Parse.Provider.Files.Count <= 0)
{
FLogger.AppendError();
FLogger.AppendText("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true);
return;
}
if (_applicationView.CUE4Parse.Game == FGame.FortniteGame &&
_applicationView.CUE4Parse.Provider.MappingsContainer == null)
{
FLogger.AppendError();
FLogger.AppendText("Mappings could not get pulled, extracting packages might not work properly. If so, press F12 or please restart.", Constants.WHITE, true);
}
FLogger.AppendError();
FLogger.AppendText("Mappings could not get pulled, extracting packages might not work properly. If so, press F12 or please restart.", Constants.WHITE, true);
}
#if DEBUG
var loadingTime = Stopwatch.StartNew();
var loadingTime = Stopwatch.StartNew();
#endif
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
await _applicationView.CUE4Parse.LoadLocalizedResources(); // load locres if not already loaded
await _applicationView.CUE4Parse.LoadVirtualPaths(); // load virtual paths if not already loaded
Helper.CloseWindow<AdonisWindow>("Search View"); // close search window if opened
await _applicationView.CUE4Parse.LoadLocalizedResources(); // load locres if not already loaded
await _applicationView.CUE4Parse.LoadVirtualPaths(); // load virtual paths if not already loaded
Helper.CloseWindow<AdonisWindow>("Search View"); // close search window if opened
await _threadWorkerView.Begin(cancellationToken =>
{
// filter what to show
switch (UserSettings.Default.LoadingMode)
{
case ELoadingMode.Single:
case ELoadingMode.Multiple:
{
var l = (IList) parameter;
if (l.Count < 1) return;
var directoryFilesToShow = l.Cast<FileItem>();
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);
break;
}
case ELoadingMode.All:
{
FilterDirectoryFilesToDisplay(cancellationToken, null);
break;
}
case ELoadingMode.AllButNew:
case ELoadingMode.AllButModified:
{
FilterNewOrModifiedFilesToDisplay(cancellationToken);
break;
}
default: throw new ArgumentOutOfRangeException();
}
_discordHandler.UpdatePresence(_applicationView.CUE4Parse);
});
#if DEBUG
loadingTime.Stop();
FLogger.AppendDebug();
FLogger.AppendText($"{_applicationView.CUE4Parse.SearchVm.SearchResults.Count} packages, {_applicationView.CUE4Parse.LocalizedResourcesCount} localized resources, and {_applicationView.CUE4Parse.VirtualPathCount} virtual paths loaded in {loadingTime.Elapsed.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)} seconds", Constants.WHITE, true);
#endif
}
private void FilterDirectoryFilesToDisplay(CancellationToken cancellationToken, IEnumerable<FileItem> directoryFiles)
await _threadWorkerView.Begin(cancellationToken =>
{
HashSet<string> filter;
if (directoryFiles == null)
filter = null;
else
{
filter = new HashSet<string>();
foreach (var directoryFile in directoryFiles)
{
if (!directoryFile.IsEnabled)
continue;
filter.Add(directoryFile.Name);
}
}
var hasFilter = filter != null && filter.Count != 0;
var entries = new List<VfsEntry>();
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
{
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
continue;
if (hasFilter)
{
if (filter.Contains(entry.Vfs.Name))
entries.Add(entry);
}
else
entries.Add(entry);
}
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
private void FilterNewOrModifiedFilesToDisplay(CancellationToken cancellationToken)
{
var openFileDialog = new OpenFileDialog
{
Title = "Select a backup file older than your current game version",
InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"),
Filter = "FBKP Files (*.fbkp)|*.fbkp|All Files (*.*)|*.*",
Multiselect = false
};
if (!(bool) openFileDialog.ShowDialog()) return;
FLogger.AppendInformation();
FLogger.AppendText($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true);
using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
using var memoryStream = new MemoryStream();
if (fileStream.ReadUInt32() == _IS_LZ4)
{
fileStream.Position -= 4;
using var compressionStream = LZ4Stream.Decode(fileStream);
compressionStream.CopyTo(memoryStream);
}
else fileStream.CopyTo(memoryStream);
memoryStream.Position = 0;
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
var entries = new List<VfsEntry>();
// filter what to show
switch (UserSettings.Default.LoadingMode)
{
case ELoadingMode.AllButNew:
case ELoadingMode.Single:
case ELoadingMode.Multiple:
{
var paths = new Dictionary<string, int>();
while (archive.Position < archive.Length)
{
cancellationToken.ThrowIfCancellationRequested();
archive.Position += 29;
paths[archive.ReadString().ToLower()[1..]] = 0;
archive.Position += 4;
}
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
{
cancellationToken.ThrowIfCancellationRequested();
if (value is not VfsEntry entry || paths.ContainsKey(key) || entry.Path.EndsWith(".uexp") ||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
entries.Add(entry);
}
var l = (IList) parameter;
if (l.Count < 1) return;
var directoryFilesToShow = l.Cast<FileItem>();
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);
break;
}
case ELoadingMode.All:
{
FilterDirectoryFilesToDisplay(cancellationToken, null);
break;
}
case ELoadingMode.AllButNew:
case ELoadingMode.AllButModified:
{
while (archive.Position < archive.Length)
{
cancellationToken.ThrowIfCancellationRequested();
archive.Position += 16;
var uncompressedSize = archive.Read<long>();
var isEncrypted = archive.ReadFlag();
archive.Position += 4;
var fullPath = archive.ReadString().ToLower()[1..];
archive.Position += 4;
if (fullPath.EndsWith(".uexp") || fullPath.EndsWith(".ubulk") || fullPath.EndsWith(".uptnl") ||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(fullPath, out var asset) || asset is not VfsEntry entry ||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
continue;
entries.Add(entry);
}
FilterNewOrModifiedFilesToDisplay(cancellationToken);
break;
}
default: throw new ArgumentOutOfRangeException();
}
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
_discordHandler.UpdatePresence(_applicationView.CUE4Parse);
});
#if DEBUG
loadingTime.Stop();
FLogger.AppendDebug();
FLogger.AppendText($"{_applicationView.CUE4Parse.SearchVm.SearchResults.Count} packages, {_applicationView.CUE4Parse.LocalizedResourcesCount} localized resources, and {_applicationView.CUE4Parse.VirtualPathCount} virtual paths loaded in {loadingTime.Elapsed.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)} seconds", Constants.WHITE, true);
#endif
}
}
private void FilterDirectoryFilesToDisplay(CancellationToken cancellationToken, IEnumerable<FileItem> directoryFiles)
{
HashSet<string> filter;
if (directoryFiles == null)
filter = null;
else
{
filter = new HashSet<string>();
foreach (var directoryFile in directoryFiles)
{
if (!directoryFile.IsEnabled)
continue;
filter.Add(directoryFile.Name);
}
}
var hasFilter = filter != null && filter.Count != 0;
var entries = new List<VfsEntry>();
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
{
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
continue;
if (hasFilter)
{
if (filter.Contains(entry.Vfs.Name))
entries.Add(entry);
}
else
entries.Add(entry);
}
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
private void FilterNewOrModifiedFilesToDisplay(CancellationToken cancellationToken)
{
var openFileDialog = new OpenFileDialog
{
Title = "Select a backup file older than your current game version",
InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"),
Filter = "FBKP Files (*.fbkp)|*.fbkp|All Files (*.*)|*.*",
Multiselect = false
};
if (!openFileDialog.ShowDialog().GetValueOrDefault()) return;
FLogger.AppendInformation();
FLogger.AppendText($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true);
using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
using var memoryStream = new MemoryStream();
if (fileStream.ReadUInt32() == _IS_LZ4)
{
fileStream.Position -= 4;
using var compressionStream = LZ4Stream.Decode(fileStream);
compressionStream.CopyTo(memoryStream);
}
else fileStream.CopyTo(memoryStream);
memoryStream.Position = 0;
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
var entries = new List<VfsEntry>();
switch (UserSettings.Default.LoadingMode)
{
case ELoadingMode.AllButNew:
{
var paths = new Dictionary<string, int>();
while (archive.Position < archive.Length)
{
cancellationToken.ThrowIfCancellationRequested();
archive.Position += 29;
paths[archive.ReadString().ToLower()[1..]] = 0;
archive.Position += 4;
}
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
{
cancellationToken.ThrowIfCancellationRequested();
if (value is not VfsEntry entry || paths.ContainsKey(key) || entry.Path.EndsWith(".uexp") ||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
entries.Add(entry);
}
break;
}
case ELoadingMode.AllButModified:
{
while (archive.Position < archive.Length)
{
cancellationToken.ThrowIfCancellationRequested();
archive.Position += 16;
var uncompressedSize = archive.Read<long>();
var isEncrypted = archive.ReadFlag();
archive.Position += 4;
var fullPath = archive.ReadString().ToLower()[1..];
archive.Position += 4;
if (fullPath.EndsWith(".uexp") || fullPath.EndsWith(".ubulk") || fullPath.EndsWith(".uptnl") ||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(fullPath, out var asset) || asset is not VfsEntry entry ||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
continue;
entries.Add(entry);
}
break;
}
}
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
}

View File

@ -9,105 +9,104 @@ using FModel.Views;
using FModel.Views.Resources.Controls;
using Newtonsoft.Json;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class MenuCommand : ViewModelCommand<ApplicationViewModel>
{
public class MenuCommand : ViewModelCommand<ApplicationViewModel>
public MenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
{
public MenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
{
}
}
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
{
switch (parameter)
{
switch (parameter)
{
case "Directory_Selector":
contextViewModel.AvoidEmptyGameDirectoryAndSetEGame(true);
break;
case "Directory_AES":
Helper.OpenWindow<AdonisWindow>("AES Manager", () => new AesManager().Show());
break;
case "Directory_Backup":
Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.GameName).Show());
break;
case "Directory_ArchivesInfo":
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false);
break;
case "Views_AudioPlayer":
Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show());
break;
case "Views_MapViewer":
Helper.OpenWindow<AdonisWindow>("Map Viewer", () => new MapViewer().Show());
break;
case "Views_ImageMerger":
Helper.OpenWindow<AdonisWindow>("Image Merger", () => new ImageMerger().Show());
break;
case "Settings":
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
break;
case "ModelSettings":
UserSettings.Default.LastOpenedSettingTab = contextViewModel.CUE4Parse.Game == FGame.FortniteGame ? 2 : 1;
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
break;
case "Help_About":
Helper.OpenWindow<AdonisWindow>("About", () => new About().Show());
break;
case "Help_Donate":
Process.Start(new ProcessStartInfo {FileName = Constants.DONATE_LINK, UseShellExecute = true});
break;
case "Help_Changelog":
UserSettings.Default.ShowChangelog = true;
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
break;
case "Help_BugsReport":
Process.Start(new ProcessStartInfo {FileName = Constants.ISSUE_LINK, UseShellExecute = true});
break;
case "Help_Discord":
Process.Start(new ProcessStartInfo {FileName = Constants.DISCORD_LINK, UseShellExecute = true});
break;
case "ToolBox_Clear_Logs":
FLogger.Logger.Text = string.Empty;
break;
case "ToolBox_Open_Output_Directory":
Process.Start(new ProcessStartInfo {FileName = UserSettings.Default.OutputDirectory, UseShellExecute = true});
break;
case "ToolBox_Expand_All":
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
case "Directory_Selector":
contextViewModel.AvoidEmptyGameDirectoryAndSetEGame(true);
break;
case "Directory_AES":
Helper.OpenWindow<AdonisWindow>("AES Manager", () => new AesManager().Show());
break;
case "Directory_Backup":
Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.GameName).Show());
break;
case "Directory_ArchivesInfo":
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false);
break;
case "Views_AudioPlayer":
Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show());
break;
case "Views_MapViewer":
Helper.OpenWindow<AdonisWindow>("Map Viewer", () => new MapViewer().Show());
break;
case "Views_ImageMerger":
Helper.OpenWindow<AdonisWindow>("Image Merger", () => new ImageMerger().Show());
break;
case "Settings":
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
break;
case "ModelSettings":
UserSettings.Default.LastOpenedSettingTab = contextViewModel.CUE4Parse.Game == FGame.FortniteGame ? 2 : 1;
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
break;
case "Help_About":
Helper.OpenWindow<AdonisWindow>("About", () => new About().Show());
break;
case "Help_Donate":
Process.Start(new ProcessStartInfo { FileName = Constants.DONATE_LINK, UseShellExecute = true });
break;
case "Help_Changelog":
UserSettings.Default.ShowChangelog = true;
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
break;
case "Help_BugsReport":
Process.Start(new ProcessStartInfo { FileName = Constants.ISSUE_LINK, UseShellExecute = true });
break;
case "Help_Discord":
Process.Start(new ProcessStartInfo { FileName = Constants.DISCORD_LINK, UseShellExecute = true });
break;
case "ToolBox_Clear_Logs":
FLogger.Logger.Text = string.Empty;
break;
case "ToolBox_Open_Output_Directory":
Process.Start(new ProcessStartInfo { FileName = UserSettings.Default.OutputDirectory, UseShellExecute = true });
break;
case "ToolBox_Expand_All":
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
{
foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders)
{
foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders)
{
LoopFolders(cancellationToken, folder, true);
}
});
break;
case "ToolBox_Collapse_All":
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
LoopFolders(cancellationToken, folder, true);
}
});
break;
case "ToolBox_Collapse_All":
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
{
foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders)
{
foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders)
{
LoopFolders(cancellationToken, folder, false);
}
});
break;
case TreeItem selectedFolder:
selectedFolder.IsSelected = false;
selectedFolder.IsSelected = true;
break;
}
}
private void LoopFolders(CancellationToken cancellationToken, TreeItem parent, bool isExpanded)
{
if (parent.IsExpanded != isExpanded)
{
parent.IsExpanded = isExpanded;
Thread.Sleep(10);
}
cancellationToken.ThrowIfCancellationRequested();
foreach (var f in parent.Folders) LoopFolders(cancellationToken, f, isExpanded);
LoopFolders(cancellationToken, folder, false);
}
});
break;
case TreeItem selectedFolder:
selectedFolder.IsSelected = false;
selectedFolder.IsSelected = true;
break;
}
}
}
private void LoopFolders(CancellationToken cancellationToken, TreeItem parent, bool isExpanded)
{
if (parent.IsExpanded != isExpanded)
{
parent.IsExpanded = isExpanded;
Thread.Sleep(10);
}
cancellationToken.ThrowIfCancellationRequested();
foreach (var f in parent.Folders) LoopFolders(cancellationToken, f, isExpanded);
}
}

View File

@ -3,60 +3,63 @@ using System.Linq;
using FModel.Framework;
using FModel.Services;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
{
public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
{
}
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
{
if (parameter is not object[] parameters || parameters[0] is not string trigger)
return;
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
if (!assetItems.Any()) return;
await _threadWorkerView.Begin(cancellationToken =>
{
switch (trigger)
{
case "Assets_Extract_New_Tab":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath, true);
}
break;
case "Assets_Export_Data":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.ExportData(asset.FullPath);
}
break;
case "Assets_Save_Properties":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath);
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveProperty(false);
}
break;
case "Assets_Save_Texture":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath);
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveImage(false);
}
break;
}
});
}
}
}
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
{
if (parameter is not object[] parameters || parameters[0] is not string trigger)
return;
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
if (!assetItems.Any()) return;
await _threadWorkerView.Begin(cancellationToken =>
{
switch (trigger)
{
case "Assets_Extract_New_Tab":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath, true);
}
break;
case "Assets_Export_Data":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.ExportData(asset.FullPath);
}
break;
case "Assets_Save_Properties":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath);
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveProperty(false);
}
break;
case "Assets_Save_Texture":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath);
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveImage(false);
}
break;
}
});
}
}

View File

@ -4,46 +4,45 @@ using FModel.Framework;
using FModel.Services;
using FModel.Views.Resources.Controls;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class TabCommand : ViewModelCommand<TabItem>
{
public class TabCommand : ViewModelCommand<TabItem>
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public TabCommand(TabItem contextViewModel) : base(contextViewModel)
{
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
}
public TabCommand(TabItem contextViewModel) : base(contextViewModel)
public override void Execute(TabItem contextViewModel, object parameter)
{
switch (parameter)
{
}
public override void Execute(TabItem contextViewModel, object parameter)
{
switch (parameter)
{
case TabItem mdlClick:
_applicationView.CUE4Parse.TabControl.RemoveTab(mdlClick);
break;
case "Close_Tab":
_applicationView.CUE4Parse.TabControl.RemoveTab(contextViewModel);
break;
case "Close_All_Tabs":
_applicationView.CUE4Parse.TabControl.RemoveAllTabs();
break;
case "Close_Other_Tabs":
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel);
break;
case "Open_Properties":
if (contextViewModel.Header == "New Tab" || contextViewModel.Document == null) return;
Helper.OpenWindow<AdonisWindow>(contextViewModel.Header + " (Properties)", () =>
case TabItem mdlClick:
_applicationView.CUE4Parse.TabControl.RemoveTab(mdlClick);
break;
case "Close_Tab":
_applicationView.CUE4Parse.TabControl.RemoveTab(contextViewModel);
break;
case "Close_All_Tabs":
_applicationView.CUE4Parse.TabControl.RemoveAllTabs();
break;
case "Close_Other_Tabs":
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel);
break;
case "Open_Properties":
if (contextViewModel.Header == "New Tab" || contextViewModel.Document == null) return;
Helper.OpenWindow<AdonisWindow>(contextViewModel.Header + " (Properties)", () =>
{
new PropertiesPopout(contextViewModel)
{
new PropertiesPopout(contextViewModel)
{
Title = contextViewModel.Header + " (Properties)"
}.Show();
});
break;
case "Copy_Asset_Name":
Clipboard.SetText(contextViewModel.Header);
break;
}
Title = contextViewModel.Header + " (Properties)"
}.Show();
});
break;
case "Copy_Asset_Name":
Clipboard.SetText(contextViewModel.Header);
break;
}
}
}

View File

@ -9,163 +9,162 @@ using FModel.Framework;
using FModel.Settings;
using FModel.ViewModels.Commands;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class CustomDirectory : ViewModel
{
public class CustomDirectory : ViewModel
private string _header;
public string Header
{
private string _header;
public string Header
{
get => _header;
set => SetProperty(ref _header, value);
}
private string _directoryPath;
public string DirectoryPath
{
get => _directoryPath;
set => SetProperty(ref _directoryPath, value);
}
public CustomDirectory()
{
Header = string.Empty;
DirectoryPath = string.Empty;
}
public CustomDirectory(string header, string path)
{
Header = header;
DirectoryPath = path;
}
public override string ToString() => Header;
get => _header;
set => SetProperty(ref _header, value);
}
public class CustomDirectoriesViewModel : ViewModel
private string _directoryPath;
public string DirectoryPath
{
private GoToCommand _goToCommand;
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(this);
private AddEditDirectoryCommand _addEditDirectoryCommand;
public AddEditDirectoryCommand AddEditDirectoryCommand => _addEditDirectoryCommand ??= new AddEditDirectoryCommand(this);
private DeleteDirectoryCommand _deleteDirectoryCommand;
public DeleteDirectoryCommand DeleteDirectoryCommand => _deleteDirectoryCommand ??= new DeleteDirectoryCommand(this);
private readonly ObservableCollection<Control> _directories;
public ReadOnlyObservableCollection<Control> Directories { get; }
private readonly FGame _game;
private readonly string _gameDirectoryAtLaunch;
public CustomDirectoriesViewModel(FGame game, string directory)
{
_game = game;
_gameDirectoryAtLaunch = directory;
_directories = new ObservableCollection<Control>(EnumerateDirectories());
Directories = new ReadOnlyObservableCollection<Control>(_directories);
}
public int GetIndex(CustomDirectory dir)
{
return _directories.IndexOf(_directories.FirstOrDefault(x =>
x is MenuItem m && m.Header.ToString() == dir.Header && m.Tag.ToString() == dir.DirectoryPath));
}
public void Add(CustomDirectory dir)
{
_directories.Add(new MenuItem {Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir)});
}
public void Edit(int index, CustomDirectory newDir)
{
if (_directories.ElementAt(index) is not MenuItem dir) return;
dir.Header = newDir.Header;
dir.Tag = newDir.DirectoryPath;
}
public void Delete(int index)
{
_directories.RemoveAt(index);
}
public void Save()
{
var cd = new List<CustomDirectory>();
for (var i = 2; i < _directories.Count; i++)
{
if (_directories[i] is not MenuItem m) continue;
cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString()));
}
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch))
UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd;
else UserSettings.Default.CustomDirectories[_game] = cd;
}
private IEnumerable<Control> EnumerateDirectories()
{
yield return new MenuItem
{
Header = "Add Directory",
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/add_directory.png", UriKind.Relative))},
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = AddEditDirectoryCommand
};
yield return new Separator();
IList<CustomDirectory> cd;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameDirectoryAtLaunch, out var settings))
cd = settings.CustomDirectories;
else cd = UserSettings.Default.CustomDirectories[_game];
foreach (var setting in cd)
{
if (setting.DirectoryPath.EndsWith('/'))
setting.DirectoryPath = setting.DirectoryPath[..^1];
yield return new MenuItem
{
Header = setting.Header,
Tag = setting.DirectoryPath,
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
ItemsSource = EnumerateCommands(setting)
};
}
}
private IEnumerable<MenuItem> EnumerateCommands(CustomDirectory dir)
{
yield return new MenuItem
{
Header = "Go To",
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/go_to_directory.png", UriKind.Relative))},
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = GoToCommand,
CommandParameter = dir.DirectoryPath
};
yield return new MenuItem
{
Header = "Edit Directory",
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/edit.png", UriKind.Relative))},
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = AddEditDirectoryCommand,
CommandParameter = dir
};
yield return new MenuItem
{
Header = "Delete Directory",
StaysOpenOnClick = true,
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/delete.png", UriKind.Relative))},
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = DeleteDirectoryCommand,
CommandParameter = dir
};
}
get => _directoryPath;
set => SetProperty(ref _directoryPath, value);
}
public CustomDirectory()
{
Header = string.Empty;
DirectoryPath = string.Empty;
}
public CustomDirectory(string header, string path)
{
Header = header;
DirectoryPath = path;
}
public override string ToString() => Header;
}
public class CustomDirectoriesViewModel : ViewModel
{
private GoToCommand _goToCommand;
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(this);
private AddEditDirectoryCommand _addEditDirectoryCommand;
public AddEditDirectoryCommand AddEditDirectoryCommand => _addEditDirectoryCommand ??= new AddEditDirectoryCommand(this);
private DeleteDirectoryCommand _deleteDirectoryCommand;
public DeleteDirectoryCommand DeleteDirectoryCommand => _deleteDirectoryCommand ??= new DeleteDirectoryCommand(this);
private readonly ObservableCollection<Control> _directories;
public ReadOnlyObservableCollection<Control> Directories { get; }
private readonly FGame _game;
private readonly string _gameDirectoryAtLaunch;
public CustomDirectoriesViewModel(FGame game, string directory)
{
_game = game;
_gameDirectoryAtLaunch = directory;
_directories = new ObservableCollection<Control>(EnumerateDirectories());
Directories = new ReadOnlyObservableCollection<Control>(_directories);
}
public int GetIndex(CustomDirectory dir)
{
return _directories.IndexOf(_directories.FirstOrDefault(x =>
x is MenuItem m && m.Header.ToString() == dir.Header && m.Tag.ToString() == dir.DirectoryPath));
}
public void Add(CustomDirectory dir)
{
_directories.Add(new MenuItem { Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir) });
}
public void Edit(int index, CustomDirectory newDir)
{
if (_directories.ElementAt(index) is not MenuItem dir) return;
dir.Header = newDir.Header;
dir.Tag = newDir.DirectoryPath;
}
public void Delete(int index)
{
_directories.RemoveAt(index);
}
public void Save()
{
var cd = new List<CustomDirectory>();
for (var i = 2; i < _directories.Count; i++)
{
if (_directories[i] is not MenuItem m) continue;
cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString()));
}
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch))
UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd;
else UserSettings.Default.CustomDirectories[_game] = cd;
}
private IEnumerable<Control> EnumerateDirectories()
{
yield return new MenuItem
{
Header = "Add Directory",
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/add_directory.png", UriKind.Relative)) },
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = AddEditDirectoryCommand
};
yield return new Separator();
IList<CustomDirectory> cd;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameDirectoryAtLaunch, out var settings))
cd = settings.CustomDirectories;
else cd = UserSettings.Default.CustomDirectories[_game];
foreach (var setting in cd)
{
if (setting.DirectoryPath.EndsWith('/'))
setting.DirectoryPath = setting.DirectoryPath[..^1];
yield return new MenuItem
{
Header = setting.Header,
Tag = setting.DirectoryPath,
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
ItemsSource = EnumerateCommands(setting)
};
}
}
private IEnumerable<MenuItem> EnumerateCommands(CustomDirectory dir)
{
yield return new MenuItem
{
Header = "Go To",
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/go_to_directory.png", UriKind.Relative)) },
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = GoToCommand,
CommandParameter = dir.DirectoryPath
};
yield return new MenuItem
{
Header = "Edit Directory",
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/edit.png", UriKind.Relative)) },
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = AddEditDirectoryCommand,
CommandParameter = dir
};
yield return new MenuItem
{
Header = "Delete Directory",
StaysOpenOnClick = true,
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/delete.png", UriKind.Relative)) },
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = DeleteDirectoryCommand,
CommandParameter = dir
};
}
}

View File

@ -6,110 +6,109 @@ using System.Windows.Data;
using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.UE4.Vfs;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class FileItem : ViewModel
{
public class FileItem : ViewModel
private string _name;
public string Name
{
private string _name;
public string Name
{
get => _name;
private set => SetProperty(ref _name, value);
}
get => _name;
private set => SetProperty(ref _name, value);
}
private long _length;
public long Length
{
get => _length;
private set => SetProperty(ref _length, value);
}
private long _length;
public long Length
{
get => _length;
private set => SetProperty(ref _length, value);
}
private int _fileCount;
public int FileCount
{
get => _fileCount;
set => SetProperty(ref _fileCount, value);
}
private int _fileCount;
public int FileCount
{
get => _fileCount;
set => SetProperty(ref _fileCount, value);
}
private string _mountPoint;
public string MountPoint
{
get => _mountPoint;
set => SetProperty(ref _mountPoint, value);
}
private string _mountPoint;
public string MountPoint
{
get => _mountPoint;
set => SetProperty(ref _mountPoint, value);
}
private bool _isEncrypted;
public bool IsEncrypted
{
get => _isEncrypted;
set => SetProperty(ref _isEncrypted, value);
}
private bool _isEncrypted;
public bool IsEncrypted
{
get => _isEncrypted;
set => SetProperty(ref _isEncrypted, value);
}
private bool _isEnabled;
public bool IsEnabled
{
get => _isEnabled;
set => SetProperty(ref _isEnabled, value);
}
private bool _isEnabled;
public bool IsEnabled
{
get => _isEnabled;
set => SetProperty(ref _isEnabled, value);
}
private string _key;
public string Key
{
get => _key;
set => SetProperty(ref _key, value);
}
private string _key;
public string Key
{
get => _key;
set => SetProperty(ref _key, value);
}
private FGuid _guid;
public FGuid Guid
{
get => _guid;
set => SetProperty(ref _guid, value);
}
private FGuid _guid;
public FGuid Guid
{
get => _guid;
set => SetProperty(ref _guid, value);
}
public FileItem(string name, long length)
{
Name = name;
Length = length;
}
public FileItem(string name, long length)
{
Name = name;
Length = length;
}
public override string ToString()
public override string ToString()
{
return $"{Name} | {Key}";
}
}
public class GameDirectoryViewModel : ViewModel
{
public bool HasNoFile => DirectoryFiles.Count < 1;
public readonly ObservableCollection<FileItem> DirectoryFiles;
public ICollectionView DirectoryFilesView { get; }
public GameDirectoryViewModel()
{
DirectoryFiles = new ObservableCollection<FileItem>();
DirectoryFilesView = new ListCollectionView(DirectoryFiles) { SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending) } };
}
public void DeactivateAll()
{
foreach (var file in DirectoryFiles)
{
return $"{Name} | {Key}";
file.IsEnabled = false;
}
}
public class GameDirectoryViewModel : ViewModel
public void Add(IAesVfsReader reader)
{
public bool HasNoFile => DirectoryFiles.Count < 1;
public readonly ObservableCollection<FileItem> DirectoryFiles;
public ICollectionView DirectoryFilesView { get; }
public GameDirectoryViewModel()
Application.Current.Dispatcher.Invoke(() =>
{
DirectoryFiles = new ObservableCollection<FileItem>();
DirectoryFilesView = new ListCollectionView(DirectoryFiles) {SortDescriptions = {new SortDescription("Name", ListSortDirection.Ascending)}};
}
public void DeactivateAll()
{
foreach (var file in DirectoryFiles)
DirectoryFiles.Add(new FileItem(reader.Name, reader.Length)
{
file.IsEnabled = false;
}
}
public void Add(IAesVfsReader reader)
{
Application.Current.Dispatcher.Invoke(() =>
{
DirectoryFiles.Add(new FileItem(reader.Name, reader.Length)
{
Guid = reader.EncryptionKeyGuid,
IsEncrypted = reader.IsEncrypted,
IsEnabled = false,
Key = string.Empty
});
Guid = reader.EncryptionKeyGuid,
IsEncrypted = reader.IsEncrypted,
IsEnabled = false,
Key = string.Empty
});
}
});
}
}

View File

@ -14,356 +14,355 @@ using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
using Microsoft.Win32;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class GameSelectorViewModel : ViewModel
{
public class GameSelectorViewModel : ViewModel
public class DetectedGame
{
public class DetectedGame
public string GameName { get; set; }
public string GameDirectory { get; set; }
public bool IsManual { get; set; }
// the followings are only used when game is manually added
public AesResponse AesKeys { get; set; }
public EGame OverridedGame { get; set; }
public List<FCustomVersion> OverridedCustomVersions { get; set; }
public Dictionary<string, bool> OverridedOptions { get; set; }
public IList<CustomDirectory> CustomDirectories { get; set; }
}
private DetectedGame _selectedDetectedGame;
public DetectedGame SelectedDetectedGame
{
get => _selectedDetectedGame;
set => SetProperty(ref _selectedDetectedGame, value);
}
private readonly ObservableCollection<DetectedGame> _autoDetectedGames;
public ReadOnlyObservableCollection<DetectedGame> AutoDetectedGames { get; }
public GameSelectorViewModel(string gameDirectory)
{
_autoDetectedGames = new ObservableCollection<DetectedGame>(EnumerateDetectedGames().Where(x => x != null));
foreach (var game in UserSettings.Default.ManualGames.Values)
{
public string GameName { get; set; }
public string GameDirectory { get; set; }
public bool IsManual { get; set; }
// the followings are only used when game is manually added
public AesResponse AesKeys { get; set; }
public EGame OverridedGame { get; set; }
public List<FCustomVersion> OverridedCustomVersions { get; set; }
public Dictionary<string, bool> OverridedOptions { get; set; }
public IList<CustomDirectory> CustomDirectories { get; set; }
}
private DetectedGame _selectedDetectedGame;
public DetectedGame SelectedDetectedGame
{
get => _selectedDetectedGame;
set => SetProperty(ref _selectedDetectedGame, value);
}
private readonly ObservableCollection<DetectedGame> _autoDetectedGames;
public ReadOnlyObservableCollection<DetectedGame> AutoDetectedGames { get; }
public GameSelectorViewModel(string gameDirectory)
{
_autoDetectedGames = new ObservableCollection<DetectedGame>(EnumerateDetectedGames().Where(x => x != null));
foreach (var game in UserSettings.Default.ManualGames.Values)
{
_autoDetectedGames.Add(game);
}
AutoDetectedGames = new ReadOnlyObservableCollection<DetectedGame>(_autoDetectedGames);
if (AutoDetectedGames.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame)
SelectedDetectedGame = detectedGame;
else if (!string.IsNullOrEmpty(gameDirectory))
AddUnknownGame(gameDirectory);
else
SelectedDetectedGame = AutoDetectedGames.FirstOrDefault();
}
/// <summary>
/// dedicated to manual games
/// </summary>
public void AddUnknownGame(string gameName, string gameDirectory)
{
var game = new DetectedGame
{
GameName = gameName,
GameDirectory = gameDirectory,
IsManual = true,
AesKeys = null,
OverridedGame = EGame.GAME_UE4_LATEST,
OverridedCustomVersions = null,
OverridedOptions = null,
CustomDirectories = new List<CustomDirectory>()
};
UserSettings.Default.ManualGames[gameDirectory] = game;
_autoDetectedGames.Add(game);
SelectedDetectedGame = AutoDetectedGames.Last();
}
public void AddUnknownGame(string gameDirectory)
{
_autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory });
SelectedDetectedGame = AutoDetectedGames.Last();
}
AutoDetectedGames = new ReadOnlyObservableCollection<DetectedGame>(_autoDetectedGames);
public void DeleteSelectedGame()
{
UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem
_autoDetectedGames.Remove(SelectedDetectedGame);
SelectedDetectedGame = AutoDetectedGames.Last();
}
if (AutoDetectedGames.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame)
SelectedDetectedGame = detectedGame;
else if (!string.IsNullOrEmpty(gameDirectory))
AddUnknownGame(gameDirectory);
else
SelectedDetectedGame = AutoDetectedGames.FirstOrDefault();
}
private IEnumerable<DetectedGame> EnumerateDetectedGames()
/// <summary>
/// dedicated to manual games
/// </summary>
public void AddUnknownGame(string gameName, string gameDirectory)
{
var game = new DetectedGame
{
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks");
yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER };
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks");
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks");
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks");
yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks");
yield return GetUnrealEngineGame("WorldExplorersLive", "\\WorldExplorers\\Content");
yield return GetUnrealEngineGame("Newt", "\\g3\\Content\\Paks");
yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks");
yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks");
yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks");
yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content");
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks");
yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER };
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks");
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks");
}
GameName = gameName,
GameDirectory = gameDirectory,
IsManual = true,
AesKeys = null,
OverridedGame = EGame.GAME_UE4_LATEST,
OverridedCustomVersions = null,
OverridedOptions = null,
CustomDirectories = new List<CustomDirectory>()
};
private LauncherInstalled _launcherInstalled;
private DetectedGame GetUnrealEngineGame(string gameName, string pakDirectory)
UserSettings.Default.ManualGames[gameDirectory] = game;
_autoDetectedGames.Add(game);
SelectedDetectedGame = AutoDetectedGames.Last();
}
public void AddUnknownGame(string gameDirectory)
{
_autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory });
SelectedDetectedGame = AutoDetectedGames.Last();
}
public void DeleteSelectedGame()
{
UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem
_autoDetectedGames.Remove(SelectedDetectedGame);
SelectedDetectedGame = AutoDetectedGames.Last();
}
private IEnumerable<DetectedGame> EnumerateDetectedGames()
{
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks");
yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER };
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks");
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks");
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks");
yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks");
yield return GetUnrealEngineGame("WorldExplorersLive", "\\WorldExplorers\\Content");
yield return GetUnrealEngineGame("Newt", "\\g3\\Content\\Paks");
yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks");
yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks");
yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks");
yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content");
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks");
yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER };
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks");
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks");
}
private LauncherInstalled _launcherInstalled;
private DetectedGame GetUnrealEngineGame(string gameName, string pakDirectory)
{
_launcherInstalled ??= GetDriveLauncherInstalls<LauncherInstalled>("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat");
if (_launcherInstalled?.InstallationList != null)
{
_launcherInstalled ??= GetDriveLauncherInstalls<LauncherInstalled>("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat");
if (_launcherInstalled?.InstallationList != null)
foreach (var installationList in _launcherInstalled.InstallationList)
{
foreach (var installationList in _launcherInstalled.InstallationList)
{
if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase))
return new DetectedGame { GameName = installationList.AppName, GameDirectory = $"{installationList.InstallLocation}{pakDirectory}" };
}
if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase))
return new DetectedGame { GameName = installationList.AppName, GameDirectory = $"{installationList.InstallLocation}{pakDirectory}" };
}
Log.Warning("Could not find {GameName} in LauncherInstalled.dat", gameName);
return null;
}
private RiotClientInstalls _riotClientInstalls;
private DetectedGame GetRiotGame(string gameName, string pakDirectory)
Log.Warning("Could not find {GameName} in LauncherInstalled.dat", gameName);
return null;
}
private RiotClientInstalls _riotClientInstalls;
private DetectedGame GetRiotGame(string gameName, string pakDirectory)
{
_riotClientInstalls ??= GetDriveLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json");
if (_riotClientInstalls is { AssociatedClient: { } })
{
_riotClientInstalls ??= GetDriveLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json");
if (_riotClientInstalls is { AssociatedClient: { } })
foreach (var (key, _) in _riotClientInstalls.AssociatedClient)
{
foreach (var (key, _) in _riotClientInstalls.AssociatedClient)
{
if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase))
return new DetectedGame { GameName = gameName, GameDirectory = $"{key.Replace('/', '\\')}{pakDirectory}" };
}
if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase))
return new DetectedGame { GameName = gameName, GameDirectory = $"{key.Replace('/', '\\')}{pakDirectory}" };
}
Log.Warning("Could not find {GameName} in RiotClientInstalls.json", gameName);
return null;
}
private LauncherSettings _launcherSettings;
private DetectedGame GetMojangGame(string gameName, string pakDirectory)
Log.Warning("Could not find {GameName} in RiotClientInstalls.json", gameName);
return null;
}
private LauncherSettings _launcherSettings;
private DetectedGame GetMojangGame(string gameName, string pakDirectory)
{
_launcherSettings ??= GetDataLauncherInstalls<LauncherSettings>("\\.minecraft\\launcher_settings.json");
if (_launcherSettings is { ProductLibraryDir: { } })
return new DetectedGame { GameName = gameName, GameDirectory = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}" };
Log.Warning("Could not find {GameName} in launcher_settings.json", gameName);
return null;
}
private DetectedGame GetSteamGame(int id, string pakDirectory)
{
var steamInfo = SteamDetection.GetSteamGameById(id);
if (steamInfo is not null)
return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" };
Log.Warning("Could not find {GameId} in steam manifests", id);
return null;
}
private DetectedGame GetRockstarGamesGame(string key, string pakDirectory)
{
var installLocation = string.Empty;
try
{
_launcherSettings ??= GetDataLauncherInstalls<LauncherSettings>("\\.minecraft\\launcher_settings.json");
if (_launcherSettings is { ProductLibraryDir: { } })
return new DetectedGame { GameName = gameName, GameDirectory = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}" };
Log.Warning("Could not find {GameName} in launcher_settings.json", gameName);
return null;
installLocation = App.GetRegistryValue(@$"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{key}", "InstallLocation", RegistryHive.LocalMachine);
}
private DetectedGame GetSteamGame(int id, string pakDirectory)
catch
{
var steamInfo = SteamDetection.GetSteamGameById(id);
if (steamInfo is not null)
return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" };
Log.Warning("Could not find {GameId} in steam manifests", id);
return null;
// ignored
}
private DetectedGame GetRockstarGamesGame(string key, string pakDirectory)
if (!string.IsNullOrEmpty(installLocation))
return new DetectedGame { GameName = key, GameDirectory = $"{installLocation}{pakDirectory}" };
Log.Warning("Could not find {GameName} in the registry", key);
return null;
}
private T GetDriveLauncherInstalls<T>(string jsonFile)
{
foreach (var drive in DriveInfo.GetDrives())
{
var installLocation = string.Empty;
try
{
installLocation = App.GetRegistryValue(@$"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{key}", "InstallLocation", RegistryHive.LocalMachine);
}
catch
{
// ignored
}
var launcher = $"{drive.Name}{jsonFile}";
if (!File.Exists(launcher)) continue;
if (!string.IsNullOrEmpty(installLocation))
return new DetectedGame { GameName = key, GameDirectory = $"{installLocation}{pakDirectory}" };
Log.Warning("Could not find {GameName} in the registry", key);
return null;
Log.Information("\"{Launcher}\" found in drive \"{DriveName}\"", launcher, drive.Name);
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
}
private T GetDriveLauncherInstalls<T>(string jsonFile)
Log.Warning("\"{JsonFile}\" not found in any drives", jsonFile);
return default;
}
private T GetDataLauncherInstalls<T>(string jsonFile)
{
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var launcher = $"{appData}{jsonFile}";
if (File.Exists(launcher))
{
foreach (var drive in DriveInfo.GetDrives())
{
var launcher = $"{drive.Name}{jsonFile}";
if (!File.Exists(launcher)) continue;
Log.Information("\"{Launcher}\" found in drive \"{DriveName}\"", launcher, drive.Name);
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
}
Log.Warning("\"{JsonFile}\" not found in any drives", jsonFile);
return default;
Log.Information("\"{Launcher}\" found in \"{AppData}\"", launcher, appData);
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
}
private T GetDataLauncherInstalls<T>(string jsonFile)
{
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var launcher = $"{appData}{jsonFile}";
if (File.Exists(launcher))
{
Log.Information("\"{Launcher}\" found in \"{AppData}\"", launcher, appData);
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
}
Log.Warning("\"{Json}\" not found anywhere", jsonFile);
return default;
}
Log.Warning("\"{Json}\" not found anywhere", jsonFile);
return default;
}
#pragma warning disable 649
private class LauncherInstalled
{
public Installation[] InstallationList;
}
private class LauncherInstalled
{
public Installation[] InstallationList;
}
private class Installation
{
public string InstallLocation;
public string AppName;
public string AppVersion;
}
private class Installation
{
public string InstallLocation;
public string AppName;
public string AppVersion;
}
private class RiotClientInstalls
{
[JsonProperty("associated_client", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> AssociatedClient;
private class RiotClientInstalls
{
[JsonProperty("associated_client", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> AssociatedClient;
[JsonProperty("patchlines", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> Patchlines;
[JsonProperty("patchlines", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> Patchlines;
[JsonProperty("rc_default", NullValueHandling = NullValueHandling.Ignore)]
public string RcDefault;
[JsonProperty("rc_default", NullValueHandling = NullValueHandling.Ignore)]
public string RcDefault;
[JsonProperty("rc_live", NullValueHandling = NullValueHandling.Ignore)]
public string RcLive;
}
[JsonProperty("rc_live", NullValueHandling = NullValueHandling.Ignore)]
public string RcLive;
}
private class LauncherSettings
{
[JsonProperty("channel", NullValueHandling = NullValueHandling.Ignore)]
public string Channel;
private class LauncherSettings
{
[JsonProperty("channel", NullValueHandling = NullValueHandling.Ignore)]
public string Channel;
[JsonProperty("customChannels", NullValueHandling = NullValueHandling.Ignore)]
public object[] CustomChannels;
[JsonProperty("customChannels", NullValueHandling = NullValueHandling.Ignore)]
public object[] CustomChannels;
[JsonProperty("deviceId", NullValueHandling = NullValueHandling.Ignore)]
public string DeviceId;
[JsonProperty("deviceId", NullValueHandling = NullValueHandling.Ignore)]
public string DeviceId;
[JsonProperty("formatVersion", NullValueHandling = NullValueHandling.Ignore)]
public int FormatVersion;
[JsonProperty("formatVersion", NullValueHandling = NullValueHandling.Ignore)]
public int FormatVersion;
[JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
public string Locale;
[JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
public string Locale;
[JsonProperty("productLibraryDir", NullValueHandling = NullValueHandling.Ignore)]
public string ProductLibraryDir;
}
[JsonProperty("productLibraryDir", NullValueHandling = NullValueHandling.Ignore)]
public string ProductLibraryDir;
}
#pragma warning restore 649
// https://stackoverflow.com/questions/54767662/finding-game-launcher-executables-in-directory-c-sharp/67679123#67679123
public static class SteamDetection
// https://stackoverflow.com/questions/54767662/finding-game-launcher-executables-in-directory-c-sharp/67679123#67679123
public static class SteamDetection
{
private static readonly List<AppInfo> _steamApps;
static SteamDetection()
{
private static readonly List<AppInfo> _steamApps;
_steamApps = GetSteamApps(GetSteamLibs());
}
static SteamDetection()
public static AppInfo GetSteamGameById(int id) => _steamApps.FirstOrDefault(app => app.Id == id.ToString());
private static List<AppInfo> GetSteamApps(IEnumerable<string> steamLibs)
{
var apps = new List<AppInfo>();
foreach (var files in steamLibs.Select(lib => Path.Combine(lib, "SteamApps")).Select(appMetaDataPath => Directory.GetFiles(appMetaDataPath, "*.acf")))
{
_steamApps = GetSteamApps(GetSteamLibs());
apps.AddRange(files.Select(GetAppInfo).Where(appInfo => appInfo != null));
}
public static AppInfo GetSteamGameById(int id) => _steamApps.FirstOrDefault(app => app.Id == id.ToString());
return apps;
}
private static List<AppInfo> GetSteamApps(IEnumerable<string> steamLibs)
private static AppInfo GetAppInfo(string appMetaFile)
{
var fileDataLines = File.ReadAllLines(appMetaFile);
var dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var line in fileDataLines)
{
var apps = new List<AppInfo>();
foreach (var files in steamLibs.Select(lib => Path.Combine(lib, "SteamApps")).Select(appMetaDataPath => Directory.GetFiles(appMetaDataPath, "*.acf")))
{
apps.AddRange(files.Select(GetAppInfo).Where(appInfo => appInfo != null));
}
return apps;
var match = Regex.Match(line, @"\s*""(?<key>\w+)""\s+""(?<val>.*)""");
if (!match.Success) continue;
var key = match.Groups["key"].Value;
var val = match.Groups["val"].Value;
dic[key] = val;
}
private static AppInfo GetAppInfo(string appMetaFile)
if (dic.Keys.Count <= 0) return null;
AppInfo appInfo = new();
var appId = dic["appid"];
var name = dic["name"];
var installDir = dic["installDir"];
var path = Path.GetDirectoryName(appMetaFile);
var libGameRoot = Path.Combine(path, "common", installDir);
if (!Directory.Exists(libGameRoot)) return null;
appInfo.Id = appId;
appInfo.Name = name;
appInfo.GameRoot = libGameRoot;
return appInfo;
}
private static List<string> GetSteamLibs()
{
var steamPath = GetSteamPath();
if (steamPath == null) return new List<string>();
var libraries = new List<string> { steamPath };
var listFile = Path.Combine(steamPath, @"steamapps\libraryfolders.vdf");
if (!File.Exists(listFile)) return new List<string>();
var lines = File.ReadAllLines(listFile);
foreach (var line in lines)
{
var fileDataLines = File.ReadAllLines(appMetaFile);
var dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var line in fileDataLines)
var match = Regex.Match(line, @"""(?<path>\w:\\\\.*)""");
if (!match.Success) continue;
var path = match.Groups["path"].Value.Replace(@"\\", @"\");
if (Directory.Exists(path))
{
var match = Regex.Match(line, @"\s*""(?<key>\w+)""\s+""(?<val>.*)""");
if (!match.Success) continue;
var key = match.Groups["key"].Value;
var val = match.Groups["val"].Value;
dic[key] = val;
libraries.Add(path);
}
if (dic.Keys.Count <= 0) return null;
AppInfo appInfo = new();
var appId = dic["appid"];
var name = dic["name"];
var installDir = dic["installDir"];
var path = Path.GetDirectoryName(appMetaFile);
var libGameRoot = Path.Combine(path, "common", installDir);
if (!Directory.Exists(libGameRoot)) return null;
appInfo.Id = appId;
appInfo.Name = name;
appInfo.GameRoot = libGameRoot;
return appInfo;
}
private static List<string> GetSteamLibs()
return libraries;
}
private static string GetSteamPath() => (string) Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath", ""); // Win64, we don't support Win32
public class AppInfo
{
public string Id { get; internal set; }
public string Name { get; internal set; }
public string GameRoot { get; internal set; }
public override string ToString()
{
var steamPath = GetSteamPath();
if (steamPath == null) return new List<string>();
var libraries = new List<string> { steamPath };
var listFile = Path.Combine(steamPath, @"steamapps\libraryfolders.vdf");
if (!File.Exists(listFile)) return new List<string>();
var lines = File.ReadAllLines(listFile);
foreach (var line in lines)
{
var match = Regex.Match(line, @"""(?<path>\w:\\\\.*)""");
if (!match.Success) continue;
var path = match.Groups["path"].Value.Replace(@"\\", @"\");
if (Directory.Exists(path))
{
libraries.Add(path);
}
}
return libraries;
}
private static string GetSteamPath() => (string) Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath", ""); // Win64, we don't support Win32
public class AppInfo
{
public string Id { get; internal set; }
public string Name { get; internal set; }
public string GameRoot { get; internal set; }
public override string ToString()
{
return $"{Name} ({Id})";
}
return $"{Name} ({Id})";
}
}
}
}
}

View File

@ -4,20 +4,19 @@ using System.Collections.ObjectModel;
using FModel.Framework;
using FModel.ViewModels.Commands;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class LoadingModesViewModel : ViewModel
{
public class LoadingModesViewModel : ViewModel
private LoadCommand _loadCommand;
public LoadCommand LoadCommand => _loadCommand ??= new LoadCommand(this);
public ReadOnlyObservableCollection<ELoadingMode> Modes { get; }
public LoadingModesViewModel()
{
private LoadCommand _loadCommand;
public LoadCommand LoadCommand => _loadCommand ??= new LoadCommand(this);
public ReadOnlyObservableCollection<ELoadingMode> Modes { get; }
public LoadingModesViewModel()
{
Modes = new ReadOnlyObservableCollection<ELoadingMode>(new ObservableCollection<ELoadingMode>(EnumerateLoadingModes()));
}
private IEnumerable<ELoadingMode> EnumerateLoadingModes() => Enum.GetValues<ELoadingMode>();
Modes = new ReadOnlyObservableCollection<ELoadingMode>(new ObservableCollection<ELoadingMode>(EnumerateLoadingModes()));
}
}
private IEnumerable<ELoadingMode> EnumerateLoadingModes() => Enum.GetValues<ELoadingMode>();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,61 +6,59 @@ using System.Text.RegularExpressions;
using System.Windows.Data;
using FModel.Framework;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class SearchViewModel : ViewModel
{
public class SearchViewModel : ViewModel
private string _filterText;
public string FilterText
{
private string _filterText;
public string FilterText
{
get => _filterText;
set => SetProperty(ref _filterText, value);
}
get => _filterText;
set => SetProperty(ref _filterText, value);
}
private bool _hasRegexEnabled;
public bool HasRegexEnabled
{
get => _hasRegexEnabled;
set => SetProperty(ref _hasRegexEnabled, value);
}
private bool _hasRegexEnabled;
public bool HasRegexEnabled
{
get => _hasRegexEnabled;
set => SetProperty(ref _hasRegexEnabled, value);
}
private bool _hasMatchCaseEnabled;
public bool HasMatchCaseEnabled
{
get => _hasMatchCaseEnabled;
set => SetProperty(ref _hasMatchCaseEnabled, value);
}
private bool _hasMatchCaseEnabled;
public bool HasMatchCaseEnabled
{
get => _hasMatchCaseEnabled;
set => SetProperty(ref _hasMatchCaseEnabled, value);
}
public int ResultsCount => SearchResults?.Count ?? 0;
public RangeObservableCollection<AssetItem> SearchResults { get; }
public ICollectionView SearchResultsView { get; }
public int ResultsCount => SearchResults?.Count ?? 0;
public RangeObservableCollection<AssetItem> SearchResults { get; }
public ICollectionView SearchResultsView { get; }
public SearchViewModel()
{
SearchResults = new RangeObservableCollection<AssetItem>();
SearchResultsView = new ListCollectionView(SearchResults);
}
public SearchViewModel()
{
SearchResults = new RangeObservableCollection<AssetItem>();
SearchResultsView = new ListCollectionView(SearchResults);
}
public void RefreshFilter()
{
if (SearchResultsView.Filter == null)
SearchResultsView.Filter = e => ItemFilter(e, FilterText.Trim().Split(' '));
else
SearchResultsView.Refresh();
}
public void RefreshFilter()
{
if (SearchResultsView.Filter == null)
SearchResultsView.Filter = e => ItemFilter(e, FilterText.Trim().Split(' '));
else
SearchResultsView.Refresh();
}
private bool ItemFilter(object item, IEnumerable<string> filters)
{
if (item is not AssetItem assetItem)
return true;
private bool ItemFilter(object item, IEnumerable<string> filters)
{
if (item is not AssetItem assetItem)
return true;
if (!HasRegexEnabled)
return filters.All(x => assetItem.FullPath.IndexOf(x,
HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) >= 0);
if (!HasRegexEnabled)
return filters.All(x => assetItem.FullPath.Contains(x, HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
var o = RegexOptions.None;
if (HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase;
return new Regex(FilterText, o).Match(assetItem.FullPath).Success;
}
var o = RegexOptions.None;
if (HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase;
return new Regex(FilterText, o).Match(assetItem.FullPath).Success;
}
}

View File

@ -14,323 +14,324 @@ using FModel.Services;
using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class SettingsViewModel : ViewModel
{
public class SettingsViewModel : ViewModel
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler;
private EUpdateMode _selectedUpdateMode;
public EUpdateMode SelectedUpdateMode
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler;
private EUpdateMode _selectedUpdateMode;
public EUpdateMode SelectedUpdateMode
{
get => _selectedUpdateMode;
set => SetProperty(ref _selectedUpdateMode, value);
}
private string _selectedPreset;
public string SelectedPreset
{
get => _selectedPreset;
set
{
SetProperty(ref _selectedPreset, value);
RaisePropertyChanged("EnableElements");
}
}
private ETexturePlatform _selectedUePlatform;
public ETexturePlatform SelectedUePlatform
{
get => _selectedUePlatform;
set => SetProperty(ref _selectedUePlatform, value);
}
private EGame _selectedUeGame;
public EGame SelectedUeGame
{
get => _selectedUeGame;
set => SetProperty(ref _selectedUeGame, value);
}
private List<FCustomVersion> _selectedCustomVersions;
public List<FCustomVersion> SelectedCustomVersions
{
get => _selectedCustomVersions;
set => SetProperty(ref _selectedCustomVersions, value);
}
private Dictionary<string, bool> _selectedOptions;
public Dictionary<string, bool> SelectedOptions
{
get => _selectedOptions;
set => SetProperty(ref _selectedOptions, value);
}
private ELanguage _selectedAssetLanguage;
public ELanguage SelectedAssetLanguage
{
get => _selectedAssetLanguage;
set => SetProperty(ref _selectedAssetLanguage, value);
}
private EAesReload _selectedAesReload;
public EAesReload SelectedAesReload
{
get => _selectedAesReload;
set => SetProperty(ref _selectedAesReload, value);
}
private EDiscordRpc _selectedDiscordRpc;
public EDiscordRpc SelectedDiscordRpc
{
get => _selectedDiscordRpc;
set => SetProperty(ref _selectedDiscordRpc, value);
}
private ECompressedAudio _selectedCompressedAudio;
public ECompressedAudio SelectedCompressedAudio
{
get => _selectedCompressedAudio;
set => SetProperty(ref _selectedCompressedAudio, value);
}
private EIconStyle _selectedCosmeticStyle;
public EIconStyle SelectedCosmeticStyle
{
get => _selectedCosmeticStyle;
set => SetProperty(ref _selectedCosmeticStyle, value);
}
private EMeshFormat _selectedMeshExportFormat;
public EMeshFormat SelectedMeshExportFormat
{
get => _selectedMeshExportFormat;
set => SetProperty(ref _selectedMeshExportFormat, value);
}
private ELodFormat _selectedLodExportFormat;
public ELodFormat SelectedLodExportFormat
{
get => _selectedLodExportFormat;
set => SetProperty(ref _selectedLodExportFormat, value);
}
private ETextureFormat _selectedTextureExportFormat;
public ETextureFormat SelectedTextureExportFormat
{
get => _selectedTextureExportFormat;
set => SetProperty(ref _selectedTextureExportFormat, value);
}
public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; }
public ObservableCollection<string> Presets { get; private set; }
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
public ReadOnlyObservableCollection<EDiscordRpc> DiscordRpcs { get; private set; }
public ReadOnlyObservableCollection<ECompressedAudio> CompressedAudios { get; private set; }
public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; }
public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { get; private set; }
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER;
private readonly FGame _game;
private Game _gamePreset;
private string _outputSnapshot;
private string _rawDataSnapshot;
private string _propertiesSnapshot;
private string _textureSnapshot;
private string _audioSnapshot;
private string _modelSnapshot;
private string _gameSnapshot;
private EUpdateMode _updateModeSnapshot;
private string _presetSnapshot;
private ETexturePlatform _uePlatformSnapshot;
private EGame _ueGameSnapshot;
private List<FCustomVersion> _customVersionsSnapshot;
private Dictionary<string, bool> _optionsSnapshot;
private ELanguage _assetLanguageSnapshot;
private ECompressedAudio _compressedAudioSnapshot;
private EIconStyle _cosmeticStyleSnapshot;
private EMeshFormat _meshExportFormatSnapshot;
private ELodFormat _lodExportFormatSnapshot;
private ETextureFormat _textureExportFormatSnapshot;
public SettingsViewModel(FGame game)
{
_game = game;
}
public void Initialize()
{
_outputSnapshot = UserSettings.Default.OutputDirectory;
_rawDataSnapshot = UserSettings.Default.RawDataDirectory;
_propertiesSnapshot = UserSettings.Default.PropertiesDirectory;
_textureSnapshot = UserSettings.Default.TextureDirectory;
_audioSnapshot = UserSettings.Default.AudioDirectory;
_modelSnapshot = UserSettings.Default.ModelDirectory;
_gameSnapshot = UserSettings.Default.GameDirectory;
_updateModeSnapshot = UserSettings.Default.UpdateMode;
_presetSnapshot = UserSettings.Default.Presets[_game];
_uePlatformSnapshot = UserSettings.Default.OverridedPlatform;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings))
{
_ueGameSnapshot = settings.OverridedGame;
_customVersionsSnapshot = settings.OverridedCustomVersions;
_optionsSnapshot = settings.OverridedOptions;
}
else
{
_ueGameSnapshot = UserSettings.Default.OverridedGame[_game];
_customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game];
_optionsSnapshot = UserSettings.Default.OverridedOptions[_game];
}
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
SelectedUpdateMode = _updateModeSnapshot;
SelectedPreset = _presetSnapshot;
SelectedUePlatform = _uePlatformSnapshot;
SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
SelectedAssetLanguage = _assetLanguageSnapshot;
SelectedCompressedAudio = _compressedAudioSnapshot;
SelectedCosmeticStyle = _cosmeticStyleSnapshot;
SelectedMeshExportFormat = _meshExportFormatSnapshot;
SelectedLodExportFormat = _lodExportFormatSnapshot;
SelectedTextureExportFormat = _textureExportFormatSnapshot;
SelectedAesReload = UserSettings.Default.AesReload;
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
Presets = new ObservableCollection<string>(EnumeratePresets());
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
DiscordRpcs = new ReadOnlyObservableCollection<EDiscordRpc>(new ObservableCollection<EDiscordRpc>(EnumerateDiscordRpcs()));
CompressedAudios = new ReadOnlyObservableCollection<ECompressedAudio>(new ObservableCollection<ECompressedAudio>(EnumerateCompressedAudios()));
CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles()));
MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat()));
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
}
public async Task InitPresets(string gameName)
{
await _threadWorkerView.Begin(cancellationToken =>
{
if (string.IsNullOrEmpty(gameName)) return;
_gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName);
});
if (_gamePreset?.Versions == null) return;
foreach (var version in _gamePreset.Versions.Keys)
{
Presets.Add(version);
}
}
public void SwitchPreset(string key)
{
if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return;
SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST);
SelectedCustomVersions = new List<FCustomVersion>();
foreach (var (guid, v) in version.CustomVersions)
{
SelectedCustomVersions.Add(new FCustomVersion {Key = new FGuid(guid), Version = v});
}
SelectedOptions = new Dictionary<string, bool>();
foreach (var (k, v) in version.Options)
{
SelectedOptions[k] = v;
}
}
public void ResetPreset()
{
SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
}
public SettingsOut Save()
{
var ret = SettingsOut.Nothing;
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
_outputSnapshot != UserSettings.Default.OutputDirectory || // textbox
_rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox
_propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox
_textureSnapshot != UserSettings.Default.TextureDirectory || // textbox
_audioSnapshot != UserSettings.Default.AudioDirectory || // textbox
_modelSnapshot != UserSettings.Default.ModelDirectory || // textbox
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
ret = SettingsOut.Restart;
if (_assetLanguageSnapshot != SelectedAssetLanguage)
ret = SettingsOut.ReloadLocres;
if (_updateModeSnapshot != SelectedUpdateMode)
ret = SettingsOut.CheckForUpdates;
UserSettings.Default.UpdateMode = SelectedUpdateMode;
UserSettings.Default.Presets[_game] = SelectedPreset;
UserSettings.Default.OverridedPlatform = SelectedUePlatform;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
{
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions;
}
else
{
UserSettings.Default.OverridedGame[_game] = SelectedUeGame;
UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions;
UserSettings.Default.OverridedOptions[_game] = SelectedOptions;
}
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle;
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
UserSettings.Default.AesReload = SelectedAesReload;
UserSettings.Default.DiscordRpc = SelectedDiscordRpc;
if (SelectedDiscordRpc == EDiscordRpc.Never)
_discordHandler.Shutdown();
return ret;
}
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
private IEnumerable<string> EnumeratePresets()
{
yield return Constants._NO_PRESET_TRIGGER;
}
private IEnumerable<EGame> EnumerateUeGames() => Enum.GetValues<EGame>();
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
private IEnumerable<ECompressedAudio> EnumerateCompressedAudios() => Enum.GetValues<ECompressedAudio>();
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues<EIconStyle>();
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues<EMeshFormat>();
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
private IEnumerable<ETexturePlatform> EnumerateUePlatforms() => Enum.GetValues<ETexturePlatform>();
get => _selectedUpdateMode;
set => SetProperty(ref _selectedUpdateMode, value);
}
}
private string _selectedPreset;
public string SelectedPreset
{
get => _selectedPreset;
set
{
SetProperty(ref _selectedPreset, value);
RaisePropertyChanged("EnableElements");
}
}
private ETexturePlatform _selectedUePlatform;
public ETexturePlatform SelectedUePlatform
{
get => _selectedUePlatform;
set => SetProperty(ref _selectedUePlatform, value);
}
private EGame _selectedUeGame;
public EGame SelectedUeGame
{
get => _selectedUeGame;
set => SetProperty(ref _selectedUeGame, value);
}
private List<FCustomVersion> _selectedCustomVersions;
public List<FCustomVersion> SelectedCustomVersions
{
get => _selectedCustomVersions;
set => SetProperty(ref _selectedCustomVersions, value);
}
private Dictionary<string, bool> _selectedOptions;
public Dictionary<string, bool> SelectedOptions
{
get => _selectedOptions;
set => SetProperty(ref _selectedOptions, value);
}
private ELanguage _selectedAssetLanguage;
public ELanguage SelectedAssetLanguage
{
get => _selectedAssetLanguage;
set => SetProperty(ref _selectedAssetLanguage, value);
}
private EAesReload _selectedAesReload;
public EAesReload SelectedAesReload
{
get => _selectedAesReload;
set => SetProperty(ref _selectedAesReload, value);
}
private EDiscordRpc _selectedDiscordRpc;
public EDiscordRpc SelectedDiscordRpc
{
get => _selectedDiscordRpc;
set => SetProperty(ref _selectedDiscordRpc, value);
}
private ECompressedAudio _selectedCompressedAudio;
public ECompressedAudio SelectedCompressedAudio
{
get => _selectedCompressedAudio;
set => SetProperty(ref _selectedCompressedAudio, value);
}
private EIconStyle _selectedCosmeticStyle;
public EIconStyle SelectedCosmeticStyle
{
get => _selectedCosmeticStyle;
set => SetProperty(ref _selectedCosmeticStyle, value);
}
private EMeshFormat _selectedMeshExportFormat;
public EMeshFormat SelectedMeshExportFormat
{
get => _selectedMeshExportFormat;
set => SetProperty(ref _selectedMeshExportFormat, value);
}
private ELodFormat _selectedLodExportFormat;
public ELodFormat SelectedLodExportFormat
{
get => _selectedLodExportFormat;
set => SetProperty(ref _selectedLodExportFormat, value);
}
private ETextureFormat _selectedTextureExportFormat;
public ETextureFormat SelectedTextureExportFormat
{
get => _selectedTextureExportFormat;
set => SetProperty(ref _selectedTextureExportFormat, value);
}
public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; }
public ObservableCollection<string> Presets { get; private set; }
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
public ReadOnlyObservableCollection<EDiscordRpc> DiscordRpcs { get; private set; }
public ReadOnlyObservableCollection<ECompressedAudio> CompressedAudios { get; private set; }
public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; }
public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { get; private set; }
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER;
private readonly FGame _game;
private Game _gamePreset;
private string _outputSnapshot;
private string _rawDataSnapshot;
private string _propertiesSnapshot;
private string _textureSnapshot;
private string _audioSnapshot;
private string _modelSnapshot;
private string _gameSnapshot;
private EUpdateMode _updateModeSnapshot;
private string _presetSnapshot;
private ETexturePlatform _uePlatformSnapshot;
private EGame _ueGameSnapshot;
private List<FCustomVersion> _customVersionsSnapshot;
private Dictionary<string, bool> _optionsSnapshot;
private ELanguage _assetLanguageSnapshot;
private ECompressedAudio _compressedAudioSnapshot;
private EIconStyle _cosmeticStyleSnapshot;
private EMeshFormat _meshExportFormatSnapshot;
private ELodFormat _lodExportFormatSnapshot;
private ETextureFormat _textureExportFormatSnapshot;
public SettingsViewModel(FGame game)
{
_game = game;
}
public void Initialize()
{
_outputSnapshot = UserSettings.Default.OutputDirectory;
_rawDataSnapshot = UserSettings.Default.RawDataDirectory;
_propertiesSnapshot = UserSettings.Default.PropertiesDirectory;
_textureSnapshot = UserSettings.Default.TextureDirectory;
_audioSnapshot = UserSettings.Default.AudioDirectory;
_modelSnapshot = UserSettings.Default.ModelDirectory;
_gameSnapshot = UserSettings.Default.GameDirectory;
_updateModeSnapshot = UserSettings.Default.UpdateMode;
_presetSnapshot = UserSettings.Default.Presets[_game];
_uePlatformSnapshot = UserSettings.Default.OverridedPlatform;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings))
{
_ueGameSnapshot = settings.OverridedGame;
_customVersionsSnapshot = settings.OverridedCustomVersions;
_optionsSnapshot = settings.OverridedOptions;
}
else
{
_ueGameSnapshot = UserSettings.Default.OverridedGame[_game];
_customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game];
_optionsSnapshot = UserSettings.Default.OverridedOptions[_game];
}
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
SelectedUpdateMode = _updateModeSnapshot;
SelectedPreset = _presetSnapshot;
SelectedUePlatform = _uePlatformSnapshot;
SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
SelectedAssetLanguage = _assetLanguageSnapshot;
SelectedCompressedAudio = _compressedAudioSnapshot;
SelectedCosmeticStyle = _cosmeticStyleSnapshot;
SelectedMeshExportFormat = _meshExportFormatSnapshot;
SelectedLodExportFormat = _lodExportFormatSnapshot;
SelectedTextureExportFormat = _textureExportFormatSnapshot;
SelectedAesReload = UserSettings.Default.AesReload;
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
Presets = new ObservableCollection<string>(EnumeratePresets());
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
DiscordRpcs = new ReadOnlyObservableCollection<EDiscordRpc>(new ObservableCollection<EDiscordRpc>(EnumerateDiscordRpcs()));
CompressedAudios = new ReadOnlyObservableCollection<ECompressedAudio>(new ObservableCollection<ECompressedAudio>(EnumerateCompressedAudios()));
CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles()));
MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat()));
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
}
public async Task InitPresets(string gameName)
{
await _threadWorkerView.Begin(cancellationToken =>
{
if (string.IsNullOrEmpty(gameName)) return;
_gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName);
});
if (_gamePreset?.Versions == null) return;
foreach (var version in _gamePreset.Versions.Keys)
{
Presets.Add(version);
}
}
public void SwitchPreset(string key)
{
if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return;
SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST);
SelectedCustomVersions = new List<FCustomVersion>();
foreach (var (guid, v) in version.CustomVersions)
{
SelectedCustomVersions.Add(new FCustomVersion { Key = new FGuid(guid), Version = v });
}
SelectedOptions = new Dictionary<string, bool>();
foreach (var (k, v) in version.Options)
{
SelectedOptions[k] = v;
}
}
public void ResetPreset()
{
SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
}
public SettingsOut Save()
{
var ret = SettingsOut.Nothing;
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
_outputSnapshot != UserSettings.Default.OutputDirectory || // textbox
_rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox
_propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox
_textureSnapshot != UserSettings.Default.TextureDirectory || // textbox
_audioSnapshot != UserSettings.Default.AudioDirectory || // textbox
_modelSnapshot != UserSettings.Default.ModelDirectory || // textbox
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
ret = SettingsOut.Restart;
if (_assetLanguageSnapshot != SelectedAssetLanguage)
ret = SettingsOut.ReloadLocres;
if (_updateModeSnapshot != SelectedUpdateMode)
ret = SettingsOut.CheckForUpdates;
UserSettings.Default.UpdateMode = SelectedUpdateMode;
UserSettings.Default.Presets[_game] = SelectedPreset;
UserSettings.Default.OverridedPlatform = SelectedUePlatform;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
{
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions;
}
else
{
UserSettings.Default.OverridedGame[_game] = SelectedUeGame;
UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions;
UserSettings.Default.OverridedOptions[_game] = SelectedOptions;
}
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle;
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
UserSettings.Default.AesReload = SelectedAesReload;
UserSettings.Default.DiscordRpc = SelectedDiscordRpc;
if (SelectedDiscordRpc == EDiscordRpc.Never)
_discordHandler.Shutdown();
return ret;
}
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
private IEnumerable<string> EnumeratePresets()
{
yield return Constants._NO_PRESET_TRIGGER;
}
private IEnumerable<EGame> EnumerateUeGames() => Enum.GetValues<EGame>();
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
private IEnumerable<ECompressedAudio> EnumerateCompressedAudios() => Enum.GetValues<ECompressedAudio>();
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues<EIconStyle>();
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues<EMeshFormat>();
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
private IEnumerable<ETexturePlatform> EnumerateUePlatforms() => Enum.GetValues<ETexturePlatform>();
}

View File

@ -18,430 +18,437 @@ using System.Windows.Media.Imaging;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse_Conversion.Textures;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class TabImage : ViewModel
{
public class TabImage : ViewModel
public string ExportName { get; }
public byte[] ImageBuffer { get; set; }
public TabImage(string name, bool rnn, SKBitmap img)
{
public string ExportName { get; }
public byte[] ImageBuffer { get; set; }
public TabImage(string name, bool rnn, SKBitmap img)
{
ExportName = name;
RenderNearestNeighbor = rnn;
SetImage(img);
}
private BitmapImage _image;
public BitmapImage Image
{
get => _image;
set
{
if (_image == value) return;
SetProperty(ref _image, value);
}
}
private bool _renderNearestNeighbor;
public bool RenderNearestNeighbor
{
get => _renderNearestNeighbor;
set => SetProperty(ref _renderNearestNeighbor, value);
}
private bool _noAlpha;
public bool NoAlpha
{
get => _noAlpha;
set
{
SetProperty(ref _noAlpha, value);
ResetImage();
}
}
private void SetImage(SKBitmap bitmap)
{
_bmp = bitmap;
using var data = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100);
using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
Image = image;
}
private SKBitmap _bmp;
private void ResetImage() => SetImage(_bmp);
ExportName = name;
RenderNearestNeighbor = rnn;
SetImage(img);
}
public class TabItem : ViewModel
private BitmapImage _image;
public BitmapImage Image
{
private string _header;
public string Header
get => _image;
set
{
get => _header;
set => SetProperty(ref _header, value);
}
private string _directory;
public string Directory
{
get => _directory;
set => SetProperty(ref _directory, value);
}
private bool _hasSearchOpen;
public bool HasSearchOpen
{
get => _hasSearchOpen;
set => SetProperty(ref _hasSearchOpen, value);
}
private string _textToFind;
public string TextToFind
{
get => _textToFind;
set => SetProperty(ref _textToFind, value);
}
private bool _searchUp;
public bool SearchUp
{
get => _searchUp;
set => SetProperty(ref _searchUp, value);
}
private bool _caseSensitive;
public bool CaseSensitive
{
get => _caseSensitive;
set => SetProperty(ref _caseSensitive, value);
}
private bool _useRegEx;
public bool UseRegEx
{
get => _useRegEx;
set => SetProperty(ref _useRegEx, value);
}
private bool _wholeWord;
public bool WholeWord
{
get => _wholeWord;
set => SetProperty(ref _wholeWord, value);
}
private TextDocument _document;
public TextDocument Document
{
get => _document;
set => SetProperty(ref _document, value);
}
private double _fontSize = 11.0;
public double FontSize
{
get => _fontSize;
set => SetProperty(ref _fontSize, value);
}
private double _scrollPosition;
public double ScrollPosition
{
get => _scrollPosition;
set => SetProperty(ref _scrollPosition, value);
}
private string _scrollTrigger;
public string ScrollTrigger
{
get => _scrollTrigger;
set => SetProperty(ref _scrollTrigger, value);
}
private IHighlightingDefinition _highlighter;
public IHighlightingDefinition Highlighter
{
get => _highlighter;
set
{
if (_highlighter == value) return;
SetProperty(ref _highlighter, value);
}
}
private TabImage _selectedImage;
public TabImage SelectedImage
{
get => _selectedImage;
set
{
if (_selectedImage == value) return;
SetProperty(ref _selectedImage, value);
RaisePropertyChanged("HasImage");
RaisePropertyChanged("Page");
}
}
public bool HasImage => SelectedImage != null;
public bool HasMultipleImages => _images.Count > 1;
public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}";
private readonly ObservableCollection<TabImage> _images;
public bool ShouldScroll => !string.IsNullOrEmpty(ScrollTrigger);
private TabCommand _tabCommand;
public TabCommand TabCommand => _tabCommand ??= new TabCommand(this);
private ImageCommand _imageCommand;
public ImageCommand ImageCommand => _imageCommand ??= new ImageCommand(this);
private GoToCommand _goToCommand;
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
public TabItem(string header, string directory)
{
Header = header;
Directory = directory;
_images = new ObservableCollection<TabImage>();
}
public void ClearImages()
{
Application.Current.Dispatcher.Invoke(() =>
{
_images.Clear();
SelectedImage = null;
RaisePropertyChanged("HasMultipleImages");
});
}
public void AddImage(UTexture2D texture) => AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform));
public void AddImage(string name, bool rnn, SKBitmap[] img)
{
foreach (var i in img) AddImage(name, rnn, i);
}
public void AddImage(string name, bool rnn, SKBitmap img)
{
Application.Current.Dispatcher.Invoke(() =>
{
var t = new TabImage(name, rnn, img);
if (UserSettings.Default.IsAutoSaveTextures)
SaveImage(t, true);
_images.Add(t);
SelectedImage ??= t;
RaisePropertyChanged("Page");
RaisePropertyChanged("HasMultipleImages");
});
}
public void GoPreviousImage() => SelectedImage = _images.Previous(SelectedImage);
public void GoNextImage() => SelectedImage = _images.Next(SelectedImage);
public void SetDocumentText(string text, bool bulkSave)
{
Application.Current.Dispatcher.Invoke(() =>
{
Document ??= new TextDocument();
Document.Text = text;
if (UserSettings.Default.IsAutoSaveProps || bulkSave)
SaveProperty(true);
});
}
public void ResetDocumentText()
{
Application.Current.Dispatcher.Invoke(() =>
{
Document ??= new TextDocument();
Document.Text = string.Empty;
});
}
public void SaveImage(bool autoSave) => SaveImage(SelectedImage, autoSave);
private void SaveImage(TabImage image, bool autoSave)
{
if (image == null) return;
var fileName = $"{image.ExportName}.png";
var directory = Path.Combine(UserSettings.Default.TextureDirectory,
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
if (!autoSave)
{
var saveFileDialog = new SaveFileDialog
{
Title = "Save Texture",
FileName = fileName,
InitialDirectory = UserSettings.Default.TextureDirectory,
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
};
var result = saveFileDialog.ShowDialog();
if (!result.HasValue || !result.Value) return;
directory = saveFileDialog.FileName;
}
else
{
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
}
using (var fs = new FileStream(directory, FileMode.Create, FileAccess.Write, FileShare.Read))
{
fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length);
}
SaveCheck(directory, fileName);
}
public void SaveProperty(bool autoSave)
{
var fileName = Path.ChangeExtension(Header, ".json");
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/');
if (!autoSave)
{
var saveFileDialog = new SaveFileDialog
{
Title = "Save Property",
FileName = fileName,
InitialDirectory = UserSettings.Default.PropertiesDirectory,
Filter = "JSON Files (*.json)|*.json|INI Files (*.ini)|*.ini|XML Files (*.xml)|*.xml|All Files (*.*)|*.*"
};
var result = saveFileDialog.ShowDialog();
if (!result.HasValue || !result.Value) return;
directory = saveFileDialog.FileName;
}
else
{
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
}
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
SaveCheck(directory, fileName);
}
private void SaveCheck(string path, string fileName)
{
if (File.Exists(path))
{
Log.Information("{FileName} successfully saved", fileName);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
}
else
{
Log.Error("{FileName} could not be saved", fileName);
FLogger.AppendError();
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
}
if (_image == value) return;
SetProperty(ref _image, value);
}
}
public class TabControlViewModel : ViewModel
private bool _renderNearestNeighbor;
public bool RenderNearestNeighbor
{
private TabItem _selectedTab;
public TabItem SelectedTab
get => _renderNearestNeighbor;
set => SetProperty(ref _renderNearestNeighbor, value);
}
private bool _noAlpha;
public bool NoAlpha
{
get => _noAlpha;
set
{
get => _selectedTab;
set => SetProperty(ref _selectedTab, value);
SetProperty(ref _noAlpha, value);
ResetImage();
}
}
private AddTabCommand _addTabCommand;
public AddTabCommand AddTabCommand => _addTabCommand ??= new AddTabCommand(this);
private void SetImage(SKBitmap bitmap)
{
_bmp = bitmap;
using var data = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100);
using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
Image = image;
}
private readonly ObservableCollection<TabItem> _tabItems;
public ReadOnlyObservableCollection<TabItem> TabsItems { get; }
private SKBitmap _bmp;
private void ResetImage() => SetImage(_bmp);
}
public bool HasNoTabs => _tabItems.Count == 0;
public bool CanAddTabs => _tabItems.Count < 25;
public class TabItem : ViewModel
{
private string _header;
public string Header
{
get => _header;
set => SetProperty(ref _header, value);
}
public TabControlViewModel()
private string _directory;
public string Directory
{
get => _directory;
set => SetProperty(ref _directory, value);
}
private bool _hasSearchOpen;
public bool HasSearchOpen
{
get => _hasSearchOpen;
set => SetProperty(ref _hasSearchOpen, value);
}
private string _textToFind;
public string TextToFind
{
get => _textToFind;
set => SetProperty(ref _textToFind, value);
}
private bool _searchUp;
public bool SearchUp
{
get => _searchUp;
set => SetProperty(ref _searchUp, value);
}
private bool _caseSensitive;
public bool CaseSensitive
{
get => _caseSensitive;
set => SetProperty(ref _caseSensitive, value);
}
private bool _useRegEx;
public bool UseRegEx
{
get => _useRegEx;
set => SetProperty(ref _useRegEx, value);
}
private bool _wholeWord;
public bool WholeWord
{
get => _wholeWord;
set => SetProperty(ref _wholeWord, value);
}
private TextDocument _document;
public TextDocument Document
{
get => _document;
set => SetProperty(ref _document, value);
}
private double _fontSize = 11.0;
public double FontSize
{
get => _fontSize;
set => SetProperty(ref _fontSize, value);
}
private double _scrollPosition;
public double ScrollPosition
{
get => _scrollPosition;
set => SetProperty(ref _scrollPosition, value);
}
private string _scrollTrigger;
public string ScrollTrigger
{
get => _scrollTrigger;
set => SetProperty(ref _scrollTrigger, value);
}
private IHighlightingDefinition _highlighter;
public IHighlightingDefinition Highlighter
{
get => _highlighter;
set
{
_tabItems = new ObservableCollection<TabItem>(EnumerateTabs());
TabsItems = new ReadOnlyObservableCollection<TabItem>(_tabItems);
SelectedTab = TabsItems.FirstOrDefault();
if (_highlighter == value) return;
SetProperty(ref _highlighter, value);
}
}
public void AddTab(string header = null, string directory = null)
private TabImage _selectedImage;
public TabImage SelectedImage
{
get => _selectedImage;
set
{
if (!CanAddTabs) return;
if (_selectedImage == value) return;
SetProperty(ref _selectedImage, value);
RaisePropertyChanged("HasImage");
RaisePropertyChanged("Page");
}
}
var h = header ?? "New Tab";
var d = directory ?? string.Empty;
if (SelectedTab is { Header : "New Tab" })
public bool HasImage => SelectedImage != null;
public bool HasMultipleImages => _images.Count > 1;
public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}";
private readonly ObservableCollection<TabImage> _images;
public bool ShouldScroll => !string.IsNullOrEmpty(ScrollTrigger);
private TabCommand _tabCommand;
public TabCommand TabCommand => _tabCommand ??= new TabCommand(this);
private ImageCommand _imageCommand;
public ImageCommand ImageCommand => _imageCommand ??= new ImageCommand(this);
private GoToCommand _goToCommand;
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
public TabItem(string header, string directory)
{
Header = header;
Directory = directory;
_images = new ObservableCollection<TabImage>();
}
public void ClearImages()
{
Application.Current.Dispatcher.Invoke(() =>
{
_images.Clear();
SelectedImage = null;
RaisePropertyChanged("HasMultipleImages");
});
}
public void AddImage(UTexture2D texture) => AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform));
public void AddImage(string name, bool rnn, SKBitmap[] img)
{
foreach (var i in img) AddImage(name, rnn, i);
}
public void AddImage(string name, bool rnn, SKBitmap img)
{
Application.Current.Dispatcher.Invoke(() =>
{
var t = new TabImage(name, rnn, img);
if (UserSettings.Default.IsAutoSaveTextures)
SaveImage(t, true);
_images.Add(t);
SelectedImage ??= t;
RaisePropertyChanged("Page");
RaisePropertyChanged("HasMultipleImages");
});
}
public void GoPreviousImage() => SelectedImage = _images.Previous(SelectedImage);
public void GoNextImage() => SelectedImage = _images.Next(SelectedImage);
public void SetDocumentText(string text, bool bulkSave)
{
Application.Current.Dispatcher.Invoke(() =>
{
Document ??= new TextDocument();
Document.Text = text;
if (UserSettings.Default.IsAutoSaveProps || bulkSave)
SaveProperty(true);
});
}
public void ResetDocumentText()
{
Application.Current.Dispatcher.Invoke(() =>
{
Document ??= new TextDocument();
Document.Text = string.Empty;
});
}
public void SaveImage(bool autoSave) => SaveImage(SelectedImage, autoSave);
private void SaveImage(TabImage image, bool autoSave)
{
if (image == null) return;
var fileName = $"{image.ExportName}.png";
var directory = Path.Combine(UserSettings.Default.TextureDirectory,
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
if (!autoSave)
{
var saveFileDialog = new SaveFileDialog
{
SelectedTab.Header = h;
SelectedTab.Directory = d;
return;
}
Title = "Save Texture",
FileName = fileName,
InitialDirectory = UserSettings.Default.TextureDirectory,
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
};
var result = saveFileDialog.ShowDialog();
if (!result.HasValue || !result.Value) return;
directory = saveFileDialog.FileName;
}
else
{
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
}
Application.Current.Dispatcher.Invoke(() =>
using (var fs = new FileStream(directory, FileMode.Create, FileAccess.Write, FileShare.Read))
{
fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length);
}
SaveCheck(directory, fileName);
}
public void SaveProperty(bool autoSave)
{
var fileName = Path.ChangeExtension(Header, ".json");
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/');
if (!autoSave)
{
var saveFileDialog = new SaveFileDialog
{
_tabItems.Add(new TabItem(h, d));
SelectedTab = _tabItems.Last();
});
Title = "Save Property",
FileName = fileName,
InitialDirectory = UserSettings.Default.PropertiesDirectory,
Filter = "JSON Files (*.json)|*.json|INI Files (*.ini)|*.ini|XML Files (*.xml)|*.xml|All Files (*.*)|*.*"
};
var result = saveFileDialog.ShowDialog();
if (!result.HasValue || !result.Value) return;
directory = saveFileDialog.FileName;
}
else
{
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
}
public void RemoveTab(TabItem tab = null)
{
Application.Current.Dispatcher.Invoke(() =>
{
var tabCount = _tabItems.Count;
var tabToDelete = tab ?? SelectedTab;
switch (tabCount)
{
case <= 0:
return;
// select previous tab before deleting current to avoid "ScrollToZero" issue on tab delete
case > 1:
SelectedTab = _tabItems.Previous(tabToDelete); // will select last if previous is -1 but who cares anyway, still better than having +1 to scroll 0
break;
}
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
SaveCheck(directory, fileName);
}
_tabItems.Remove(tabToDelete);
OnTabRemove?.Invoke(this, new TabEventArgs(tabToDelete));
});
private void SaveCheck(string path, string fileName)
{
if (File.Exists(path))
{
Log.Information("{FileName} successfully saved", fileName);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
}
public class TabEventArgs : EventArgs
else
{
public TabItem TabToRemove { get; set; }
public TabEventArgs(TabItem tab) { TabToRemove = tab; }
}
public event EventHandler OnTabRemove;
public void GoLeftTab() => SelectedTab = _tabItems.Previous(SelectedTab);
public void GoRightTab() => SelectedTab = _tabItems.Next(SelectedTab);
public void RemoveOtherTabs(TabItem tab)
{
Application.Current.Dispatcher.Invoke(() =>
{
foreach (var t in _tabItems.Where(t => t != tab).ToList())
{
_tabItems.Remove(t);
}
});
}
public void RemoveAllTabs()
{
Application.Current.Dispatcher.Invoke(() =>
{
SelectedTab = null;
_tabItems.Clear();
});
}
private static IEnumerable<TabItem> EnumerateTabs()
{
yield return new TabItem("New Tab", string.Empty);
Log.Error("{FileName} could not be saved", fileName);
FLogger.AppendError();
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
}
}
}
public class TabControlViewModel : ViewModel
{
private TabItem _selectedTab;
public TabItem SelectedTab
{
get => _selectedTab;
set => SetProperty(ref _selectedTab, value);
}
private AddTabCommand _addTabCommand;
public AddTabCommand AddTabCommand => _addTabCommand ??= new AddTabCommand(this);
private readonly ObservableCollection<TabItem> _tabItems;
public ReadOnlyObservableCollection<TabItem> TabsItems { get; }
public bool HasNoTabs => _tabItems.Count == 0;
public bool CanAddTabs => _tabItems.Count < 25;
public TabControlViewModel()
{
_tabItems = new ObservableCollection<TabItem>(EnumerateTabs());
TabsItems = new ReadOnlyObservableCollection<TabItem>(_tabItems);
SelectedTab = TabsItems.FirstOrDefault();
}
public void AddTab(string header = null, string directory = null)
{
if (!CanAddTabs) return;
var h = header ?? "New Tab";
var d = directory ?? string.Empty;
if (SelectedTab is { Header : "New Tab" })
{
SelectedTab.Header = h;
SelectedTab.Directory = d;
return;
}
Application.Current.Dispatcher.Invoke(() =>
{
_tabItems.Add(new TabItem(h, d));
SelectedTab = _tabItems.Last();
});
}
public void RemoveTab(TabItem tab = null)
{
Application.Current.Dispatcher.Invoke(() =>
{
var tabCount = _tabItems.Count;
var tabToDelete = tab ?? SelectedTab;
switch (tabCount)
{
case <= 0:
return;
// select previous tab before deleting current to avoid "ScrollToZero" issue on tab delete
case > 1:
SelectedTab = _tabItems.Previous(tabToDelete); // will select last if previous is -1 but who cares anyway, still better than having +1 to scroll 0
break;
}
_tabItems.Remove(tabToDelete);
OnTabRemove?.Invoke(this, new TabEventArgs(tabToDelete));
});
}
public class TabEventArgs : EventArgs
{
public TabItem TabToRemove { get; set; }
public TabEventArgs(TabItem tab)
{
TabToRemove = tab;
}
}
public event EventHandler OnTabRemove;
public void GoLeftTab() => SelectedTab = _tabItems.Previous(SelectedTab);
public void GoRightTab() => SelectedTab = _tabItems.Next(SelectedTab);
public void RemoveOtherTabs(TabItem tab)
{
Application.Current.Dispatcher.Invoke(() =>
{
foreach (var t in _tabItems.Where(t => t != tab).ToList())
{
_tabItems.Remove(t);
}
});
}
public void RemoveAllTabs()
{
Application.Current.Dispatcher.Invoke(() =>
{
SelectedTab = null;
_tabItems.Clear();
});
}
private static IEnumerable<TabItem> EnumerateTabs()
{
yield return new TabItem("New Tab", string.Empty);
}
}

View File

@ -7,113 +7,112 @@ using FModel.Services;
using FModel.Views.Resources.Controls;
using Serilog;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class ThreadWorkerViewModel : ViewModel
{
public class ThreadWorkerViewModel : ViewModel
private bool _statusChangeAttempted;
public bool StatusChangeAttempted
{
private bool _statusChangeAttempted;
public bool StatusChangeAttempted
get => _statusChangeAttempted;
private set => SetProperty(ref _statusChangeAttempted, value);
}
private bool _operationCancelled;
public bool OperationCancelled
{
get => _operationCancelled;
private set => SetProperty(ref _operationCancelled, value);
}
private CancellationTokenSource _currentCancellationTokenSource;
public CancellationTokenSource CurrentCancellationTokenSource
{
get => _currentCancellationTokenSource;
set
{
get => _statusChangeAttempted;
private set => SetProperty(ref _statusChangeAttempted, value);
}
private bool _operationCancelled;
public bool OperationCancelled
{
get => _operationCancelled;
private set => SetProperty(ref _operationCancelled, value);
}
private CancellationTokenSource _currentCancellationTokenSource;
public CancellationTokenSource CurrentCancellationTokenSource
{
get => _currentCancellationTokenSource;
set
{
if (_currentCancellationTokenSource == value) return;
SetProperty(ref _currentCancellationTokenSource, value);
RaisePropertyChanged("CanBeCanceled");
}
}
public bool CanBeCanceled => CurrentCancellationTokenSource != null;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private readonly AsyncQueue<Action<CancellationToken>> _jobs;
public ThreadWorkerViewModel()
{
_jobs = new AsyncQueue<Action<CancellationToken>>();
}
public async Task Begin(Action<CancellationToken> action)
{
if (!_applicationView.IsReady)
{
SignalOperationInProgress();
return;
}
CurrentCancellationTokenSource ??= new CancellationTokenSource();
_jobs.Enqueue(action);
await ProcessQueues();
}
public void Cancel()
{
if (!CanBeCanceled)
{
SignalOperationInProgress();
return;
}
CurrentCancellationTokenSource.Cancel();
}
private async Task ProcessQueues()
{
if (_jobs.Count > 0)
{
_applicationView.Status = EStatusKind.Loading;
await foreach (var job in _jobs)
{
try
{
// will end in "catch" if canceled
await Task.Run(() => job(CurrentCancellationTokenSource.Token));
}
catch (OperationCanceledException)
{
_applicationView.Status = EStatusKind.Stopped;
CurrentCancellationTokenSource = null; // kill token
OperationCancelled = true;
OperationCancelled = false;
return;
}
catch (Exception e)
{
_applicationView.Status = EStatusKind.Failed;
CurrentCancellationTokenSource = null; // kill token
Log.Error("{Exception}", e);
FLogger.AppendError();
FLogger.AppendText(e.Message, Constants.WHITE, true);
FLogger.AppendText(" " + e.StackTrace.SubstringBefore('\n').Trim(), Constants.WHITE, true);
return;
}
}
_applicationView.Status = EStatusKind.Completed;
CurrentCancellationTokenSource = null; // kill token
}
}
public void SignalOperationInProgress()
{
StatusChangeAttempted = true;
StatusChangeAttempted = false;
if (_currentCancellationTokenSource == value) return;
SetProperty(ref _currentCancellationTokenSource, value);
RaisePropertyChanged("CanBeCanceled");
}
}
}
public bool CanBeCanceled => CurrentCancellationTokenSource != null;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private readonly AsyncQueue<Action<CancellationToken>> _jobs;
public ThreadWorkerViewModel()
{
_jobs = new AsyncQueue<Action<CancellationToken>>();
}
public async Task Begin(Action<CancellationToken> action)
{
if (!_applicationView.IsReady)
{
SignalOperationInProgress();
return;
}
CurrentCancellationTokenSource ??= new CancellationTokenSource();
_jobs.Enqueue(action);
await ProcessQueues();
}
public void Cancel()
{
if (!CanBeCanceled)
{
SignalOperationInProgress();
return;
}
CurrentCancellationTokenSource.Cancel();
}
private async Task ProcessQueues()
{
if (_jobs.Count > 0)
{
_applicationView.Status = EStatusKind.Loading;
await foreach (var job in _jobs)
{
try
{
// will end in "catch" if canceled
await Task.Run(() => job(CurrentCancellationTokenSource.Token));
}
catch (OperationCanceledException)
{
_applicationView.Status = EStatusKind.Stopped;
CurrentCancellationTokenSource = null; // kill token
OperationCancelled = true;
OperationCancelled = false;
return;
}
catch (Exception e)
{
_applicationView.Status = EStatusKind.Failed;
CurrentCancellationTokenSource = null; // kill token
Log.Error("{Exception}", e);
FLogger.AppendError();
FLogger.AppendText(e.Message, Constants.WHITE, true);
FLogger.AppendText(" " + e.StackTrace.SubstringBefore('\n').Trim(), Constants.WHITE, true);
return;
}
}
_applicationView.Status = EStatusKind.Completed;
CurrentCancellationTokenSource = null; // kill token
}
}
public void SignalOperationInProgress()
{
StatusChangeAttempted = true;
StatusChangeAttempted = false;
}
}

View File

@ -1,10 +1,9 @@
namespace FModel.Views
namespace FModel.Views;
public partial class About
{
public partial class About
public About()
{
public About()
{
InitializeComponent();
}
InitializeComponent();
}
}

View File

@ -3,33 +3,32 @@ using System.Windows;
using FModel.Services;
using FModel.ViewModels;
namespace FModel.Views
namespace FModel.Views;
public partial class AesManager
{
public partial class AesManager
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public AesManager()
{
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
DataContext = _applicationView;
InitializeComponent();
}
public AesManager()
{
DataContext = _applicationView;
InitializeComponent();
}
private void OnClick(object sender, RoutedEventArgs e)
{
Close();
}
private void OnClick(object sender, RoutedEventArgs e)
{
Close();
}
private async void OnRefreshAes(object sender, RoutedEventArgs e)
{
await _applicationView.CUE4Parse.RefreshAes();
await _applicationView.AesManager.InitAes();
_applicationView.AesManager.HasChange = true; // yes even if nothing actually changed
}
private async void OnRefreshAes(object sender, RoutedEventArgs e)
{
await _applicationView.CUE4Parse.RefreshAes();
await _applicationView.AesManager.InitAes();
_applicationView.AesManager.HasChange = true; // yes even if nothing actually changed
}
private async void OnClosing(object sender, CancelEventArgs e)
{
await _applicationView.AesManager.UpdateProvider(false);
}
private async void OnClosing(object sender, CancelEventArgs e)
{
await _applicationView.AesManager.UpdateProvider(false);
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@ -11,88 +10,84 @@ using FModel.Settings;
using FModel.ViewModels;
using Microsoft.Win32;
namespace FModel.Views
namespace FModel.Views;
public partial class AudioPlayer
{
public partial class AudioPlayer
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public AudioPlayer()
{
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public AudioPlayer()
{
DataContext = _applicationView;
InitializeComponent();
}
public void Load(byte[] data, string filePath)
{
_applicationView.AudioPlayer.AddToPlaylist(data, filePath);
}
private void OnClosing(object sender, CancelEventArgs e)
{
_applicationView.AudioPlayer.Stop();
_applicationView.AudioPlayer.Dispose();
DiscordService.DiscordHandler.UpdateToSavedPresence();
}
private void OnDeviceSwap(object sender, SelectionChangedEventArgs e)
{
if (sender is not ComboBox {SelectedItem: MMDevice selectedDevice})
return;
UserSettings.Default.AudioDeviceId = selectedDevice.DeviceID;
_applicationView.AudioPlayer.Device();
}
private void OnVolumeChange(object sender, RoutedEventArgs e)
{
_applicationView.AudioPlayer.Volume();
}
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.OriginalSource is TextBox)
return;
if (UserSettings.Default.AddAudio.IsTriggered(e.Key))
{
var openFileDialog = new OpenFileDialog
{
Title = "Select an audio file",
InitialDirectory = UserSettings.Default.AudioDirectory,
Filter = "OGG Files (*.ogg)|*.ogg|WAV Files (*.wav)|*.wav|WEM Files (*.wem)|*.wem|ADPCM Files (*.adpcm)|*.adpcm|All Files (*.*)|*.*",
Multiselect = true
};
if (!(bool) openFileDialog.ShowDialog()) return;
foreach (var file in openFileDialog.FileNames)
{
_applicationView.AudioPlayer.AddToPlaylist(file);
}
}
else if (UserSettings.Default.PlayPauseAudio.IsTriggered(e.Key))
_applicationView.AudioPlayer.PlayPauseOnStart();
else if (UserSettings.Default.PreviousAudio.IsTriggered(e.Key))
_applicationView.AudioPlayer.Previous();
else if (UserSettings.Default.NextAudio.IsTriggered(e.Key))
_applicationView.AudioPlayer.Next();
}
private void OnAudioFileMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
_applicationView.AudioPlayer.PlayPauseOnForce();
}
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
{
if (sender is not TextBox textBox)
return;
var filters = textBox.Text.Trim().Split(' ');
_applicationView.AudioPlayer.AudioFilesView.Filter = o =>
{
return o is AudioFile audio && filters.All(x => audio.FileName.Contains(x, StringComparison.OrdinalIgnoreCase));
};
}
DataContext = _applicationView;
InitializeComponent();
}
}
public void Load(byte[] data, string filePath)
{
_applicationView.AudioPlayer.AddToPlaylist(data, filePath);
}
private void OnClosing(object sender, CancelEventArgs e)
{
_applicationView.AudioPlayer.Stop();
_applicationView.AudioPlayer.Dispose();
DiscordService.DiscordHandler.UpdateToSavedPresence();
}
private void OnDeviceSwap(object sender, SelectionChangedEventArgs e)
{
if (sender is not ComboBox { SelectedItem: MMDevice selectedDevice })
return;
UserSettings.Default.AudioDeviceId = selectedDevice.DeviceID;
_applicationView.AudioPlayer.Device();
}
private void OnVolumeChange(object sender, RoutedEventArgs e)
{
_applicationView.AudioPlayer.Volume();
}
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.OriginalSource is TextBox)
return;
if (UserSettings.Default.AddAudio.IsTriggered(e.Key))
{
var openFileDialog = new OpenFileDialog
{
Title = "Select an audio file",
InitialDirectory = UserSettings.Default.AudioDirectory,
Filter = "OGG Files (*.ogg)|*.ogg|WAV Files (*.wav)|*.wav|WEM Files (*.wem)|*.wem|ADPCM Files (*.adpcm)|*.adpcm|All Files (*.*)|*.*",
Multiselect = true
};
if (!openFileDialog.ShowDialog().GetValueOrDefault()) return;
foreach (var file in openFileDialog.FileNames)
{
_applicationView.AudioPlayer.AddToPlaylist(file);
}
}
else if (UserSettings.Default.PlayPauseAudio.IsTriggered(e.Key))
_applicationView.AudioPlayer.PlayPauseOnStart();
else if (UserSettings.Default.PreviousAudio.IsTriggered(e.Key))
_applicationView.AudioPlayer.Previous();
else if (UserSettings.Default.NextAudio.IsTriggered(e.Key))
_applicationView.AudioPlayer.Next();
}
private void OnAudioFileMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
_applicationView.AudioPlayer.PlayPauseOnForce();
}
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
{
if (sender is not TextBox textBox)
return;
var filters = textBox.Text.Trim().Split(' ');
_applicationView.AudioPlayer.AudioFilesView.Filter = o => { return o is AudioFile audio && filters.All(x => audio.FileName.Contains(x, StringComparison.OrdinalIgnoreCase)); };
}
}

View File

@ -1,31 +1,30 @@
using System.Windows;
using FModel.ViewModels;
namespace FModel.Views
namespace FModel.Views;
public partial class BackupManager
{
public partial class BackupManager
private readonly BackupManagerViewModel _viewModel;
public BackupManager(string gameName)
{
private readonly BackupManagerViewModel _viewModel;
DataContext = _viewModel = new BackupManagerViewModel(gameName);
InitializeComponent();
}
public BackupManager(string gameName)
{
DataContext = _viewModel = new BackupManagerViewModel(gameName);
InitializeComponent();
}
private async void OnLoaded(object sender, RoutedEventArgs e)
{
await _viewModel.Initialize();
}
private async void OnLoaded(object sender, RoutedEventArgs e)
{
await _viewModel.Initialize();
}
private async void OnDownloadClick(object sender, RoutedEventArgs e)
{
await _viewModel.Download();
}
private async void OnDownloadClick(object sender, RoutedEventArgs e)
{
await _viewModel.Download();
}
private async void OnCreateBackupClick(object sender, RoutedEventArgs e)
{
await _viewModel.CreateBackup();
}
private async void OnCreateBackupClick(object sender, RoutedEventArgs e)
{
await _viewModel.CreateBackup();
}
}

View File

@ -1,24 +1,23 @@
using System.Windows;
using FModel.ViewModels;
namespace FModel.Views
{
public partial class CustomDir
{
public CustomDir(CustomDirectory customDir)
{
DataContext = customDir;
InitializeComponent();
Activate();
WpfSuckMyDick.Focus();
WpfSuckMyDick.SelectAll();
}
namespace FModel.Views;
private void OnClick(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
public partial class CustomDir
{
public CustomDir(CustomDirectory customDir)
{
DataContext = customDir;
InitializeComponent();
Activate();
WpfSuckMyDick.Focus();
WpfSuckMyDick.SelectAll();
}
private void OnClick(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
}

View File

@ -2,64 +2,63 @@
using Ookii.Dialogs.Wpf;
using System.Windows;
namespace FModel.Views
namespace FModel.Views;
/// <summary>
/// Logique d'interaction pour DirectorySelector.xaml
/// </summary>
public partial class DirectorySelector
{
/// <summary>
/// Logique d'interaction pour DirectorySelector.xaml
/// </summary>
public partial class DirectorySelector
public DirectorySelector(GameSelectorViewModel gameSelectorViewModel)
{
public DirectorySelector(GameSelectorViewModel gameSelectorViewModel)
DataContext = gameSelectorViewModel;
InitializeComponent();
}
private void OnClick(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void OnBrowseDirectories(object sender, RoutedEventArgs e)
{
if (DataContext is not GameSelectorViewModel gameLauncherViewModel)
return;
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
if (folderBrowser.ShowDialog() == true)
{
DataContext = gameSelectorViewModel;
InitializeComponent();
}
private void OnClick(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void OnBrowseDirectories(object sender, RoutedEventArgs e)
{
if (DataContext is not GameSelectorViewModel gameLauncherViewModel)
return;
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
if (folderBrowser.ShowDialog() == true)
{
gameLauncherViewModel.AddUnknownGame(folderBrowser.SelectedPath);
}
}
private void OnBrowseManualDirectories(object sender, RoutedEventArgs e)
{
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
if (folderBrowser.ShowDialog() == true)
{
HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath;
}
}
private void OnAddDirectory(object sender, RoutedEventArgs e)
{
if (DataContext is not GameSelectorViewModel gameLauncherViewModel||
string.IsNullOrEmpty(HelloMyNameIsGame.Text) ||
string.IsNullOrEmpty(HelloGameMyNameIsDirectory.Text))
return;
gameLauncherViewModel.AddUnknownGame(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text);
HelloMyNameIsGame.Clear();
HelloGameMyNameIsDirectory.Clear();
}
private void OnDeleteDirectory(object sender, RoutedEventArgs e)
{
if (DataContext is not GameSelectorViewModel gameLauncherViewModel)
return;
gameLauncherViewModel.DeleteSelectedGame();
gameLauncherViewModel.AddUnknownGame(folderBrowser.SelectedPath);
}
}
}
private void OnBrowseManualDirectories(object sender, RoutedEventArgs e)
{
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
if (folderBrowser.ShowDialog() == true)
{
HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath;
}
}
private void OnAddDirectory(object sender, RoutedEventArgs e)
{
if (DataContext is not GameSelectorViewModel gameLauncherViewModel||
string.IsNullOrEmpty(HelloMyNameIsGame.Text) ||
string.IsNullOrEmpty(HelloGameMyNameIsDirectory.Text))
return;
gameLauncherViewModel.AddUnknownGame(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text);
HelloMyNameIsGame.Clear();
HelloGameMyNameIsDirectory.Clear();
}
private void OnDeleteDirectory(object sender, RoutedEventArgs e)
{
if (DataContext is not GameSelectorViewModel gameLauncherViewModel)
return;
gameLauncherViewModel.DeleteSelectedGame();
}
}

View File

@ -1,15 +1,10 @@
using AdonisUI.Controls;
using FModel.Extensions;
using FModel.Settings;
using FModel.Views.Resources.Controls;
using Microsoft.Win32;
using Serilog;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
@ -22,281 +17,281 @@ using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media.Imaging;
namespace FModel.Views
namespace FModel.Views;
public partial class ImageMerger
{
public partial class ImageMerger
private const string FILENAME = "Preview.png";
private byte[] _imageBuffer;
public ImageMerger()
{
private const string FILENAME = "Preview.png";
private byte[] _imagebuffer;
InitializeComponent();
}
public ImageMerger()
{
InitializeComponent();
}
private async void DrawPreview(object sender, DragCompletedEventArgs dragCompletedEventArgs)
{
if (ImagePreview.Source != null)
await DrawPreview().ConfigureAwait(false);
}
private async void Click_DrawPreview(object sender, MouseButtonEventArgs e)
{
if (ImagePreview.Source != null)
await DrawPreview().ConfigureAwait(false);
}
private async Task DrawPreview()
{
AddButton.IsEnabled = false;
UpButton.IsEnabled = false;
DownButton.IsEnabled = false;
DeleteButton.IsEnabled = false;
ClearButton.IsEnabled = false;
SizeSlider.IsEnabled = false;
OpenImageButton.IsEnabled = false;
SaveImageButton.IsEnabled = false;
var margin = UserSettings.Default.ImageMergerMargin;
int num = 1, curW = 0, curH = 0, maxWidth = 0, maxHeight = 0, lineMaxHeight = 0, imagesPerRow = Convert.ToInt32(SizeSlider.Value);
var positions = new Dictionary<int, SKPoint>();
var images = new SKBitmap[ImagesListBox.Items.Count];
for (var i = 0; i < images.Length; i++)
{
var item = (ListBoxItem) ImagesListBox.Items[i];
var ms = new MemoryStream();
var stream = new FileStream(item.ContentStringFormat, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
if (item.ContentStringFormat.EndsWith(".tif"))
{
await using var tmp = new MemoryStream();
await stream.CopyToAsync(tmp);
System.Drawing.Image.FromStream(tmp).Save(ms, ImageFormat.Png);
}
else
{
await stream.CopyToAsync(ms);
}
var image = SKBitmap.Decode(ms.ToArray());
positions[i] = new SKPoint(curW, curH);
images[i] = image;
if (image.Height > lineMaxHeight)
lineMaxHeight = image.Height;
if (num % imagesPerRow == 0)
{
maxWidth = curW + image.Width + margin;
curH += lineMaxHeight + margin;
curW = 0;
lineMaxHeight = 0;
}
else
{
maxHeight = curH + lineMaxHeight + margin;
curW += image.Width + margin;
if (curW > maxWidth)
maxWidth = curW;
}
num++;
}
await Task.Run(() =>
{
using var bmp = new SKBitmap(maxWidth - margin, maxHeight - margin, SKColorType.Rgba8888, SKAlphaType.Premul);
using var canvas = new SKCanvas(bmp);
for (var i = 0; i < images.Length; i++)
{
using (images[i])
{
canvas.DrawBitmap(images[i], positions[i], new SKPaint {FilterQuality = SKFilterQuality.High, IsAntialias = true});
}
}
using var data = bmp.Encode(SKEncodedImageFormat.Png, 100);
using var stream = new MemoryStream(_imagebuffer = data.ToArray());
var photo = new BitmapImage();
photo.BeginInit();
photo.CacheOption = BitmapCacheOption.OnLoad;
photo.StreamSource = stream;
photo.EndInit();
photo.Freeze();
Application.Current.Dispatcher.Invoke(delegate { ImagePreview.Source = photo; });
}).ContinueWith(t =>
{
AddButton.IsEnabled = true;
UpButton.IsEnabled = true;
DownButton.IsEnabled = true;
DeleteButton.IsEnabled = true;
ClearButton.IsEnabled = true;
SizeSlider.IsEnabled = true;
OpenImageButton.IsEnabled = true;
SaveImageButton.IsEnabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private async void OnImageAdd(object sender, RoutedEventArgs e)
{
var fileBrowser = new OpenFileDialog
{
Title = "Add image(s)",
InitialDirectory = $"{UserSettings.Default.OutputDirectory}\\Exports",
Multiselect = true,
Filter = "Image Files (*.png,*.bmp,*.jpg,*.jpeg,*.jfif,*.jpe,*.tiff,*.tif)|*.png;*.bmp;*.jpg;*.jpeg;*.jfif;*.jpe;*.tiff;*.tif|All Files (*.*)|*.*"
};
var result = fileBrowser.ShowDialog();
if (!result.HasValue || !result.Value) return;
foreach (var file in fileBrowser.FileNames)
{
ImagesListBox.Items.Add(new ListBoxItem
{
ContentStringFormat = file,
Content = Path.GetFileNameWithoutExtension(file)
});
}
SizeSlider.Value = Math.Min(ImagesListBox.Items.Count, Math.Round(Math.Sqrt(ImagesListBox.Items.Count)));
private async void DrawPreview(object sender, DragCompletedEventArgs dragCompletedEventArgs)
{
if (ImagePreview.Source != null)
await DrawPreview().ConfigureAwait(false);
}
}
private async void ModifyItemInList(object sender, RoutedEventArgs e)
private async void Click_DrawPreview(object sender, MouseButtonEventArgs e)
{
if (ImagePreview.Source != null)
await DrawPreview().ConfigureAwait(false);
}
private async Task DrawPreview()
{
AddButton.IsEnabled = false;
UpButton.IsEnabled = false;
DownButton.IsEnabled = false;
DeleteButton.IsEnabled = false;
ClearButton.IsEnabled = false;
SizeSlider.IsEnabled = false;
OpenImageButton.IsEnabled = false;
SaveImageButton.IsEnabled = false;
var margin = UserSettings.Default.ImageMergerMargin;
int num = 1, curW = 0, curH = 0, maxWidth = 0, maxHeight = 0, lineMaxHeight = 0, imagesPerRow = Convert.ToInt32(SizeSlider.Value);
var positions = new Dictionary<int, SKPoint>();
var images = new SKBitmap[ImagesListBox.Items.Count];
for (var i = 0; i < images.Length; i++)
{
if (ImagesListBox.Items.Count <= 0 || ImagesListBox.SelectedItems.Count <= 0) return;
var indices = ImagesListBox.SelectedItems.Cast<ListBoxItem>().Select(i => ImagesListBox.Items.IndexOf(i)).ToArray();
var reloadImage = false;
var item = (ListBoxItem) ImagesListBox.Items[i];
var ms = new MemoryStream();
var stream = new FileStream(item.ContentStringFormat, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
switch (((Button) sender).Name)
if (item.ContentStringFormat.EndsWith(".tif"))
{
case "UpButton":
{
if (indices.Length > 0 && indices[0] > 0)
{
for (var i = 0; i < ImagesListBox.Items.Count; i++)
{
if (!indices.Contains(i)) continue;
var item = (ListBoxItem) ImagesListBox.Items[i];
ImagesListBox.Items.Remove(item);
ImagesListBox.Items.Insert(i - 1, item);
item.IsSelected = true;
reloadImage = true;
}
}
ImagesListBox.SelectedItems.Add(indices);
if (reloadImage)
{
await DrawPreview().ConfigureAwait(false);
}
break;
}
case "DownButton":
{
if (indices.Length > 0 && indices[^1] < ImagesListBox.Items.Count - 1)
{
for (var i = ImagesListBox.Items.Count - 1; i > -1; --i)
{
if (!indices.Contains(i)) continue;
var item = (ListBoxItem) ImagesListBox.Items[i];
ImagesListBox.Items.Remove(item);
ImagesListBox.Items.Insert(i + 1, item);
item.IsSelected = true;
reloadImage = true;
}
}
if (reloadImage)
{
await DrawPreview().ConfigureAwait(false);
}
break;
}
case "DeleteButton":
{
if (ImagesListBox.Items.Count > 0 && ImagesListBox.SelectedItems.Count > 0)
{
for (var i = ImagesListBox.SelectedItems.Count - 1; i >= 0; --i)
ImagesListBox.Items.Remove(ImagesListBox.SelectedItems[i]);
}
await DrawPreview().ConfigureAwait(false);
break;
}
}
}
private void OnClear(object sender, RoutedEventArgs e)
{
ImagesListBox.Items.Clear();
ImagePreview.Source = null;
}
private void OnOpenImage(object sender, RoutedEventArgs e)
{
if (ImagePreview.Source == null) return;
Helper.OpenWindow<AdonisWindow>("Merged Image", () =>
{
new ImagePopout
{
Title = "Merged Image",
Width = ImagePreview.Source.Width,
Height = ImagePreview.Source.Height,
WindowState = ImagePreview.Source.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
ImageCtrl = {Source = ImagePreview.Source}
}.Show();
});
}
private void OnSaveImage(object sender, RoutedEventArgs e)
{
Application.Current.Dispatcher.Invoke(delegate
{
if (ImagePreview.Source == null) return;
var saveFileDialog = new SaveFileDialog
{
Title = "Save Image",
FileName = FILENAME,
InitialDirectory = UserSettings.Default.OutputDirectory,
Filter = "Png Files (*.png)|*.png|All Files (*.*)|*.*"
};
var result = saveFileDialog.ShowDialog();
if (!result.HasValue || !result.Value) return;
using (var fs = new FileStream(saveFileDialog.FileName, FileMode.Create, FileAccess.Write, FileShare.Read))
{
fs.Write(_imagebuffer, 0, _imagebuffer.Length);
}
SaveCheck(saveFileDialog.FileName, Path.GetFileName(saveFileDialog.FileName));
});
}
private static void SaveCheck(string path, string fileName)
{
if (File.Exists(path))
{
Log.Information("{FileName} successfully saved", fileName);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
await using var tmp = new MemoryStream();
await stream.CopyToAsync(tmp);
System.Drawing.Image.FromStream(tmp).Save(ms, ImageFormat.Png);
}
else
{
Log.Error("{FileName} could not be saved", fileName);
FLogger.AppendError();
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
await stream.CopyToAsync(ms);
}
var image = SKBitmap.Decode(ms.ToArray());
positions[i] = new SKPoint(curW, curH);
images[i] = image;
if (image.Height > lineMaxHeight)
lineMaxHeight = image.Height;
if (num % imagesPerRow == 0)
{
maxWidth = curW + image.Width + margin;
curH += lineMaxHeight + margin;
curW = 0;
lineMaxHeight = 0;
}
else
{
maxHeight = curH + lineMaxHeight + margin;
curW += image.Width + margin;
if (curW > maxWidth)
maxWidth = curW;
}
num++;
}
private void OnCopyImage(object sender, RoutedEventArgs e)
await Task.Run(() =>
{
ClipboardExtensions.SetImage(_imagebuffer, FILENAME);
using var bmp = new SKBitmap(maxWidth - margin, maxHeight - margin, SKColorType.Rgba8888, SKAlphaType.Premul);
using var canvas = new SKCanvas(bmp);
for (var i = 0; i < images.Length; i++)
{
using (images[i])
{
canvas.DrawBitmap(images[i], positions[i], new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
}
}
using var data = bmp.Encode(SKEncodedImageFormat.Png, 100);
using var stream = new MemoryStream(_imageBuffer = data.ToArray());
var photo = new BitmapImage();
photo.BeginInit();
photo.CacheOption = BitmapCacheOption.OnLoad;
photo.StreamSource = stream;
photo.EndInit();
photo.Freeze();
Application.Current.Dispatcher.Invoke(delegate { ImagePreview.Source = photo; });
}).ContinueWith(t =>
{
AddButton.IsEnabled = true;
UpButton.IsEnabled = true;
DownButton.IsEnabled = true;
DeleteButton.IsEnabled = true;
ClearButton.IsEnabled = true;
SizeSlider.IsEnabled = true;
OpenImageButton.IsEnabled = true;
SaveImageButton.IsEnabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private async void OnImageAdd(object sender, RoutedEventArgs e)
{
var fileBrowser = new OpenFileDialog
{
Title = "Add image(s)",
InitialDirectory = $"{UserSettings.Default.OutputDirectory}\\Exports",
Multiselect = true,
Filter = "Image Files (*.png,*.bmp,*.jpg,*.jpeg,*.jfif,*.jpe,*.tiff,*.tif)|*.png;*.bmp;*.jpg;*.jpeg;*.jfif;*.jpe;*.tiff;*.tif|All Files (*.*)|*.*"
};
var result = fileBrowser.ShowDialog();
if (!result.HasValue || !result.Value) return;
foreach (var file in fileBrowser.FileNames)
{
ImagesListBox.Items.Add(new ListBoxItem
{
ContentStringFormat = file,
Content = Path.GetFileNameWithoutExtension(file)
});
}
SizeSlider.Value = Math.Min(ImagesListBox.Items.Count, Math.Round(Math.Sqrt(ImagesListBox.Items.Count)));
await DrawPreview().ConfigureAwait(false);
}
private async void ModifyItemInList(object sender, RoutedEventArgs e)
{
if (ImagesListBox.Items.Count <= 0 || ImagesListBox.SelectedItems.Count <= 0) return;
var indices = ImagesListBox.SelectedItems.Cast<ListBoxItem>().Select(i => ImagesListBox.Items.IndexOf(i)).ToArray();
var reloadImage = false;
switch (((Button) sender).Name)
{
case "UpButton":
{
if (indices.Length > 0 && indices[0] > 0)
{
for (var i = 0; i < ImagesListBox.Items.Count; i++)
{
if (!indices.Contains(i)) continue;
var item = (ListBoxItem) ImagesListBox.Items[i];
ImagesListBox.Items.Remove(item);
ImagesListBox.Items.Insert(i - 1, item);
item.IsSelected = true;
reloadImage = true;
}
}
ImagesListBox.SelectedItems.Add(indices);
if (reloadImage)
{
await DrawPreview().ConfigureAwait(false);
}
break;
}
case "DownButton":
{
if (indices.Length > 0 && indices[^1] < ImagesListBox.Items.Count - 1)
{
for (var i = ImagesListBox.Items.Count - 1; i > -1; --i)
{
if (!indices.Contains(i)) continue;
var item = (ListBoxItem) ImagesListBox.Items[i];
ImagesListBox.Items.Remove(item);
ImagesListBox.Items.Insert(i + 1, item);
item.IsSelected = true;
reloadImage = true;
}
}
if (reloadImage)
{
await DrawPreview().ConfigureAwait(false);
}
break;
}
case "DeleteButton":
{
if (ImagesListBox.Items.Count > 0 && ImagesListBox.SelectedItems.Count > 0)
{
for (var i = ImagesListBox.SelectedItems.Count - 1; i >= 0; --i)
ImagesListBox.Items.Remove(ImagesListBox.SelectedItems[i]);
}
await DrawPreview().ConfigureAwait(false);
break;
}
}
}
}
private void OnClear(object sender, RoutedEventArgs e)
{
ImagesListBox.Items.Clear();
ImagePreview.Source = null;
}
private void OnOpenImage(object sender, RoutedEventArgs e)
{
if (ImagePreview.Source == null) return;
Helper.OpenWindow<AdonisWindow>("Merged Image", () =>
{
new ImagePopout
{
Title = "Merged Image",
Width = ImagePreview.Source.Width,
Height = ImagePreview.Source.Height,
WindowState = ImagePreview.Source.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
ImageCtrl = { Source = ImagePreview.Source }
}.Show();
});
}
private void OnSaveImage(object sender, RoutedEventArgs e)
{
Application.Current.Dispatcher.Invoke(delegate
{
if (ImagePreview.Source == null) return;
var saveFileDialog = new SaveFileDialog
{
Title = "Save Image",
FileName = FILENAME,
InitialDirectory = UserSettings.Default.OutputDirectory,
Filter = "Png Files (*.png)|*.png|All Files (*.*)|*.*"
};
var result = saveFileDialog.ShowDialog();
if (!result.HasValue || !result.Value) return;
using (var fs = new FileStream(saveFileDialog.FileName, FileMode.Create, FileAccess.Write, FileShare.Read))
{
fs.Write(_imageBuffer, 0, _imageBuffer.Length);
}
SaveCheck(saveFileDialog.FileName, Path.GetFileName(saveFileDialog.FileName));
});
}
private static void SaveCheck(string path, string fileName)
{
if (File.Exists(path))
{
Log.Information("{FileName} successfully saved", fileName);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
}
else
{
Log.Error("{FileName} could not be saved", fileName);
FLogger.AppendError();
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
}
}
private void OnCopyImage(object sender, RoutedEventArgs e)
{
ClipboardExtensions.SetImage(_imageBuffer, FILENAME);
}
}

View File

@ -11,71 +11,70 @@ using FModel.Views.Resources.Controls;
using Microsoft.Win32;
using Serilog;
namespace FModel.Views
namespace FModel.Views;
public partial class MapViewer
{
public partial class MapViewer
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public MapViewer()
{
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
DataContext = _applicationView;
_applicationView.MapViewer.Initialize();
public MapViewer()
InitializeComponent();
}
private void OnClosing(object sender, CancelEventArgs e) => DiscordService.DiscordHandler.UpdateToSavedPresence();
private void OnClick(object sender, RoutedEventArgs e)
{
if (_applicationView.MapViewer.MapImage == null) return;
var path = Path.Combine(UserSettings.Default.TextureDirectory, "MiniMap.png");
var saveFileDialog = new SaveFileDialog
{
DataContext = _applicationView;
_applicationView.MapViewer.Initialize();
Title = "Save MiniMap",
FileName = "MiniMap.png",
InitialDirectory = path.SubstringBeforeLast('\\'),
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
};
InitializeComponent();
if (!saveFileDialog.ShowDialog().GetValueOrDefault()) return;
path = saveFileDialog.FileName;
using var fileStream = new FileStream(path, FileMode.Create);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(_applicationView.MapViewer.GetImageToSave()));
encoder.Save(fileStream);
if (File.Exists(path))
{
Log.Information("MiniMap.png successfully saved");
FLogger.AppendInformation();
FLogger.AppendText("Successfully saved 'MiniMap.png'", Constants.WHITE, true);
}
private void OnClosing(object sender, CancelEventArgs e) => DiscordService.DiscordHandler.UpdateToSavedPresence();
private void OnClick(object sender, RoutedEventArgs e)
else
{
if (_applicationView.MapViewer.MapImage == null) return;
var path = Path.Combine(UserSettings.Default.TextureDirectory, "MiniMap.png");
var saveFileDialog = new SaveFileDialog
{
Title = "Save MiniMap",
FileName = "MiniMap.png",
InitialDirectory = path.SubstringBeforeLast('\\'),
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
};
if (!(bool) saveFileDialog.ShowDialog()) return;
path = saveFileDialog.FileName;
using var fileStream = new FileStream(path, FileMode.Create);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(_applicationView.MapViewer.GetImageToSave()));
encoder.Save(fileStream);
if (File.Exists(path))
{
Log.Information("MiniMap.png successfully saved");
FLogger.AppendInformation();
FLogger.AppendText("Successfully saved 'MiniMap.png'", Constants.WHITE, true);
}
else
{
Log.Error("MiniMap.png could not be saved");
FLogger.AppendError();
FLogger.AppendText("Could not save 'MiniMap.png'", Constants.WHITE, true);
}
}
private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var i = 0;
foreach (var item in MapTree.Items)
{
if (item is not TreeViewItem {IsSelected: true})
{
i++;
continue;
}
_applicationView.MapViewer.MapIndex = i;
break;
}
Log.Error("MiniMap.png could not be saved");
FLogger.AppendError();
FLogger.AppendText("Could not save 'MiniMap.png'", Constants.WHITE, true);
}
}
}
private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var i = 0;
foreach (var item in MapTree.Items)
{
if (item is not TreeViewItem { IsSelected: true })
{
i++;
continue;
}
_applicationView.MapViewer.MapIndex = i;
break;
}
}
}

View File

@ -10,107 +10,106 @@ using HelixToolkit.Wpf.SharpDX;
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
namespace FModel.Views
namespace FModel.Views;
public partial class ModelViewer
{
public partial class ModelViewer
private bool _messageShown;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public ModelViewer()
{
private bool _messageShown;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
DataContext = _applicationView;
InitializeComponent();
}
public ModelViewer()
public void Load(UObject export) => _applicationView.ModelViewer.LoadExport(export);
public void Overwrite(UMaterialInstance materialInstance)
{
if (_applicationView.ModelViewer.TryOverwriteMaterial(materialInstance))
{
DataContext = _applicationView;
InitializeComponent();
}
public void Load(UObject export) => _applicationView.ModelViewer.LoadExport(export);
public void Overwrite(UMaterialInstance materialInstance)
{
if (_applicationView.ModelViewer.TryOverwriteMaterial(materialInstance))
{
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = false;
}
else
{
MessageBox.Show(new MessageBoxModel
{
Text = "An attempt to load a material failed.",
Caption = "Error",
Icon = MessageBoxImage.Error,
Buttons = MessageBoxButtons.OkCancel(),
IsSoundEnabled = false
});
}
}
private void OnClosing(object sender, CancelEventArgs e)
{
_applicationView.ModelViewer.Clear();
_applicationView.ModelViewer.AppendMode = false;
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = false;
MyAntiCrashGroup.ItemsSource = null; // <3
}
private async void OnWindowKeyDown(object sender, KeyEventArgs e)
else
{
switch (e.Key)
MessageBox.Show(new MessageBoxModel
{
case Key.W:
_applicationView.ModelViewer.WirefreameToggle();
break;
case Key.H:
_applicationView.ModelViewer.RenderingToggle();
break;
case Key.D:
_applicationView.ModelViewer.DiffuseOnlyToggle();
break;
case Key.M:
_applicationView.ModelViewer.MaterialColorToggle();
break;
case Key.Decimal:
_applicationView.ModelViewer.FocusOnSelectedMesh();
break;
case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift):
_applicationView.ModelViewer.SaveAsScene();
break;
case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control):
await _applicationView.ModelViewer.SaveLoadedModels();
break;
}
}
private void OnMouse3DDown(object sender, MouseDown3DEventArgs e)
{
if (!Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) || e.HitTestResult.ModelHit is not CustomMeshGeometryModel3D m) return;
_applicationView.ModelViewer.SelectedModel.SelectedGeometry = m;
MaterialsListName.ScrollIntoView(m);
}
private void OnFocusClick(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.FocusOnSelectedMesh();
private void OnCopyClick(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.CopySelectedMaterialName();
private async void Save(object sender, RoutedEventArgs e)
=> await _applicationView.ModelViewer.SaveLoadedModels();
private void OnOverwriteMaterialClick(object sender, RoutedEventArgs e)
{
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = true;
if (!_messageShown)
{
MessageBox.Show(new MessageBoxModel
{
Text = "Simply extract a material once FModel will be brought to the foreground. This message will be shown once per Model Viewer's lifetime, close it to begin.",
Caption = "How To Overwrite Material?",
Icon = MessageBoxImage.Information,
IsSoundEnabled = false
});
_messageShown = true;
}
MainWindow.YesWeCats.Activate();
Text = "An attempt to load a material failed.",
Caption = "Error",
Icon = MessageBoxImage.Error,
Buttons = MessageBoxButtons.OkCancel(),
IsSoundEnabled = false
});
}
}
}
private void OnClosing(object sender, CancelEventArgs e)
{
_applicationView.ModelViewer.Clear();
_applicationView.ModelViewer.AppendMode = false;
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = false;
MyAntiCrashGroup.ItemsSource = null; // <3
}
private async void OnWindowKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.W:
_applicationView.ModelViewer.WirefreameToggle();
break;
case Key.H:
_applicationView.ModelViewer.RenderingToggle();
break;
case Key.D:
_applicationView.ModelViewer.DiffuseOnlyToggle();
break;
case Key.M:
_applicationView.ModelViewer.MaterialColorToggle();
break;
case Key.Decimal:
_applicationView.ModelViewer.FocusOnSelectedMesh();
break;
case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift):
_applicationView.ModelViewer.SaveAsScene();
break;
case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control):
await _applicationView.ModelViewer.SaveLoadedModels();
break;
}
}
private void OnMouse3DDown(object sender, MouseDown3DEventArgs e)
{
if (!Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) || e.HitTestResult.ModelHit is not CustomMeshGeometryModel3D m) return;
_applicationView.ModelViewer.SelectedModel.SelectedGeometry = m;
MaterialsListName.ScrollIntoView(m);
}
private void OnFocusClick(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.FocusOnSelectedMesh();
private void OnCopyClick(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.CopySelectedMaterialName();
private async void Save(object sender, RoutedEventArgs e)
=> await _applicationView.ModelViewer.SaveLoadedModels();
private void OnOverwriteMaterialClick(object sender, RoutedEventArgs e)
{
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = true;
if (!_messageShown)
{
MessageBox.Show(new MessageBoxModel
{
Text = "Simply extract a material once FModel will be brought to the foreground. This message will be shown once per Model Viewer's lifetime, close it to begin.",
Caption = "How To Overwrite Material?",
Icon = MessageBoxImage.Information,
IsSoundEnabled = false
});
_messageShown = true;
}
MainWindow.YesWeCats.Activate();
}
}

View File

@ -3,127 +3,128 @@ using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;
namespace FModel.Views.Resources.Controls
namespace FModel.Views.Resources.Controls;
public class JsonFoldingStrategies
{
public class JsonFoldingStrategies
{
private readonly BraceFoldingStrategy _strategy;
private readonly FoldingManager _foldingManager;
private readonly BraceFoldingStrategy _strategy;
private readonly FoldingManager _foldingManager;
public JsonFoldingStrategies(TextEditor avalonEditor)
{
_foldingManager = FoldingManager.Install(avalonEditor.TextArea);
_strategy = new BraceFoldingStrategy(avalonEditor);
}
public void UpdateFoldings(TextDocument document)
{
_foldingManager.UpdateFoldings(_strategy.UpdateFoldings(document), -1);
}
public void UnfoldAll()
{
if (_foldingManager.AllFoldings == null)
return;
foreach (var folding in _foldingManager.AllFoldings)
{
folding.IsFolded = false;
}
}
public void FoldToggle(int offset)
{
if (_foldingManager.AllFoldings == null)
return;
var foldSection = _foldingManager.GetFoldingsContaining(offset);
if (foldSection.Count > 0)
foldSection[^1].IsFolded = !foldSection[^1].IsFolded;
}
public void FoldToggleAtLevel(int level = 0)
{
if (_foldingManager.AllFoldings == null)
return;
var dowhat = -1;
var foldunfold = false;
foreach (var folding in _foldingManager.AllFoldings)
{
if (folding.Tag is not CustomNewFolding realFolding || realFolding.Level != level) continue;
if (dowhat < 0) // determine if we fold or unfold based on the first one
{
dowhat = 1;
foldunfold = !folding.IsFolded;
}
folding.IsFolded = foldunfold;
}
}
}
public class BraceFoldingStrategy
{
public BraceFoldingStrategy(TextEditor editor)
{
UpdateFoldings(editor.Document);
}
public IEnumerable<CustomNewFolding> UpdateFoldings(TextDocument document)
{
return CreateNewFoldings(document);
}
public IEnumerable<CustomNewFolding> CreateNewFoldings(ITextSource document)
{
var newFoldings = new List<CustomNewFolding>();
var startOffsets = new Stack<int>();
var lastNewLineOffset = 0;
var level = -1;
for (var i = 0; i < document.TextLength; i++)
{
var c = document.GetCharAt(i);
switch (c)
{
case '{' or '[':
level++;
startOffsets.Push(i);
break;
case '}' or ']' when startOffsets.Count > 0:
{
var startOffset = startOffsets.Pop();
if (startOffset < lastNewLineOffset)
{
newFoldings.Add(new CustomNewFolding(startOffset, i + 1, level));
}
level--;
break;
}
case '\n' or '\r':
lastNewLineOffset = i + 1;
break;
}
}
newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset));
return newFoldings;
}
public JsonFoldingStrategies(TextEditor avalonEditor)
{
_foldingManager = FoldingManager.Install(avalonEditor.TextArea);
_strategy = new BraceFoldingStrategy(avalonEditor);
}
public class CustomNewFolding : NewFolding
{
public int Level { get; }
public CustomNewFolding(int start, int end, int level) : base(start, end)
{
Level = level;
}
public void UpdateFoldings(TextDocument document)
{
_foldingManager.UpdateFoldings(_strategy.UpdateFoldings(document), -1);
}
public override string ToString()
{
return $"[{Level}] {StartOffset} -> {EndOffset}";
}
}
public void UnfoldAll()
{
if (_foldingManager.AllFoldings == null)
return;
foreach (var folding in _foldingManager.AllFoldings)
{
folding.IsFolded = false;
}
}
public void FoldToggle(int offset)
{
if (_foldingManager.AllFoldings == null)
return;
var foldSection = _foldingManager.GetFoldingsContaining(offset);
if (foldSection.Count > 0)
foldSection[^1].IsFolded = !foldSection[^1].IsFolded;
}
public void FoldToggleAtLevel(int level = 0)
{
if (_foldingManager.AllFoldings == null)
return;
var dowhat = -1;
var foldunfold = false;
foreach (var folding in _foldingManager.AllFoldings)
{
if (folding.Tag is not CustomNewFolding realFolding || realFolding.Level != level) continue;
if (dowhat < 0) // determine if we fold or unfold based on the first one
{
dowhat = 1;
foldunfold = !folding.IsFolded;
}
folding.IsFolded = foldunfold;
}
}
}
public class BraceFoldingStrategy
{
public BraceFoldingStrategy(TextEditor editor)
{
UpdateFoldings(editor.Document);
}
public IEnumerable<CustomNewFolding> UpdateFoldings(TextDocument document)
{
return CreateNewFoldings(document);
}
public IEnumerable<CustomNewFolding> CreateNewFoldings(ITextSource document)
{
var newFoldings = new List<CustomNewFolding>();
var startOffsets = new Stack<int>();
var lastNewLineOffset = 0;
var level = -1;
for (var i = 0; i < document.TextLength; i++)
{
var c = document.GetCharAt(i);
switch (c)
{
case '{' or '[':
level++;
startOffsets.Push(i);
break;
case '}' or ']' when startOffsets.Count > 0:
{
var startOffset = startOffsets.Pop();
if (startOffset < lastNewLineOffset)
{
newFoldings.Add(new CustomNewFolding(startOffset, i + 1, level));
}
level--;
break;
}
case '\n' or '\r':
lastNewLineOffset = i + 1;
break;
}
}
newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset));
return newFoldings;
}
}
public class CustomNewFolding : NewFolding
{
public int Level { get; }
public CustomNewFolding(int start, int end, int level) : base(start, end)
{
Level = level;
}
public override string ToString()
{
return $"[{Level}] {StartOffset} -> {EndOffset}";
}
}

View File

@ -1,39 +1,36 @@
using System.Text.RegularExpressions;
using ICSharpCode.AvalonEdit.Rendering;
namespace FModel.Views.Resources.Controls
namespace FModel.Views.Resources.Controls;
public class GamePathElementGenerator : VisualLineElementGenerator
{
public class GamePathElementGenerator : VisualLineElementGenerator
private readonly Regex _gamePathRegex =
new("\"(?:ObjectPath|AssetPathName|AssetName|ParameterName|CollisionProfileName|TableId)\": \"(?'target'(?!/?Script/)(.*/.*))\",?$",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
public GamePathElementGenerator()
{
private readonly Regex _gamePathRegex =
new("\"(?:ObjectPath|AssetPathName|AssetName|ParameterName|CollisionProfileName|TableId)\": \"(?'target'(?!/?Script/)(.*/.*))\",?$",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
public GamePathElementGenerator()
{
}
private Match FindMatch(int startOffset)
{
var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);
return _gamePathRegex.Match(relevantText);
}
private Match FindMatch(int startOffset)
{
var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);
return _gamePathRegex.Match(relevantText);
}
public override int GetFirstInterestedOffset(int startOffset)
{
var m = FindMatch(startOffset);
return m.Success ? startOffset + m.Index : -1;
}
public override int GetFirstInterestedOffset(int startOffset)
{
var m = FindMatch(startOffset);
return m.Success ? startOffset + m.Index : -1;
}
public override VisualLineElement ConstructElement(int offset)
{
var m = FindMatch(offset);
if (!m.Success || m.Index != 0) return null;
public override VisualLineElement ConstructElement(int offset)
{
var m = FindMatch(offset);
if (!m.Success || m.Index != 0) return null;
return m.Groups.TryGetValue("target", out var g) ?
new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) :
null;
}
return m.Groups.TryGetValue("target", out var g) ? new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) : null;
}
}

View File

@ -7,73 +7,73 @@ using FModel.Services;
using FModel.ViewModels;
using ICSharpCode.AvalonEdit.Rendering;
namespace FModel.Views.Resources.Controls
namespace FModel.Views.Resources.Controls;
public class GamePathVisualLineText : VisualLineText
{
public class GamePathVisualLineText : VisualLineText
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public delegate void GamePathOnClick(string gamePath);
public event GamePathOnClick OnGamePathClicked;
private readonly string _gamePath;
public GamePathVisualLineText(string gamePath, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public delegate void GamePathOnClick(string gamePath);
public event GamePathOnClick OnGamePathClicked;
private readonly string _gamePath;
public GamePathVisualLineText(string gamePath, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
{
_gamePath = gamePath;
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
TextRunProperties.SetForegroundBrush(Brushes.Plum);
return base.CreateTextRun(startVisualColumn, context);
}
private bool GamePathIsClickable() => !string.IsNullOrEmpty(_gamePath) && Keyboard.Modifiers == ModifierKeys.None;
protected override void OnQueryCursor(QueryCursorEventArgs e)
{
if (!GamePathIsClickable()) return;
e.Handled = true;
e.Cursor = Cursors.Hand;
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left || !GamePathIsClickable())
return;
if (e.Handled || OnGamePathClicked == null)
return;
OnGamePathClicked(_gamePath);
e.Handled = true;
}
protected override VisualLineText CreateInstance(int length)
{
var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length);
a.OnGamePathClicked += async gamePath =>
{
var obj = gamePath.SubstringAfterLast('.');
var package = gamePath.SubstringBeforeLast('.');
var fullPath = _applicationView.CUE4Parse.Provider.FixPath(package, StringComparison.Ordinal);
if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase))
{
var lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj);
var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
}
else
{
await _threadWorkerView.Begin(_ =>
_applicationView.CUE4Parse.ExtractAndScroll(fullPath, obj));
}
};
return a;
}
_gamePath = gamePath;
}
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
TextRunProperties.SetForegroundBrush(Brushes.Plum);
return base.CreateTextRun(startVisualColumn, context);
}
private bool GamePathIsClickable() => !string.IsNullOrEmpty(_gamePath) && Keyboard.Modifiers == ModifierKeys.None;
protected override void OnQueryCursor(QueryCursorEventArgs e)
{
if (!GamePathIsClickable()) return;
e.Handled = true;
e.Cursor = Cursors.Hand;
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left || !GamePathIsClickable())
return;
if (e.Handled || OnGamePathClicked == null)
return;
OnGamePathClicked(_gamePath);
e.Handled = true;
}
protected override VisualLineText CreateInstance(int length)
{
var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length);
a.OnGamePathClicked += async gamePath =>
{
var obj = gamePath.SubstringAfterLast('.');
var package = gamePath.SubstringBeforeLast('.');
var fullPath = _applicationView.CUE4Parse.Provider.FixPath(package, StringComparison.Ordinal);
if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase))
{
var lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj);
var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
}
else
{
await _threadWorkerView.Begin(_ =>
_applicationView.CUE4Parse.ExtractAndScroll(fullPath, obj));
}
};
return a;
}
}

View File

@ -1,39 +1,37 @@
using System.Text.RegularExpressions;
using ICSharpCode.AvalonEdit.Rendering;
namespace FModel.Views.Resources.Controls
namespace FModel.Views.Resources.Controls;
public class HexColorElementGenerator : VisualLineElementGenerator
{
public class HexColorElementGenerator : VisualLineElementGenerator
private readonly Regex _hexColorRegex = new("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
public HexColorElementGenerator()
{
private readonly Regex _hexColorRegex =
new Regex("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
public HexColorElementGenerator()
{
}
private Match FindMatch(int startOffset)
{
var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);
return _hexColorRegex.Match(relevantText);
}
private Match FindMatch(int startOffset)
{
var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);
return _hexColorRegex.Match(relevantText);
}
public override int GetFirstInterestedOffset(int startOffset)
{
var m = FindMatch(startOffset);
return m.Success ? startOffset + m.Index : -1;
}
public override int GetFirstInterestedOffset(int startOffset)
{
var m = FindMatch(startOffset);
return m.Success ? startOffset + m.Index : -1;
}
public override VisualLineElement ConstructElement(int offset)
{
var m = FindMatch(offset);
if (!m.Success || m.Index != 0) return null;
public override VisualLineElement ConstructElement(int offset)
{
var m = FindMatch(offset);
if (!m.Success || m.Index != 0) return null;
return m.Groups.TryGetValue("target", out var g) ?
new HexColorVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) :
null;
}
return m.Groups.TryGetValue("target", out var g) ?
new HexColorVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) :
null;
}
}

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