mirror of
https://github.com/4sval/FModel.git
synced 2026-04-26 08:13:27 -05:00
commit
c014478abc
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
|
|
@ -22,15 +22,15 @@ jobs:
|
|||
run: git submodule update --init --recursive
|
||||
|
||||
- name: .NET 6 Setup
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 763aca666c1782ce51287100641bc31d9fce0d41
|
||||
Subproject commit d1251ca4502cc374e65e8bdd6c920fffce20a6f6
|
||||
|
|
@ -16,119 +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);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
|
||||
UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
|
||||
if (!Directory.Exists(UserSettings.Default.ModelDirectory))
|
||||
UserSettings.Default.ModelDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Saves");
|
||||
|
||||
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Saves"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Textures"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Sounds"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
||||
|
||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
|
||||
Path.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 = GetRegistryValue(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", RegistryHive.LocalMachine);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(productName))
|
||||
productName = Environment.OSVersion.VersionString;
|
||||
|
||||
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -1,43 +1,42 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Creator.Bases.FN;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.BB
|
||||
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")};
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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 SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
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);
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
|
|
@ -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 SKImage 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)
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
DrawDisplayName(c);
|
||||
DrawQuests(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.GameplayTags;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
|
|
@ -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 SKImage 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)
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
if (_design == null)
|
||||
{
|
||||
base.Draw(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
if (_design.DrawSeason && _design.Fonts.TryGetValue("Season", out var font))
|
||||
DrawToBottom(c, font, _season);
|
||||
if (_design.DrawSource && _design.Fonts.TryGetValue("Source", out font))
|
||||
DrawToBottom(c, font, _source);
|
||||
DrawUserFacingFlags(c, _design.GameplayTags.DrawCustomOnly);
|
||||
}
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
|
@ -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 SKImage 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"))
|
||||
{
|
||||
using 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 SKImage.FromBitmap(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 = SKBitmap.Decode(texture2D.Decode()?.Encode());
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,271 +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 float dmgPb, "DmgPB") && dmgPb != 0f && weaponRowValue.TryGetValue(out int bpc , "BulletsPerCartridge"))
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "BF7E3CF34A9ACFF52E95EAAD4F09F133", "Damage to Player"), dmgPb * (bpc != 0f ? bpc : 1), 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 SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
DrawDisplayName(c);
|
||||
DrawStatistics(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private bool TryGetCurveTableStat(FStructFallback property, out float statValue)
|
||||
{
|
||||
if (property.TryGetValue(out FStructFallback curve, "Curve") &&
|
||||
curve.TryGetValue(out FName rowName, "RowName") &&
|
||||
curve.TryGetValue(out UCurveTable curveTable, "CurveTable") &&
|
||||
curveTable.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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
_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 BaseItemAccessToken(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FPackageIndex accessItem, "access_item") &&
|
||||
Utils.TryGetPackageIndexExport(accessItem, out UObject uObject))
|
||||
{
|
||||
_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);
|
||||
_exportName = uObject.Name;
|
||||
_icon = new BaseIcon(uObject, EIconStyle.Default);
|
||||
_icon.ParseForReward(false);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
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)
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private void DrawInformation(SKCanvas c)
|
||||
{
|
||||
var size = 45;
|
||||
var left = Width / 2;
|
||||
|
||||
while (DisplayNamePaint.MeasureText(DisplayName) > Width - _icon.Margin * 2)
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
Preview = _icon.Preview;
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
Preview = _icon.Preview;
|
||||
_icon.DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
break;
|
||||
default:
|
||||
_icon.DrawBackground(c);
|
||||
DrawInformation(c);
|
||||
DrawToBottom(c, SKTextAlign.Right, _exportName);
|
||||
break;
|
||||
}
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
DisplayNamePaint.TextSize = size -= 2;
|
||||
}
|
||||
|
||||
private void DrawInformation(SKCanvas c)
|
||||
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))
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +1,86 @@
|
|||
using System.Linq;
|
||||
using System.Linq;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
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 SKImage 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
|
||||
{
|
||||
using 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 SKImage.FromBitmap(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 };
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +1,84 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
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 SKImage Draw()
|
||||
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)
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
break;
|
||||
}
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
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 };
|
||||
}
|
||||
}
|
||||
39
FModel/Creator/Bases/FN/BaseOfferDisplayData.cs
Normal file
39
FModel/Creator/Bases/FN/BaseOfferDisplayData.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseOfferDisplayData : UCreator
|
||||
{
|
||||
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++)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
|
|
@ -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;
|
||||
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 BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
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)
|
||||
{
|
||||
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");
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
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 SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawPreview(c);
|
||||
DrawMissionIcon(c);
|
||||
break;
|
||||
default:
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
DrawMissionIcon(c);
|
||||
break;
|
||||
}
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private void DrawMissionIcon(SKCanvas c)
|
||||
{
|
||||
if (_missionIcon == null) return;
|
||||
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
|
||||
}
|
||||
private void DrawMissionIcon(SKCanvas c)
|
||||
{
|
||||
if (_missionIcon == null) return;
|
||||
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||
|
|
@ -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 SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawQuest(c, 0);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private string ReformatString(string s, string completionCount, bool isAll)
|
||||
{
|
||||
s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "<text color=\"FFF\" case=\"upper\" fontface=\"black\">{0}</>");
|
||||
var index = s.IndexOf("{0}|plural(", StringComparison.OrdinalIgnoreCase);
|
||||
if (index > -1)
|
||||
{
|
||||
var p = s.Substring(index, s[index..].IndexOf(')') + 1);
|
||||
s = s.Replace(p, string.Empty);
|
||||
s = s.Insert(s.IndexOf("</>", StringComparison.OrdinalIgnoreCase), p.SubstringAfter("(").SubstringAfter("=").SubstringBefore(","));
|
||||
}
|
||||
|
||||
var upper = s.SubstringAfter(">").SubstringBefore("</>");
|
||||
return string.Format(Utils.RemoveHtmlTags(s.Replace(upper, upper.ToUpper())), isAll ? string.Empty : completionCount).Replace(" ", " ");
|
||||
}
|
||||
|
||||
private readonly SKPaint _informationPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColor.Parse("#262630")
|
||||
};
|
||||
|
||||
private void DrawBackground(SKCanvas c, int y)
|
||||
{
|
||||
c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4,
|
||||
new[] {Background[0].WithAlpha(50), Background[1].WithAlpha(50)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Height / 2, y, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, y + Height), new SKPoint(Width / 2, 75),
|
||||
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, y + 75, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Margin, y, Height / 2, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Height / 2, y + Height / 2), new SKPoint(Height / 2 + 100, y + Height / 2),
|
||||
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Height / 2, y, Height / 2 + 100, y + Height), _informationPaint);
|
||||
}
|
||||
|
||||
private void DrawPreview(SKCanvas c, int y)
|
||||
{
|
||||
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
|
||||
c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, Height - Margin, y + Height), ImagePaint);
|
||||
}
|
||||
|
||||
private void DrawTexts(SKCanvas c, int y)
|
||||
{
|
||||
_informationPaint.Shader = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(DisplayName))
|
||||
{
|
||||
_informationPaint.TextSize = 40;
|
||||
_informationPaint.Color = SKColors.White;
|
||||
_informationPaint.Typeface = Utils.Typefaces.Bundle;
|
||||
while (_informationPaint.MeasureText(DisplayName) > Width - Height - 10)
|
||||
{
|
||||
_informationPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_informationPaint.Typeface);
|
||||
shaper.Shape(DisplayName, _informationPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint);
|
||||
}
|
||||
|
||||
var outY = y + 75f;
|
||||
if (!string.IsNullOrWhiteSpace(Description))
|
||||
{
|
||||
_informationPaint.TextSize = 16;
|
||||
_informationPaint.Color = SKColors.White.WithAlpha(175);
|
||||
_informationPaint.Typeface = Utils.Typefaces.Description;
|
||||
Utils.DrawMultilineText(c, Description, Width - Height, y, SKTextAlign.Left,
|
||||
new SKRect(Height, outY, Width - 10, y + Height), _informationPaint, out outY);
|
||||
}
|
||||
|
||||
_informationPaint.Color = Border[0].WithAlpha(100);
|
||||
c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint);
|
||||
|
||||
if (_count > 0)
|
||||
{
|
||||
_informationPaint.TextSize = 25;
|
||||
_informationPaint.Color = SKColors.White;
|
||||
_informationPaint.Typeface = Utils.Typefaces.BundleNumber;
|
||||
c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint);
|
||||
|
||||
_informationPaint.Color = Border[0];
|
||||
c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _informationPaint);
|
||||
}
|
||||
|
||||
_reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25));
|
||||
_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));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
|
|
@ -8,158 +8,152 @@ using FModel.Framework;
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
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
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
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)
|
||||
{
|
||||
Width = 1024;
|
||||
Height = _headerHeight + 50;
|
||||
Margin = 0;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
_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"))
|
||||
{
|
||||
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;
|
||||
|
||||
var p = new Page
|
||||
{
|
||||
LevelsNeededForUnlock = levelsNeededForUnlock,
|
||||
RewardsNeededForUnlock = rewardsNeededForUnlock,
|
||||
RewardEntryList = new Reward[rewardEntryList.Length]
|
||||
};
|
||||
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 SKImage 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)
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
_firstWinReward?.DrawSeasonWin(c, _headerHeight);
|
||||
DrawBookSchedule(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,26 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
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 void ParseForInfo()
|
||||
{
|
||||
GetSeries(Object);
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawBackground(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
return new []{ret};
|
||||
}
|
||||
}
|
||||
228
FModel/Creator/Bases/FN/BaseTandem.cs
Normal file
228
FModel/Creator/Bases/FN/BaseTandem.cs
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseTandem : BaseIcon
|
||||
{
|
||||
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 _);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName"))
|
||||
DisplayName = optionDisplayName.Text.ToUpperInvariant();
|
||||
if (Object.TryGetValue(out FText optionDescription, "OptionDescription"))
|
||||
{
|
||||
Width = 512;
|
||||
Height = 128;
|
||||
Margin = 32;
|
||||
Description = optionDescription.Text;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Description)) return;
|
||||
Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count;
|
||||
Height += (int) _descriptionPaint.TextSize;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues"))
|
||||
{
|
||||
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName"))
|
||||
DisplayName = optionDisplayName.Text.ToUpperInvariant();
|
||||
if (Object.TryGetValue(out FText optionDescription, "OptionDescription"))
|
||||
_optionValues = new List<Options>();
|
||||
foreach (var option in optionValues)
|
||||
{
|
||||
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"))
|
||||
{
|
||||
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);
|
||||
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});
|
||||
}
|
||||
_optionValues.Add(opt);
|
||||
}
|
||||
else if (option.TryGetValue(out FName primaryAssetName, "PrimaryAssetName"))
|
||||
{
|
||||
_optionValues.Add(new Options { Option = primaryAssetName.Text });
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText optionOnText, "OptionOnText"))
|
||||
_optionValues.Add(new Options {Option = optionOnText.Text});
|
||||
if (Object.TryGetValue(out FText optionOffText, "OptionOffText"))
|
||||
_optionValues.Add(new Options {Option = optionOffText.Text});
|
||||
|
||||
if (Object.TryGetValue(out int iMin, "Min", "DefaultValue") &&
|
||||
Object.TryGetValue(out int iMax, "Max"))
|
||||
{
|
||||
var increment = iMin;
|
||||
if (Object.TryGetValue(out int incrementValue, "IncrementValue"))
|
||||
increment = incrementValue;
|
||||
|
||||
var format = "{0}";
|
||||
if (Object.TryGetValue(out FText unitName, "UnitName"))
|
||||
format = unitName.Text;
|
||||
|
||||
for (var i = iMin; i <= iMax; i += increment)
|
||||
{
|
||||
_optionValues.Add(new Options {Option = string.Format(format, i)});
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out float fMin, "Min", "DefaultValue") &&
|
||||
Object.TryGetValue(out float fMax, "Max"))
|
||||
{
|
||||
var increment = fMin;
|
||||
if (Object.TryGetValue(out float incrementValue, "IncrementValue"))
|
||||
increment = incrementValue;
|
||||
|
||||
var format = "{0}";
|
||||
if (Object.TryGetValue(out FText unitName, "UnitName"))
|
||||
format = unitName.Text;
|
||||
|
||||
for (var i = fMin; i <= fMax; i += increment)
|
||||
{
|
||||
_optionValues.Add(new Options {Option = string.Format(format, i)});
|
||||
}
|
||||
}
|
||||
|
||||
Height += Margin;
|
||||
Height += 35 * _optionValues.Count;
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
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"))
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
var increment = iMin;
|
||||
if (Object.TryGetValue(out int incrementValue, "IncrementValue"))
|
||||
increment = incrementValue;
|
||||
|
||||
DrawBackground(c);
|
||||
DrawInformation(c);
|
||||
var format = "{0}";
|
||||
if (Object.TryGetValue(out FText unitName, "UnitName"))
|
||||
format = unitName.Text;
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private new void DrawBackground(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(Width / 2, Height),
|
||||
new SKPoint(Width, Height / 4),
|
||||
new[] {SKColor.Parse("01369C"), SKColor.Parse("1273C8")},
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawInformation(SKCanvas c)
|
||||
{
|
||||
// display name
|
||||
while (_displayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
|
||||
for (var i = iMin; i <= iMax; i += increment)
|
||||
{
|
||||
_displayNamePaint.TextSize -= 2;
|
||||
_optionValues.Add(new Options { Option = string.Format(format, i) });
|
||||
}
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_displayNamePaint.Typeface);
|
||||
shaper.Shape(DisplayName, _displayNamePaint);
|
||||
c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint);
|
||||
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});
|
||||
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;
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
// options
|
||||
foreach (var option in _optionValues)
|
||||
{
|
||||
option.Draw(c, Margin, Width, ref top);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Options
|
||||
public class Options
|
||||
{
|
||||
private const int _SPACE = 5;
|
||||
private const int _HEIGHT = 30;
|
||||
|
||||
private readonly SKPaint _optionPaint = new()
|
||||
{
|
||||
private const int _SPACE = 5;
|
||||
private const int _HEIGHT = 30;
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = 20,
|
||||
Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left
|
||||
};
|
||||
|
||||
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 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +1,47 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.SB
|
||||
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);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
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"))
|
||||
{
|
||||
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;
|
||||
Background = new[] { SKColor.Parse(lightColor.Hex), SKColor.Parse(cardColor.Hex) };
|
||||
Border = new[] { SKColor.Parse(mediumColor.Hex), SKColor.Parse(darkColor.Hex) };
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
}
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
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 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 SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
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);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawIcon(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private void DrawIcon(SKCanvas c)
|
||||
{
|
||||
if (_icon == null) return;
|
||||
c.DrawBitmap(_icon, new SKPoint(5, 5), ImagePaint);
|
||||
}
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private void DrawIcon(SKCanvas c)
|
||||
{
|
||||
if (_icon == null) return;
|
||||
c.DrawBitmap(_icon, new SKPoint(5, 5), ImagePaint);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
_promotionXp = 0;
|
||||
_xpLostPerMatch = 0;
|
||||
}
|
||||
|
||||
public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
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))
|
||||
{
|
||||
_promotionXp = 0;
|
||||
_xpLostPerMatch = 0;
|
||||
var d = new BaseDivision(div, Style);
|
||||
d.ParseForInfo();
|
||||
Preview = d.Preview;
|
||||
Background = d.Background;
|
||||
Border = d.Border;
|
||||
}
|
||||
|
||||
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 FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
}
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
DrawToBottom(c, SKTextAlign.Left, $"PromotionXP: {_promotionXp}");
|
||||
DrawToBottom(c, SKTextAlign.Right, $"XPLostPerMatch: {_xpLostPerMatch}");
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
|
||||
DrawToBottom(c, SKTextAlign.Left, $"PromotionXP: {_promotionXp}");
|
||||
DrawToBottom(c, SKTextAlign.Right, $"XPLostPerMatch: {_xpLostPerMatch}");
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
|
|
@ -7,91 +7,90 @@ 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 SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackgrounds(c);
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private void DrawBackgrounds(SKCanvas c)
|
||||
{
|
||||
if (SeriesBackground != null)
|
||||
c.DrawBitmap(SeriesBackground, new SKRect(0, 0, Width, Height), ImagePaint);
|
||||
if (_seriesBackground2 != null)
|
||||
c.DrawBitmap(_seriesBackground2, new SKRect(0, 0, Width, Height), _overlayPaint);
|
||||
|
||||
var x = Margin * (int) 2.5;
|
||||
const int radi = 15;
|
||||
c.DrawCircle(x + radi, x + radi, radi, new SKPaint
|
||||
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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using FModel.Creator.Bases.FN;
|
||||
|
|
@ -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 SKImage 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,240 +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 "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 "FortTrapItemDefinition":
|
||||
case "FortTandemCharacterData":
|
||||
case "FortSpyTechItemDefinition":
|
||||
case "FortAccoladeItemDefinition":
|
||||
case "FortContextTrapItemDefinition":
|
||||
case "FortWeaponRangedItemDefinition":
|
||||
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
|
||||
creator = new BaseIconStats(_object, _style);
|
||||
return true;
|
||||
case "FortItemSeriesDefinition":
|
||||
creator = new BaseSeries(_object, _style);
|
||||
return true;
|
||||
case "MaterialInstanceConstant"
|
||||
when _object.Owner != null &&
|
||||
(_object.Owner.Name.EndsWith($"/MI_OfferImages/{_object.Name}", 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 "FortMtxOfferData":
|
||||
creator = new BaseMtxOffer(_object, _style);
|
||||
return true;
|
||||
case "FortPlaylistAthena":
|
||||
creator = new BasePlaylist(_object, _style);
|
||||
return true;
|
||||
case "FortFeatItemDefinition":
|
||||
case "FortQuestItemDefinition":
|
||||
case "AthenaDailyQuestDefinition":
|
||||
case "FortUrgentQuestItemDefinition":
|
||||
creator = new BaseQuest(_object, _style);
|
||||
return true;
|
||||
case "FortCompendiumItemDefinition":
|
||||
case "FortChallengeBundleItemDefinition":
|
||||
creator = new BaseBundle(_object, _style);
|
||||
return true;
|
||||
case "AthenaSeasonItemDefinition":
|
||||
creator = new BaseSeason(_object, _style);
|
||||
return true;
|
||||
case "FortItemAccessTokenType":
|
||||
creator = new BaseItemAccessToken(_object, _style);
|
||||
return true;
|
||||
case "PlaylistUserOptionEnum":
|
||||
case "PlaylistUserOptionBool":
|
||||
case "PlaylistUserOptionString":
|
||||
case "PlaylistUserOptionIntEnum":
|
||||
case "PlaylistUserOptionIntRange":
|
||||
case "PlaylistUserOptionColorEnum":
|
||||
case "PlaylistUserOptionFloatEnum":
|
||||
case "PlaylistUserOptionFloatRange":
|
||||
case "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;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,243 +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 Uri("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 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 +
|
||||
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 bottomPath = _FORTNITE_BASE_PATH +
|
||||
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;
|
||||
|
||||
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.ShooterGame:
|
||||
break;
|
||||
case FGame.DeadByDaylight:
|
||||
break;
|
||||
case FGame.OakGame:
|
||||
break;
|
||||
case FGame.Dungeons:
|
||||
break;
|
||||
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);
|
||||
}
|
||||
case FGame.StateOfDecay2:
|
||||
break;
|
||||
case FGame.Prospect:
|
||||
break;
|
||||
case FGame.Indiana:
|
||||
break;
|
||||
case FGame.RogueCompany:
|
||||
break;
|
||||
case FGame.SwGame:
|
||||
break;
|
||||
case FGame.Platform:
|
||||
break;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,278 +1,394 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse.Utils;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
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 (!TryGetPackageIndexExport(packageIndex, out UObject export)) return null;
|
||||
switch (export)
|
||||
{
|
||||
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"))
|
||||
{
|
||||
packageIndex = smallPreview;
|
||||
continue;
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
return null;
|
||||
preview = GetBitmap(material);
|
||||
return preview != null;
|
||||
}
|
||||
|
||||
public static SKBitmap GetBitmap(FPackageIndex packageIndex)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!TryGetPackageIndexExport(packageIndex, out UObject export)) return null;
|
||||
switch (export)
|
||||
{
|
||||
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"))
|
||||
{
|
||||
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 : SKBitmap.Decode(texture.Decode()?.Encode());
|
||||
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 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)
|
||||
{
|
||||
if (line == null) continue;
|
||||
var lineText = line.Trim();
|
||||
var shaper = new CustomSKShaper(paint.Typeface);
|
||||
var shapedText = shaper.Shape(lineText, paint);
|
||||
|
||||
var x = side switch
|
||||
{
|
||||
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
|
||||
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
|
||||
SKTextAlign.Left => area.Left,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, lineText, x, yPos, paint);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
c.DrawRect(new SKRect(area.Left, area.Top - paint.TextSize, area.Right, yPos), new SKPaint {Color = SKColors.Red, IsStroke = true});
|
||||
#endif
|
||||
}
|
||||
|
||||
public static List<string> SplitLines(string text, SKPaint paint, float maxWidth)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return null;
|
||||
|
||||
var spaceWidth = paint.MeasureText(" ");
|
||||
var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var ret = new List<string>(lines.Length);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
float width = 0;
|
||||
var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
var lineResult = new StringBuilder();
|
||||
foreach (var word in words)
|
||||
{
|
||||
var wordWidth = paint.MeasureText(word);
|
||||
var wordWithSpaceWidth = wordWidth + spaceWidth;
|
||||
var wordWithSpace = word + " ";
|
||||
|
||||
if (width + wordWidth > maxWidth)
|
||||
{
|
||||
ret.Add(lineResult.ToString());
|
||||
lineResult = new StringBuilder(wordWithSpace);
|
||||
width = wordWithSpaceWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
lineResult.Append(wordWithSpace);
|
||||
width += wordWithSpaceWidth;
|
||||
}
|
||||
}
|
||||
|
||||
ret.Add(lineResult.ToString());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = @"(\ |[a-zA-Z0-9]+\.[a-z]{2,}|[一-龠々〆ヵヶゝ]+|[ぁ-んゝ]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[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;
|
||||
}
|
||||
}
|
||||
273
FModel/Enums.cs
273
FModel/Enums.cs
|
|
@ -1,140 +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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -4,47 +4,49 @@ 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");
|
||||
private static readonly IHighlightingDefinition _ulangHighlighter = LoadHighlighter("ULang.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 "ulang":
|
||||
return _ulangHighlighter;
|
||||
case "bat":
|
||||
case "txt":
|
||||
case "po":
|
||||
return null;
|
||||
default:
|
||||
return _jsonHighlighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,9 @@
|
|||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon>FModel.ico</ApplicationIcon>
|
||||
<Version>4.2.0</Version>
|
||||
<AssemblyVersion>4.2.0.0</AssemblyVersion>
|
||||
<FileVersion>4.2.0.0</FileVersion>
|
||||
<Version>4.3.0</Version>
|
||||
<AssemblyVersion>4.3.0.0</AssemblyVersion>
|
||||
<FileVersion>4.3.0.0</FileVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsPublishable>true</IsPublishable>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
|
@ -69,6 +69,7 @@
|
|||
<None Remove="Resources\athena.png" />
|
||||
<None Remove="Resources\Json.xshd" />
|
||||
<None Remove="Resources\Ini.xshd" />
|
||||
<None Remove="Resources\Ulang.xshd" />
|
||||
<None Remove="Resources\Xml.xshd" />
|
||||
<None Remove="Resources\Cpp.xshd" />
|
||||
<None Remove="Resources\Changelog.xshd" />
|
||||
|
|
@ -91,11 +92,13 @@
|
|||
<None Remove="Resources\delete.png" />
|
||||
<None Remove="Resources\edit.png" />
|
||||
<None Remove="Resources\go_to_directory.png" />
|
||||
<None Remove="Resources\npcleftside.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Json.xshd" />
|
||||
<EmbeddedResource Include="Resources\Ini.xshd" />
|
||||
<EmbeddedResource Include="Resources\ULang.xshd" />
|
||||
<EmbeddedResource Include="Resources\Xml.xshd" />
|
||||
<EmbeddedResource Include="Resources\Cpp.xshd" />
|
||||
<EmbeddedResource Include="Resources\Changelog.xshd" />
|
||||
|
|
@ -104,21 +107,21 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="AdonisUI" Version="1.17.1" />
|
||||
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.0" />
|
||||
<PackageReference Include="AvalonEdit" Version="6.1.2.30" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.3" />
|
||||
<PackageReference Include="AvalonEdit" Version="6.1.3.50" />
|
||||
<PackageReference Include="CSCore" Version="1.2.1.2" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
|
||||
<PackageReference Include="EpicManifestParser" Version="1.2.70-temp" />
|
||||
<PackageReference Include="HelixToolkit.SharpDX.Core.Wpf" Version="2.20.0" />
|
||||
<PackageReference Include="HelixToolkit.SharpDX.Core.Wpf" Version="2.21.0" />
|
||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.2.16" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.4" />
|
||||
<PackageReference Include="Oodle.NET" Version="1.0.1" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.0" />
|
||||
<PackageReference Include="RestSharp" Version="106.15.0" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||
<PackageReference Include="RestSharp" Version="108.0.1" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.3" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.0" />
|
||||
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
@ -187,6 +190,7 @@
|
|||
<Resource Include="Resources\edit.png" />
|
||||
<Resource Include="Resources\go_to_directory.png" />
|
||||
<Resource Include="Resources\approaching_storm_cubemap.dds" />
|
||||
<Resource Include="Resources\npcleftside.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,42 +2,29 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using RestSharp;
|
||||
using RestSharp.Serialization;
|
||||
using RestSharp.Serializers;
|
||||
|
||||
namespace FModel.Framework
|
||||
namespace FModel.Framework;
|
||||
|
||||
public class JsonNetSerializer : IRestSerializer, ISerializer, IDeserializer
|
||||
{
|
||||
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);
|
||||
}
|
||||
public string Serialize(Parameter parameter) => JsonConvert.SerializeObject(parameter.Value);
|
||||
public string Serialize(object obj) => JsonConvert.SerializeObject(obj);
|
||||
public T Deserialize<T>(RestResponse response) => JsonConvert.DeserializeObject<T>(response.Content!, SerializerSettings);
|
||||
|
||||
[Obsolete]
|
||||
public string Serialize(Parameter parameter)
|
||||
{
|
||||
return JsonConvert.SerializeObject(parameter.Value);
|
||||
}
|
||||
public ISerializer Serializer => this;
|
||||
public IDeserializer Deserializer => this;
|
||||
|
||||
public T Deserialize<T>(IRestResponse response)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(response.Content, SerializerSettings);
|
||||
}
|
||||
public string ContentType { get; set; } = "application/json";
|
||||
public string[] AcceptedContentTypes => RestSharp.Serializers.ContentType.JsonAccept;
|
||||
public SupportsContentType SupportsContentType => contentType => contentType.EndsWith("json", StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
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 DataFormat DataFormat => DataFormat.Json;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
151
FModel/Helper.cs
151
FModel/Helper.cs
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -663,7 +663,8 @@
|
|||
</Grid.RowDefinitions>
|
||||
|
||||
<TabControl Grid.Row="0" x:Name="TabControlName" Style="{StaticResource GameFilesTabControl}" />
|
||||
<Expander Grid.Row="1" Margin="0 5 0 5" ExpandDirection="Down">
|
||||
<Expander Grid.Row="1" Margin="0 5 0 5" ExpandDirection="Down"
|
||||
IsExpanded="{Binding IsLoggerExpanded, Source={x:Static local:Settings.UserSettings.Default}}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
|
|
|
|||
|
|
@ -11,234 +11,232 @@ using FModel.Settings;
|
|||
using FModel.ViewModels;
|
||||
using FModel.Views;
|
||||
using FModel.Views.Resources.Controls;
|
||||
using ICSharpCode.AvalonEdit.Editing;
|
||||
|
||||
namespace FModel
|
||||
namespace FModel;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow
|
||||
public static MainWindow YesWeCats;
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
public static MainWindow YesWeCats;
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
|
||||
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveProps", typeof(MainWindow), new InputGestureCollection { new KeyGesture(UserSettings.Default.AutoSaveProps.Key, UserSettings.Default.AutoSaveProps.Modifiers) }), OnAutoTriggerExecuted));
|
||||
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveTextures", typeof(MainWindow), new InputGestureCollection { new KeyGesture(UserSettings.Default.AutoSaveTextures.Key, UserSettings.Default.AutoSaveTextures.Modifiers) }), OnAutoTriggerExecuted));
|
||||
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoOpenSounds", typeof(MainWindow), new InputGestureCollection { new KeyGesture(UserSettings.Default.AutoOpenSounds.Key, UserSettings.Default.AutoOpenSounds.Modifiers) }), OnAutoTriggerExecuted));
|
||||
CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection { new KeyGesture(Key.F12) }), OnMappingsReload));
|
||||
CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (_, _) => OnOpenAvalonFinder()));
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveProps", typeof(MainWindow), new InputGestureCollection
|
||||
{new KeyGesture(UserSettings.Default.AutoSaveProps.Key, UserSettings.Default.AutoSaveProps.Modifiers)}), OnAutoTriggerExecuted));
|
||||
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveTextures", typeof(MainWindow), new InputGestureCollection
|
||||
{new KeyGesture(UserSettings.Default.AutoSaveTextures.Key, UserSettings.Default.AutoSaveTextures.Modifiers)}), OnAutoTriggerExecuted));
|
||||
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoOpenSounds", typeof(MainWindow), new InputGestureCollection
|
||||
{new KeyGesture(UserSettings.Default.AutoOpenSounds.Key, UserSettings.Default.AutoOpenSounds.Modifiers)}), OnAutoTriggerExecuted));
|
||||
CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection {new KeyGesture(Key.F12)}), OnMappingsReload));
|
||||
CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (s, e) => OnOpenAvalonFinder()));
|
||||
DataContext = _applicationView;
|
||||
InitializeComponent();
|
||||
|
||||
DataContext = _applicationView;
|
||||
InitializeComponent();
|
||||
FLogger.Logger = LogRtbName;
|
||||
YesWeCats = this;
|
||||
}
|
||||
|
||||
FLogger.Logger = LogRtbName;
|
||||
YesWeCats = this;
|
||||
}
|
||||
private void OnClosing(object sender, CancelEventArgs e)
|
||||
{
|
||||
_applicationView.CustomDirectories.Save();
|
||||
_discordHandler.Dispose();
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, CancelEventArgs e)
|
||||
{
|
||||
_applicationView.CustomDirectories.Save();
|
||||
_discordHandler.Dispose();
|
||||
}
|
||||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
#if !DEBUG
|
||||
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
|
||||
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
|
||||
#endif
|
||||
|
||||
switch (UserSettings.Default.AesReload)
|
||||
{
|
||||
case EAesReload.Always:
|
||||
await _applicationView.CUE4Parse.RefreshAes();
|
||||
break;
|
||||
case EAesReload.OncePerDay when UserSettings.Default.LastAesReload != DateTime.Today:
|
||||
UserSettings.Default.LastAesReload = DateTime.Today;
|
||||
await _applicationView.CUE4Parse.RefreshAes();
|
||||
break;
|
||||
}
|
||||
|
||||
await _applicationView.CUE4Parse.InitInformation();
|
||||
await _applicationView.CUE4Parse.Initialize();
|
||||
await _applicationView.AesManager.InitAes();
|
||||
await _applicationView.AesManager.UpdateProvider(true);
|
||||
await _applicationView.CUE4Parse.InitBenMappings();
|
||||
await _applicationView.InitVgmStream();
|
||||
await _applicationView.InitOodle();
|
||||
|
||||
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
|
||||
_discordHandler.Initialize(_applicationView.CUE4Parse.Game);
|
||||
switch (UserSettings.Default.AesReload)
|
||||
{
|
||||
case EAesReload.Always:
|
||||
await _applicationView.CUE4Parse.RefreshAes();
|
||||
break;
|
||||
case EAesReload.OncePerDay when UserSettings.Default.LastAesReload != DateTime.Today:
|
||||
UserSettings.Default.LastAesReload = DateTime.Today;
|
||||
await _applicationView.CUE4Parse.RefreshAes();
|
||||
break;
|
||||
}
|
||||
|
||||
private void OnGridSplitterDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
await _applicationView.CUE4Parse.Initialize();
|
||||
await _applicationView.AesManager.InitAes();
|
||||
await _applicationView.AesManager.UpdateProvider(true);
|
||||
await _applicationView.CUE4Parse.InitInformation();
|
||||
await _applicationView.CUE4Parse.InitBenMappings();
|
||||
await _applicationView.InitVgmStream();
|
||||
await _applicationView.InitOodle();
|
||||
|
||||
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
|
||||
_discordHandler.Initialize(_applicationView.CUE4Parse.Game);
|
||||
}
|
||||
|
||||
private void OnGridSplitterDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
RootGrid.ColumnDefinitions[0].Width = GridLength.Auto;
|
||||
}
|
||||
|
||||
private void OnWindowKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is TextBox || e.OriginalSource is TextArea && Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
|
||||
return;
|
||||
|
||||
if (_threadWorkerView.CanBeCanceled && e.Key == Key.Escape)
|
||||
{
|
||||
RootGrid.ColumnDefinitions[0].Width = GridLength.Auto;
|
||||
_applicationView.Status = EStatusKind.Stopping;
|
||||
_threadWorkerView.Cancel();
|
||||
}
|
||||
|
||||
private void OnWindowKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is TextBox)
|
||||
return;
|
||||
|
||||
if (_threadWorkerView.CanBeCanceled && e.Key == Key.Escape)
|
||||
{
|
||||
_applicationView.Status = EStatusKind.Stopping;
|
||||
_threadWorkerView.Cancel();
|
||||
}
|
||||
else if (_applicationView.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
|
||||
OnSearchViewClick(null, null);
|
||||
else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key))
|
||||
_applicationView.CUE4Parse.TabControl.AddTab();
|
||||
else if (UserSettings.Default.AssetRemoveTab.IsTriggered(e.Key))
|
||||
_applicationView.CUE4Parse.TabControl.RemoveTab();
|
||||
else if (UserSettings.Default.AssetLeftTab.IsTriggered(e.Key))
|
||||
_applicationView.CUE4Parse.TabControl.GoLeftTab();
|
||||
else if (UserSettings.Default.AssetRightTab.IsTriggered(e.Key))
|
||||
_applicationView.CUE4Parse.TabControl.GoRightTab();
|
||||
else if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex > 0)
|
||||
LeftTabControl.SelectedIndex--;
|
||||
else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex < LeftTabControl.Items.Count - 1)
|
||||
LeftTabControl.SelectedIndex++;
|
||||
}
|
||||
|
||||
private void OnSearchViewClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Helper.OpenWindow<AdonisWindow>("Search View", () => new SearchView().Show());
|
||||
}
|
||||
|
||||
private void OnTabItemChange(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is not TabControl tabControl)
|
||||
return;
|
||||
|
||||
(tabControl.SelectedItem as System.Windows.Controls.TabItem)?.Focus();
|
||||
}
|
||||
|
||||
private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
await _applicationView.CUE4Parse.InitBenMappings();
|
||||
}
|
||||
|
||||
private void OnAutoTriggerExecuted(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
switch ((e.Command as RoutedCommand)?.Name)
|
||||
{
|
||||
case "AutoSaveProps":
|
||||
UserSettings.Default.IsAutoSaveProps = !UserSettings.Default.IsAutoSaveProps;
|
||||
break;
|
||||
case "AutoSaveTextures":
|
||||
UserSettings.Default.IsAutoSaveTextures = !UserSettings.Default.IsAutoSaveTextures;
|
||||
break;
|
||||
case "AutoOpenSounds":
|
||||
UserSettings.Default.IsAutoOpenSounds = !UserSettings.Default.IsAutoOpenSounds;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenAvalonFinder()
|
||||
{
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.HasSearchOpen = true;
|
||||
AvalonEditor.YesWeSearch.Focus();
|
||||
AvalonEditor.YesWeSearch.SelectAll();
|
||||
}
|
||||
|
||||
private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is not TreeView {SelectedItem: TreeItem treeItem} || treeItem.Folders.Count > 0) return;
|
||||
|
||||
else if (_applicationView.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
|
||||
OnSearchViewClick(null, null);
|
||||
else if (e.Key == Key.Left && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage)
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.GoPreviousImage();
|
||||
else if (e.Key == Key.Right && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage)
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.GoNextImage();
|
||||
else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key))
|
||||
_applicationView.CUE4Parse.TabControl.AddTab();
|
||||
else if (UserSettings.Default.AssetRemoveTab.IsTriggered(e.Key))
|
||||
_applicationView.CUE4Parse.TabControl.RemoveTab();
|
||||
else if (UserSettings.Default.AssetLeftTab.IsTriggered(e.Key))
|
||||
_applicationView.CUE4Parse.TabControl.GoLeftTab();
|
||||
else if (UserSettings.Default.AssetRightTab.IsTriggered(e.Key))
|
||||
_applicationView.CUE4Parse.TabControl.GoRightTab();
|
||||
else if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex > 0)
|
||||
LeftTabControl.SelectedIndex--;
|
||||
else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex < LeftTabControl.Items.Count - 1)
|
||||
LeftTabControl.SelectedIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnAssetsListMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
private void OnSearchViewClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Helper.OpenWindow<AdonisWindow>("Search View", () => new SearchView().Show());
|
||||
}
|
||||
|
||||
private void OnTabItemChange(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is not TabControl tabControl)
|
||||
return;
|
||||
|
||||
(tabControl.SelectedItem as System.Windows.Controls.TabItem)?.Focus();
|
||||
}
|
||||
|
||||
private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
await _applicationView.CUE4Parse.InitBenMappings();
|
||||
}
|
||||
|
||||
private void OnAutoTriggerExecuted(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
switch ((e.Command as RoutedCommand)?.Name)
|
||||
{
|
||||
if (sender is not ListBox listBox) return;
|
||||
|
||||
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
||||
case "AutoSaveProps":
|
||||
UserSettings.Default.IsAutoSaveProps = !UserSettings.Default.IsAutoSaveProps;
|
||||
break;
|
||||
case "AutoSaveTextures":
|
||||
UserSettings.Default.IsAutoSaveTextures = !UserSettings.Default.IsAutoSaveTextures;
|
||||
break;
|
||||
case "AutoOpenSounds":
|
||||
UserSettings.Default.IsAutoOpenSounds = !UserSettings.Default.IsAutoOpenSounds;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnFolderExtractClick(object sender, RoutedEventArgs e)
|
||||
private void OnOpenAvalonFinder()
|
||||
{
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.HasSearchOpen = true;
|
||||
AvalonEditor.YesWeSearch.Focus();
|
||||
AvalonEditor.YesWeSearch.SelectAll();
|
||||
}
|
||||
|
||||
private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is not TreeView { SelectedItem: TreeItem treeItem } || treeItem.Folders.Count > 0) return;
|
||||
|
||||
LeftTabControl.SelectedIndex++;
|
||||
}
|
||||
|
||||
private async void OnAssetsListMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is not ListBox listBox) return;
|
||||
|
||||
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
||||
}
|
||||
|
||||
private async void OnFolderExtractClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
||||
{
|
||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractFolder(cancellationToken, folder); });
|
||||
}
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractFolder(cancellationToken, folder); });
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnFolderExportClick(object sender, RoutedEventArgs e)
|
||||
private async void OnFolderExportClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
||||
{
|
||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExportFolder(cancellationToken, folder); });
|
||||
}
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExportFolder(cancellationToken, folder); });
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnFolderSaveClick(object sender, RoutedEventArgs e)
|
||||
private async void OnFolderSaveClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
||||
{
|
||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.SaveFolder(cancellationToken, folder); });
|
||||
}
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.SaveFolder(cancellationToken, folder); });
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSaveDirectoryClick(object sender, RoutedEventArgs e)
|
||||
private void OnSaveDirectoryClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
|
||||
|
||||
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully saved '{folder.PathAtThisPoint}' as a new custom directory", Constants.WHITE, true);
|
||||
}
|
||||
|
||||
private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
|
||||
Clipboard.SetText(folder.PathAtThisPoint);
|
||||
}
|
||||
|
||||
private void OnDeleteSearchClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AssetsSearchName.Text = string.Empty;
|
||||
AssetsListName.ScrollIntoView(AssetsListName.SelectedItem);
|
||||
}
|
||||
|
||||
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (sender is not TextBox textBox || AssetsFolderName.SelectedItem is not TreeItem folder)
|
||||
return;
|
||||
|
||||
var filters = textBox.Text.Trim().Split(' ');
|
||||
folder.AssetsList.AssetsView.Filter = o => { return o is AssetItem assetItem && filters.All(x => assetItem.FullPath.SubstringAfterLast('/').Contains(x, StringComparison.OrdinalIgnoreCase)); };
|
||||
}
|
||||
|
||||
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_applicationView.IsReady || sender is not ListBox listBox) return;
|
||||
UserSettings.Default.LoadingMode = ELoadingMode.Multiple;
|
||||
_applicationView.LoadingModes.LoadCommand.Execute(listBox.SelectedItems);
|
||||
}
|
||||
|
||||
private async void OnPreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (!_applicationView.IsReady || sender is not ListBox listBox) return;
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
|
||||
|
||||
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully saved '{folder.PathAtThisPoint}' as a new custom directory", Constants.WHITE, true);
|
||||
}
|
||||
|
||||
private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
|
||||
Clipboard.SetText(folder.PathAtThisPoint);
|
||||
}
|
||||
|
||||
private void OnDeleteSearchClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AssetsSearchName.Text = string.Empty;
|
||||
AssetsListName.ScrollIntoView(AssetsListName.SelectedItem);
|
||||
}
|
||||
|
||||
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (sender is not TextBox textBox || AssetsFolderName.SelectedItem is not TreeItem folder)
|
||||
return;
|
||||
|
||||
var filters = textBox.Text.Trim().Split(' ');
|
||||
folder.AssetsList.AssetsView.Filter = o =>
|
||||
{
|
||||
return o is AssetItem assetItem && filters.All(x => assetItem.FullPath.SubstringAfterLast('/').Contains(x, StringComparison.OrdinalIgnoreCase));
|
||||
};
|
||||
}
|
||||
|
||||
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_applicationView.IsReady || sender is not ListBox listBox) return;
|
||||
UserSettings.Default.LoadingMode = ELoadingMode.Multiple;
|
||||
_applicationView.LoadingModes.LoadCommand.Execute(listBox.SelectedItems);
|
||||
}
|
||||
|
||||
private async void OnPreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (!_applicationView.IsReady || sender is not ListBox listBox) return;
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Enter:
|
||||
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
||||
break;
|
||||
}
|
||||
case Key.Enter:
|
||||
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
FModel/Resources/ULang.xshd
Normal file
25
FModel/Resources/ULang.xshd
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<SyntaxDefinition name="Ulang Visual Process Language" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"
|
||||
extensions=".ulang">
|
||||
|
||||
<Color name="Constant" foreground="#C792DD" />
|
||||
<Color name="Method" foreground="#FFCB6B" />
|
||||
<Color name="Variable" foreground="#529EFF" />
|
||||
<Color name="Tag1" foreground="#F07178" />
|
||||
<Color name="Tag2" foreground="#7F848E" />
|
||||
<Color name="Parameter" foreground="#89DDFF" />
|
||||
<Color name="Class" foreground="#FF9D5E" />
|
||||
<Color name="Type" foreground="#C3E88D" />
|
||||
<Color name="Punctuation" foreground="#E5C07B" />
|
||||
|
||||
<RuleSet>
|
||||
<Rule color="Constant">\b(?:module|class|attribute|interface|struct|external|enum|where|comparable|component|void|type|subtype)|\@\w+</Rule>
|
||||
<Rule color="Method">\w+(?:\'.*\')?(?=(?:<\w+>)+\()</Rule>
|
||||
<Rule color="Variable">\w+(?:\'.*\')?(?=(?:<\w+>)+\^?:)</Rule>
|
||||
<Rule color="Tag1"><\w+></Rule>
|
||||
<Rule color="Tag2">(?:^using|\# ).*</Rule>
|
||||
<Rule color="Parameter">(?!\()\w+(?=:)</Rule>
|
||||
<Rule color="Class">\w+(?=(?:<\w+>)+\s:=)</Rule>
|
||||
<Rule color="Type">(?<=:)[\w\[\]\?]+</Rule>
|
||||
<Rule color="Punctuation">[()*+,\-.\/:;<=>?[\]^`{|}~]</Rule>
|
||||
</RuleSet>
|
||||
</SyntaxDefinition>
|
||||
BIN
FModel/Resources/npcleftside.png
Normal file
BIN
FModel/Resources/npcleftside.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 257 KiB |
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse_Conversion.Meshes;
|
||||
|
|
@ -51,6 +53,34 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _outputDirectory, value);
|
||||
}
|
||||
|
||||
private string _rawDataDirectory;
|
||||
public string RawDataDirectory
|
||||
{
|
||||
get => _rawDataDirectory;
|
||||
set => SetProperty(ref _rawDataDirectory, value);
|
||||
}
|
||||
|
||||
private string _propertiesDirectory;
|
||||
public string PropertiesDirectory
|
||||
{
|
||||
get => _propertiesDirectory;
|
||||
set => SetProperty(ref _propertiesDirectory, value);
|
||||
}
|
||||
|
||||
private string _textureDirectory;
|
||||
public string TextureDirectory
|
||||
{
|
||||
get => _textureDirectory;
|
||||
set => SetProperty(ref _textureDirectory, value);
|
||||
}
|
||||
|
||||
private string _audioDirectory;
|
||||
public string AudioDirectory
|
||||
{
|
||||
get => _audioDirectory;
|
||||
set => SetProperty(ref _audioDirectory, value);
|
||||
}
|
||||
|
||||
private string _modelDirectory;
|
||||
public string ModelDirectory
|
||||
{
|
||||
|
|
@ -114,6 +144,13 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _isLoggerExpanded, value);
|
||||
}
|
||||
|
||||
private GridLength _avalonImageSize = GridLength.Auto;
|
||||
public GridLength AvalonImageSize
|
||||
{
|
||||
get => _avalonImageSize;
|
||||
set => SetProperty(ref _avalonImageSize, value);
|
||||
}
|
||||
|
||||
private IDictionary<FGame, AesResponse> _aesKeys = new Dictionary<FGame, AesResponse>();
|
||||
public IDictionary<FGame, AesResponse> AesKeys
|
||||
{
|
||||
|
|
@ -205,6 +242,30 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _imageMergerMargin, value);
|
||||
}
|
||||
|
||||
// <gameDirectory as string, settings>
|
||||
// can't refactor to use this data layout for everything
|
||||
// because it will wipe old user settings that relies on FGame
|
||||
private IDictionary<string, GameSelectorViewModel.DetectedGame> _manualGames = new Dictionary<string, GameSelectorViewModel.DetectedGame>();
|
||||
public IDictionary<string, GameSelectorViewModel.DetectedGame> ManualGames
|
||||
{
|
||||
get => _manualGames;
|
||||
set => SetProperty(ref _manualGames, value);
|
||||
}
|
||||
|
||||
private ETexturePlatform _overridedPlatform = ETexturePlatform.DesktopMobile;
|
||||
public ETexturePlatform OverridedPlatform
|
||||
{
|
||||
get => _overridedPlatform;
|
||||
set => SetProperty(ref _overridedPlatform, value);
|
||||
}
|
||||
|
||||
private bool _saveMorphTargets = true;
|
||||
public bool SaveMorphTargets
|
||||
{
|
||||
get => _saveMorphTargets;
|
||||
set => SetProperty(ref _saveMorphTargets, value);
|
||||
}
|
||||
|
||||
private IDictionary<FGame, string> _presets = new Dictionary<FGame, string>
|
||||
{
|
||||
{FGame.Unknown, Constants._NO_PRESET_TRIGGER},
|
||||
|
|
@ -224,7 +285,8 @@ namespace FModel.Settings
|
|||
{FGame.BendGame, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.TslGame, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.PortalWars, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Gameface, Constants._NO_PRESET_TRIGGER}
|
||||
{FGame.Gameface, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Athena, Constants._NO_PRESET_TRIGGER}
|
||||
};
|
||||
public IDictionary<FGame, string> Presets
|
||||
{
|
||||
|
|
@ -251,7 +313,8 @@ namespace FModel.Settings
|
|||
{FGame.BendGame, EGame.GAME_UE4_11},
|
||||
{FGame.TslGame, EGame.GAME_PlayerUnknownsBattlegrounds},
|
||||
{FGame.PortalWars, EGame.GAME_UE4_LATEST},
|
||||
{FGame.Gameface, EGame.GAME_GTATheTrilogyDefinitiveEdition}
|
||||
{FGame.Gameface, EGame.GAME_GTATheTrilogyDefinitiveEdition},
|
||||
{FGame.Athena, EGame.GAME_SeaOfThieves}
|
||||
};
|
||||
public IDictionary<FGame, EGame> OverridedGame
|
||||
{
|
||||
|
|
@ -278,7 +341,8 @@ namespace FModel.Settings
|
|||
{FGame.BendGame, null},
|
||||
{FGame.TslGame, null},
|
||||
{FGame.PortalWars, null},
|
||||
{FGame.Gameface, null}
|
||||
{FGame.Gameface, null},
|
||||
{FGame.Athena, null}
|
||||
};
|
||||
public IDictionary<FGame, List<FCustomVersion>> OverridedCustomVersions
|
||||
{
|
||||
|
|
@ -305,7 +369,8 @@ namespace FModel.Settings
|
|||
{FGame.BendGame, null},
|
||||
{FGame.TslGame, null},
|
||||
{FGame.PortalWars, null},
|
||||
{FGame.Gameface, null}
|
||||
{FGame.Gameface, null},
|
||||
{FGame.Athena, null}
|
||||
};
|
||||
public IDictionary<FGame, Dictionary<string, bool>> OverridedOptions
|
||||
{
|
||||
|
|
@ -379,7 +444,8 @@ namespace FModel.Settings
|
|||
{FGame.BendGame, new List<CustomDirectory>()},
|
||||
{FGame.TslGame, new List<CustomDirectory>()},
|
||||
{FGame.PortalWars, new List<CustomDirectory>()},
|
||||
{FGame.Gameface, new List<CustomDirectory>()}
|
||||
{FGame.Gameface, new List<CustomDirectory>()},
|
||||
{FGame.Athena, new List<CustomDirectory>()}
|
||||
};
|
||||
public IDictionary<FGame, IList<CustomDirectory>> CustomDirectories
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,133 +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 (!UserSettings.Default.AesKeys.TryGetValue(_cue4Parse.Game, out _keysFromSettings) || _keysFromSettings == null)
|
||||
{
|
||||
_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
|
||||
UserSettings.Default.AesKeys.TryGetValue(_cue4Parse.Game, out _keysFromSettings);
|
||||
}
|
||||
|
||||
_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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,29 +2,31 @@
|
|||
using FModel.ViewModels.ApiEndpoints;
|
||||
using RestSharp;
|
||||
|
||||
namespace FModel.ViewModels
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class ApiEndpointViewModel
|
||||
{
|
||||
public class ApiEndpointViewModel
|
||||
private readonly RestClient _client = new RestClient
|
||||
{
|
||||
private readonly IRestClient _client = new RestClient
|
||||
Options =
|
||||
{
|
||||
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 ApiEndpointViewModel()
|
||||
{
|
||||
FortniteApi = new FortniteApiEndpoint(_client);
|
||||
ValorantApi = new ValorantApiEndpoint(_client);
|
||||
BenbotApi = new BenbotApiEndpoint(_client);
|
||||
EpicApi = new EpicApiEndpoint(_client);
|
||||
FModelApi = new FModelApi(_client);
|
||||
MaxTimeout = 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 ApiEndpointViewModel()
|
||||
{
|
||||
FortniteApi = new FortniteApiEndpoint(_client);
|
||||
ValorantApi = new ValorantApiEndpoint(_client);
|
||||
BenbotApi = new BenbotApiEndpoint(_client);
|
||||
EpicApi = new EpicApiEndpoint(_client);
|
||||
FModelApi = new FModelApi(_client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 RestClient _client;
|
||||
|
||||
protected AbstractApiProvider(RestClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(RestClient client) : base(client)
|
||||
{
|
||||
public BenbotApiEndpoint(IRestClient client) : base(client)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<AesResponse> GetAesKeysAsync(CancellationToken token)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public AesResponse GetAesKeys(CancellationToken token)
|
||||
{
|
||||
return GetAesKeysAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<MappingsResponse[]> GetMappingsAsync(CancellationToken token)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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 void DownloadFile(string fileLink, string installationPath)
|
||||
{
|
||||
DownloadFileAsync(fileLink, installationPath).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AesResponse> GetAesKeysAsync(CancellationToken token)
|
||||
{
|
||||
var request = new RestRequest("https://benbot.app/api/v2/aes")
|
||||
{
|
||||
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, response.ResponseUri?.OriginalString);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public AesResponse GetAesKeys(CancellationToken token)
|
||||
{
|
||||
return GetAesKeysAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<MappingsResponse[]> GetMappingsAsync(CancellationToken token)
|
||||
{
|
||||
var request = new RestRequest("https://benbot.app/api/v1/mappings")
|
||||
{
|
||||
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, response.ResponseUri?.OriginalString);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
var request = new RestRequest("https://benbot.app/api/v1/hotfixes")
|
||||
{
|
||||
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, response.ResponseUri?.OriginalString);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
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);
|
||||
var data = _client.DownloadData(request);
|
||||
await File.WriteAllBytesAsync(installationPath, data);
|
||||
}
|
||||
|
||||
public void DownloadFile(string fileLink, string installationPath)
|
||||
{
|
||||
DownloadFileAsync(fileLink, installationPath).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(RestClient 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())
|
||||
{
|
||||
var auth = await GetAuthAsync(token);
|
||||
if (auth != null)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ManifestInfo> GetManifestAsync(CancellationToken token)
|
||||
{
|
||||
if (IsExpired())
|
||||
{
|
||||
var auth = await GetAuthAsync(token);
|
||||
if (auth != null)
|
||||
{
|
||||
UserSettings.Default.LastAuthResponse = auth;
|
||||
}
|
||||
}
|
||||
|
||||
var request = new RestRequest(_LAUNCHER_ASSETS);
|
||||
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, response.ResponseUri?.OriginalString);
|
||||
return new ManifestInfo(response.Content);
|
||||
}
|
||||
|
||||
public ManifestInfo GetManifest(CancellationToken token)
|
||||
{
|
||||
return GetManifestAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
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, response.ResponseUri?.OriginalString);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,169 +17,169 @@ 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(RestClient 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, string game)
|
||||
{
|
||||
var request = new RestRequest($"https://api.fmodel.app/v1/news/{Constants.APP_VERSION}");
|
||||
request.AddParameter("game", game);
|
||||
var response = await _client.ExecuteAsync<News>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
||||
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, string game)
|
||||
{
|
||||
return _news ??= GetNewsAsync(token, game).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}");
|
||||
var response = await _client.ExecuteAsync<Info>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
||||
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}");
|
||||
var response = await _client.ExecuteAsync<Backup[]>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
||||
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}");
|
||||
var response = await _client.ExecuteAsync<Game>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
||||
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}");
|
||||
var response = await _client.ExecuteAsync<Community>(request).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(RestClient 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 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 async Task<PlaylistResponse> GetPlaylistAsync(string playlistId)
|
||||
{
|
||||
var request = new RestRequest($"https://fortnite-api.com/v1/playlists/{playlistId}");
|
||||
var response = await _client.ExecuteAsync<PlaylistResponse>(request).ConfigureAwait(false);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public PlaylistResponse GetPlaylist(string playlistId)
|
||||
{
|
||||
return GetPlaylistAsync(playlistId).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public bool TryGetBytes(Uri link, out byte[] data)
|
||||
{
|
||||
var request = new RestRequest(link);
|
||||
data = _client.DownloadData(request);
|
||||
return data != null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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(RestClient 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);
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,199 +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()} ({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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Header + " (Image)", () =>
|
||||
var popout = new ImagePopout
|
||||
{
|
||||
var popout = new ImagePopout
|
||||
{
|
||||
Title = contextViewModel.Header + " (Image)",
|
||||
Width = contextViewModel.Image.Width,
|
||||
Height = contextViewModel.Image.Height,
|
||||
WindowState = contextViewModel.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
|
||||
ImageCtrl = {Source = contextViewModel.Image}
|
||||
};
|
||||
RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.RenderNearestNeighbor));
|
||||
popout.Show();
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "Copy":
|
||||
ClipboardExtensions.SetImage(contextViewModel.ImageBuffer, Path.ChangeExtension(contextViewModel.Header, ".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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,211 +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, either press F12, restart, or come back later.", 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(async 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:
|
||||
{
|
||||
await FilterNewOrModifiedFilesToDisplay(cancellationToken).ConfigureAwait(false);
|
||||
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 async Task 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);
|
||||
|
||||
await using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
|
||||
await using var memoryStream = new MemoryStream();
|
||||
|
||||
if (fileStream.ReadUInt32() == _IS_LZ4)
|
||||
{
|
||||
fileStream.Position -= 4;
|
||||
await using var compressionStream = LZ4Stream.Decode(fileStream);
|
||||
await compressionStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await fileStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
memoryStream.Position = 0;
|
||||
await 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,64 +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 =>
|
||||
{
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
// case "Assets_Save_Material":
|
||||
// break;
|
||||
// case "Assets_Save_Mesh":
|
||||
// break;
|
||||
}
|
||||
});
|
||||
}
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,152 +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);
|
||||
get => _directoryPath;
|
||||
set => SetProperty(ref _directoryPath, value);
|
||||
}
|
||||
|
||||
private readonly ObservableCollection<Control> _directories;
|
||||
public ReadOnlyObservableCollection<Control> Directories { get; }
|
||||
public CustomDirectory()
|
||||
{
|
||||
Header = string.Empty;
|
||||
DirectoryPath = string.Empty;
|
||||
}
|
||||
|
||||
private readonly FGame _game;
|
||||
public CustomDirectory(string header, string path)
|
||||
{
|
||||
Header = header;
|
||||
DirectoryPath = path;
|
||||
}
|
||||
|
||||
public CustomDirectoriesViewModel(FGame game)
|
||||
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++)
|
||||
{
|
||||
_game = game;
|
||||
_directories = new ObservableCollection<Control>(EnumerateDirectories());
|
||||
Directories = new ReadOnlyObservableCollection<Control>(_directories);
|
||||
if (_directories[i] is not MenuItem m) continue;
|
||||
cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString()));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch))
|
||||
UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd;
|
||||
else UserSettings.Default.CustomDirectories[_game] = cd;
|
||||
}
|
||||
|
||||
public void Add(CustomDirectory dir)
|
||||
private IEnumerable<Control> EnumerateDirectories()
|
||||
{
|
||||
yield return new MenuItem
|
||||
{
|
||||
_directories.Add(new MenuItem {Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir)});
|
||||
}
|
||||
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();
|
||||
|
||||
public void Edit(int index, CustomDirectory newDir)
|
||||
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 (_directories.ElementAt(index) is not MenuItem dir) return;
|
||||
if (setting.DirectoryPath.EndsWith('/'))
|
||||
setting.DirectoryPath = setting.DirectoryPath[..^1];
|
||||
|
||||
dir.Header = newDir.Header;
|
||||
dir.Tag = newDir.DirectoryPath;
|
||||
}
|
||||
|
||||
public void Delete(int index)
|
||||
{
|
||||
_directories.RemoveAt(index);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
UserSettings.Default.CustomDirectories[_game] = new List<CustomDirectory>();
|
||||
for (var i = 2; i < _directories.Count; i++)
|
||||
{
|
||||
if (_directories[i] is not MenuItem m) continue;
|
||||
UserSettings.Default.CustomDirectories[_game].Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
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))},
|
||||
Header = setting.Header,
|
||||
Tag = setting.DirectoryPath,
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
Command = AddEditDirectoryCommand
|
||||
ItemsSource = EnumerateCommands(setting)
|
||||
};
|
||||
yield return new Separator();
|
||||
|
||||
foreach (var setting in UserSettings.Default.CustomDirectories[_game])
|
||||
{
|
||||
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)
|
||||
private IEnumerable<MenuItem> EnumerateCommands(CustomDirectory dir)
|
||||
{
|
||||
yield return new MenuItem
|
||||
{
|
||||
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
|
||||
};
|
||||
}
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -8,315 +8,361 @@ using System.Collections.ObjectModel;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
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; }
|
||||
_autoDetectedGames.Add(game);
|
||||
}
|
||||
|
||||
private DetectedGame _selectedDetectedGame;
|
||||
public DetectedGame SelectedDetectedGame
|
||||
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
|
||||
{
|
||||
get => _selectedDetectedGame;
|
||||
set => SetProperty(ref _selectedDetectedGame, value);
|
||||
}
|
||||
GameName = gameName,
|
||||
GameDirectory = gameDirectory,
|
||||
IsManual = true,
|
||||
AesKeys = null,
|
||||
OverridedGame = EGame.GAME_UE4_LATEST,
|
||||
OverridedCustomVersions = null,
|
||||
OverridedOptions = null,
|
||||
CustomDirectories = new List<CustomDirectory>()
|
||||
};
|
||||
|
||||
private readonly ObservableCollection<DetectedGame> _autoDetectedGames;
|
||||
public ReadOnlyObservableCollection<DetectedGame> AutoDetectedGames { get; }
|
||||
UserSettings.Default.ManualGames[gameDirectory] = game;
|
||||
_autoDetectedGames.Add(game);
|
||||
SelectedDetectedGame = AutoDetectedGames.Last();
|
||||
}
|
||||
|
||||
public GameSelectorViewModel(string gameDirectory)
|
||||
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)
|
||||
{
|
||||
_autoDetectedGames = new ObservableCollection<DetectedGame>(EnumerateDetectedGames().Where(x => x != null));
|
||||
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();
|
||||
}
|
||||
|
||||
public void AddUnknownGame(string gameDirectory)
|
||||
{
|
||||
_autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory });
|
||||
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 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)
|
||||
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})";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
|
|
@ -13,279 +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;
|
||||
get => _selectedUpdateMode;
|
||||
set => SetProperty(ref _selectedUpdateMode, value);
|
||||
}
|
||||
|
||||
private EUpdateMode _selectedUpdateMode;
|
||||
public EUpdateMode SelectedUpdateMode
|
||||
private string _selectedPreset;
|
||||
public string SelectedPreset
|
||||
{
|
||||
get => _selectedPreset;
|
||||
set
|
||||
{
|
||||
get => _selectedUpdateMode;
|
||||
set => SetProperty(ref _selectedUpdateMode, value);
|
||||
SetProperty(ref _selectedPreset, value);
|
||||
RaisePropertyChanged("EnableElements");
|
||||
}
|
||||
}
|
||||
|
||||
private string _selectedPreset;
|
||||
public string SelectedPreset
|
||||
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))
|
||||
{
|
||||
get => _selectedPreset;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedPreset, value);
|
||||
RaisePropertyChanged("EnableElements");
|
||||
}
|
||||
_ueGameSnapshot = settings.OverridedGame;
|
||||
_customVersionsSnapshot = settings.OverridedCustomVersions;
|
||||
_optionsSnapshot = settings.OverridedOptions;
|
||||
}
|
||||
|
||||
private EGame _selectedUeGame;
|
||||
public EGame SelectedUeGame
|
||||
else
|
||||
{
|
||||
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 bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER;
|
||||
|
||||
private readonly FGame _game;
|
||||
private Game _gamePreset;
|
||||
private string _outputSnapshot;
|
||||
private string _modelSnapshot;
|
||||
private string _gameSnapshot;
|
||||
private EUpdateMode _updateModeSnapshot;
|
||||
private string _presetSnapshot;
|
||||
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;
|
||||
_modelSnapshot = UserSettings.Default.ModelDirectory;
|
||||
_gameSnapshot = UserSettings.Default.GameDirectory;
|
||||
_updateModeSnapshot = UserSettings.Default.UpdateMode;
|
||||
_presetSnapshot = UserSettings.Default.Presets[_game];
|
||||
_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;
|
||||
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()));
|
||||
}
|
||||
|
||||
public async Task InitPresets(string gameName)
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(gameName)) return;
|
||||
_gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName);
|
||||
});
|
||||
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
|
||||
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
|
||||
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
|
||||
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
|
||||
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
|
||||
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
|
||||
|
||||
if (_gamePreset?.Versions == null) return;
|
||||
foreach (var version in _gamePreset.Versions.Keys)
|
||||
{
|
||||
Presets.Add(version);
|
||||
}
|
||||
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 });
|
||||
}
|
||||
|
||||
public void SwitchPreset(string key)
|
||||
SelectedOptions = new Dictionary<string, bool>();
|
||||
foreach (var (k, v) in version.Options)
|
||||
{
|
||||
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;
|
||||
}
|
||||
SelectedOptions[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetPreset()
|
||||
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))
|
||||
{
|
||||
SelectedUeGame = _ueGameSnapshot;
|
||||
SelectedCustomVersions = _customVersionsSnapshot;
|
||||
SelectedOptions = _optionsSnapshot;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions;
|
||||
}
|
||||
|
||||
public SettingsOut Save()
|
||||
else
|
||||
{
|
||||
var ret = SettingsOut.Nothing;
|
||||
|
||||
if (_ueGameSnapshot != SelectedUeGame || // combobox
|
||||
_customVersionsSnapshot != SelectedCustomVersions || _optionsSnapshot != SelectedOptions ||
|
||||
_outputSnapshot != UserSettings.Default.OutputDirectory || // 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.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>();
|
||||
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>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
|
|
@ -15,371 +15,440 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Windows;
|
||||
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 TabItem : ViewModel
|
||||
public string ExportName { get; }
|
||||
public byte[] ImageBuffer { get; set; }
|
||||
|
||||
public TabImage(string name, bool rnn, SKBitmap img)
|
||||
{
|
||||
private string _header;
|
||||
public string Header
|
||||
ExportName = name;
|
||||
RenderNearestNeighbor = rnn;
|
||||
SetImage(img);
|
||||
}
|
||||
|
||||
private BitmapImage _image;
|
||||
public BitmapImage Image
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] ImageBuffer { get; private set; }
|
||||
|
||||
private BitmapImage _image;
|
||||
public BitmapImage Image
|
||||
{
|
||||
get => _image;
|
||||
set
|
||||
{
|
||||
if (_image == value) return;
|
||||
SetProperty(ref _image, value);
|
||||
RaisePropertyChanged("HasImage");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _noAlpha;
|
||||
public bool NoAlpha
|
||||
{
|
||||
get => _noAlpha;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _noAlpha, value);
|
||||
ResetImage();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _renderNearestNeighbor;
|
||||
public bool RenderNearestNeighbor
|
||||
{
|
||||
get => _renderNearestNeighbor;
|
||||
set => SetProperty(ref _renderNearestNeighbor, value);
|
||||
}
|
||||
|
||||
public bool HasImage => Image != null;
|
||||
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;
|
||||
}
|
||||
|
||||
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 SaveProperty(bool autoSave)
|
||||
{
|
||||
var fileName = Path.ChangeExtension(Header, ".json");
|
||||
var directory = Path.Combine(UserSettings.Default.OutputDirectory, "Saves",
|
||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/');
|
||||
|
||||
if (!autoSave)
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Save Property",
|
||||
FileName = fileName,
|
||||
InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Saves"),
|
||||
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 SKImage _img;
|
||||
public void ResetImage() => SetImage(_img);
|
||||
public void SetImage(SKImage img)
|
||||
{
|
||||
_img = img;
|
||||
|
||||
using var data = _img.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;
|
||||
if (UserSettings.Default.IsAutoSaveTextures)
|
||||
SaveImage(true);
|
||||
}
|
||||
|
||||
public void SaveImage(bool autoSave)
|
||||
{
|
||||
if (!HasImage) return;
|
||||
var fileName = Path.ChangeExtension(Header, ".png");
|
||||
var directory = Path.Combine(UserSettings.Default.OutputDirectory, "Textures",
|
||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
|
||||
|
||||
if (!autoSave)
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Save Texture",
|
||||
FileName = fileName,
|
||||
InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Textures"),
|
||||
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(ImageBuffer, 0, ImageBuffer.Length);
|
||||
}
|
||||
|
||||
SaveCheck(directory, 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
<TextBlock Text="				" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30"
|
||||
Text="TheGameVlog ♥, Quentin ♥, Maiky ♥, HYPEX ♥, AnimatedAspect, Evan, VenomLeaks, JayKey, Fevers, Netu, Laggy, s0ll, RazTracker, Mikey, kyle, Yanteh, Shiina, SexyNutella, Alexander, Jinx, koba, Tector, imatrix, LamZykoss, Frenzy Leaks, LlamaLeaks, xplore, XTigerHyperX, FunGames, WeLoveFortnite." />
|
||||
Text="TheGameVlog ♥, Quentin ♥, Maiky ♥, HYPEX ♥, AnimatedAspect, Evan, VenomLeaks, Fortnite.GG, JayKey, Fevers, Netu, Laggy, s0ll, RazTracker, Mikey, kyle, Yanteh, Shiina, SexyNutella, Alexander, Jinx, koba, Tector, imatrix, LamZykoss, Frenzy Leaks, LlamaLeaks, xplore, XTigerHyperX, FunGames, WeLoveFortnite." />
|
||||
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="Powered by" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
namespace FModel.Views
|
||||
namespace FModel.Views;
|
||||
|
||||
public partial class About
|
||||
{
|
||||
public partial class About
|
||||
public About()
|
||||
{
|
||||
public About()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
DataContext = _applicationView;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public AudioPlayer()
|
||||
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))
|
||||
{
|
||||
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
|
||||
{
|
||||
var openFileDialog = new OpenFileDialog
|
||||
{
|
||||
Title = "Select an audio file",
|
||||
InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Sounds"),
|
||||
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));
|
||||
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)); };
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
<StackPanel Grid.Row="0" Orientation="Vertical" Margin="10 5 10 10">
|
||||
<TextBlock Text="What to do?" HorizontalAlignment="Center" FontSize="20" FontWeight="SemiBold" />
|
||||
<TextBlock TextAlignment="Center" TextWrapping="Wrap" HorizontalAlignment="Center"
|
||||
Text="We like to make things as simple as possible. Choose between the detected games or manually select your own directory. FModel will use this information to automatically find archives to load and decrypt if needed. Make sure to not skip this step!" />
|
||||
Text="We like to make things as simple as possible. Choose between the detected games or manually add your own. FModel will use this information to automatically find archives to load and decrypt if needed. Make sure to not skip this step!" />
|
||||
</StackPanel>
|
||||
|
||||
<GroupBox Grid.Row="1" adonisExtensions:LayerExtension.Layer="2" Margin="10 10 10 18"
|
||||
|
|
@ -53,16 +53,61 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Grid x:Name="Hello" Margin="0 0 0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox Grid.Column="0" Text="{Binding SelectedDetectedGame.GameDirectory, Mode=TwoWay}" />
|
||||
<Button Grid.Column="2" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" />
|
||||
<Button Grid.Column="3" Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}" Padding="0"
|
||||
Click="OnDeleteDirectory" Width="{Binding ActualWidth, ElementName=OkGuysButWhoFuckingAsked}"
|
||||
Visibility="{Binding SelectedDetectedGame.IsManual, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
ToolTip="Delete Game" Margin="5 0 0 0">
|
||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource RemoveIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Style="{StaticResource CustomSeparator}" Tag="ADD UNDETECTED GAME" />
|
||||
<Expander ExpandDirection="Down" IsExpanded="False">
|
||||
<Grid MaxWidth="{Binding ActualWidth, ElementName=Hello}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="5" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Name" VerticalAlignment="Center" />
|
||||
<TextBox x:Name="HelloMyNameIsGame" Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="5" />
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Directory" VerticalAlignment="Center" />
|
||||
<TextBox x:Name="HelloGameMyNameIsDirectory" Grid.Row="2" Grid.Column="2" />
|
||||
<Button x:Name="OkGuysButWhoFuckingAsked" Grid.Row="2" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseManualDirectories" />
|
||||
<Button Grid.Row="2" Grid.Column="6" Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}" Padding="0"
|
||||
Click="OnAddDirectory" ToolTip="Add Game" Width="{Binding ActualWidth, ElementName=OkGuysButWhoFuckingAsked}">
|
||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AddIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Expander>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,35 +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();
|
||||
}
|
||||
DataContext = gameSelectorViewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
private void OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnBrowseDirectories(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not GameSelectorViewModel gameLauncherViewModel)
|
||||
return;
|
||||
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);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user