mirror of
https://github.com/4sval/FModel.git
synced 2026-03-21 17:24:26 -05:00
parent
85158296a9
commit
e21a3be55b
9
.github/workflows/main.yml
vendored
9
.github/workflows/main.yml
vendored
|
|
@ -21,16 +21,17 @@ jobs:
|
|||
- name: Fetch Submodules Recursively
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: .NET 6 Setup
|
||||
uses: actions/setup-dotnet@v1
|
||||
- name: .NET 7 Setup
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
dotnet-version: '7.0.x'
|
||||
include-prerelease: true
|
||||
|
||||
- name: .NET Restore
|
||||
run: dotnet restore FModel
|
||||
|
||||
- name: .NET Publish
|
||||
run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net6.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }}
|
||||
run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net7.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }}
|
||||
|
||||
- name: ZIP File
|
||||
uses: papeloto/action-zip@v1
|
||||
|
|
|
|||
|
|
@ -16,146 +16,150 @@ using MessageBox = AdonisUI.Controls.MessageBox;
|
|||
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
|
||||
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
|
||||
|
||||
namespace FModel
|
||||
namespace FModel;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool AttachConsole(int dwProcessId);
|
||||
|
||||
[DllImport("winbrand.dll", CharSet = CharSet.Unicode)]
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
static extern string BrandingFormatString(string format);
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool AttachConsole(int dwProcessId);
|
||||
|
||||
[DllImport("winbrand.dll", CharSet = CharSet.Unicode)]
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
static extern string BrandingFormatString(string format);
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
#if DEBUG
|
||||
AttachConsole(-1);
|
||||
AttachConsole(-1);
|
||||
#endif
|
||||
base.OnStartup(e);
|
||||
base.OnStartup(e);
|
||||
|
||||
try
|
||||
{
|
||||
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
|
||||
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
UserSettings.Default = new UserSettings();
|
||||
}
|
||||
|
||||
var createMe = false;
|
||||
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
|
||||
{
|
||||
UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
|
||||
}
|
||||
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
|
||||
{
|
||||
createMe = true;
|
||||
UserSettings.Default.RawDataDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
if (!Directory.Exists(UserSettings.Default.PropertiesDirectory))
|
||||
{
|
||||
createMe = true;
|
||||
UserSettings.Default.PropertiesDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
if (!Directory.Exists(UserSettings.Default.TextureDirectory))
|
||||
{
|
||||
createMe = true;
|
||||
UserSettings.Default.TextureDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
if (!Directory.Exists(UserSettings.Default.AudioDirectory))
|
||||
{
|
||||
createMe = true;
|
||||
UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
if (!Directory.Exists(UserSettings.Default.ModelDirectory))
|
||||
{
|
||||
createMe = true;
|
||||
UserSettings.Default.ModelDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"));
|
||||
if (createMe) Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
||||
|
||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
|
||||
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
|
||||
|
||||
Log.Information("Version {Version}", Constants.APP_VERSION);
|
||||
Log.Information("{OS}", GetOperatingSystemProductName());
|
||||
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
|
||||
Log.Information("Culture {SysLang}", Thread.CurrentThread.CurrentUICulture);
|
||||
}
|
||||
|
||||
private void AppExit(object sender, ExitEventArgs e)
|
||||
try
|
||||
{
|
||||
Log.Information("––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––");
|
||||
Log.CloseAndFlush();
|
||||
UserSettings.Save();
|
||||
Environment.Exit(0);
|
||||
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
|
||||
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||
catch
|
||||
{
|
||||
Log.Error("{Exception}", e.Exception);
|
||||
|
||||
var messageBox = new MessageBoxModel
|
||||
{
|
||||
Text = $"An unhandled exception occurred: {e.Exception.Message}",
|
||||
Caption = "Fatal Error",
|
||||
Icon = MessageBoxImage.Error,
|
||||
Buttons = new[]
|
||||
{
|
||||
MessageBoxButtons.Custom("Reset Settings", EErrorKind.ResetSettings),
|
||||
MessageBoxButtons.Custom("Restart", EErrorKind.Restart),
|
||||
MessageBoxButtons.Custom("OK", EErrorKind.Ignore)
|
||||
},
|
||||
IsSoundEnabled = false
|
||||
};
|
||||
|
||||
MessageBox.Show(messageBox);
|
||||
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
|
||||
{
|
||||
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
|
||||
UserSettings.Default = new UserSettings();
|
||||
|
||||
ApplicationService.ApplicationView.Restart();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
UserSettings.Default = new UserSettings();
|
||||
}
|
||||
|
||||
private string GetOperatingSystemProductName()
|
||||
var createMe = false;
|
||||
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
|
||||
{
|
||||
var productName = string.Empty;
|
||||
try
|
||||
{
|
||||
productName = BrandingFormatString("%WINDOWS_LONG%");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(productName))
|
||||
productName = Environment.OSVersion.VersionString;
|
||||
|
||||
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
|
||||
UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
|
||||
}
|
||||
|
||||
public static string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
|
||||
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
|
||||
{
|
||||
using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path);
|
||||
if (rk != null)
|
||||
return rk.GetValue(name, null) as string;
|
||||
return string.Empty;
|
||||
createMe = true;
|
||||
UserSettings.Default.RawDataDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(UserSettings.Default.PropertiesDirectory))
|
||||
{
|
||||
createMe = true;
|
||||
UserSettings.Default.PropertiesDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(UserSettings.Default.TextureDirectory))
|
||||
{
|
||||
createMe = true;
|
||||
UserSettings.Default.TextureDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(UserSettings.Default.AudioDirectory))
|
||||
{
|
||||
createMe = true;
|
||||
UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(UserSettings.Default.ModelDirectory))
|
||||
{
|
||||
createMe = true;
|
||||
UserSettings.Default.ModelDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"));
|
||||
if (createMe) Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
||||
|
||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
|
||||
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
|
||||
|
||||
Log.Information("Version {Version}", Constants.APP_VERSION);
|
||||
Log.Information("{OS}", GetOperatingSystemProductName());
|
||||
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
|
||||
Log.Information("Culture {SysLang}", Thread.CurrentThread.CurrentUICulture);
|
||||
}
|
||||
}
|
||||
|
||||
private void AppExit(object sender, ExitEventArgs e)
|
||||
{
|
||||
Log.Information("––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––");
|
||||
Log.CloseAndFlush();
|
||||
UserSettings.Save();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
Log.Error("{Exception}", e.Exception);
|
||||
|
||||
var messageBox = new MessageBoxModel
|
||||
{
|
||||
Text = $"An unhandled exception occurred: {e.Exception.Message}",
|
||||
Caption = "Fatal Error",
|
||||
Icon = MessageBoxImage.Error,
|
||||
Buttons = new[]
|
||||
{
|
||||
MessageBoxButtons.Custom("Reset Settings", EErrorKind.ResetSettings),
|
||||
MessageBoxButtons.Custom("Restart", EErrorKind.Restart),
|
||||
MessageBoxButtons.Custom("OK", EErrorKind.Ignore)
|
||||
},
|
||||
IsSoundEnabled = false
|
||||
};
|
||||
|
||||
MessageBox.Show(messageBox);
|
||||
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
|
||||
{
|
||||
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
|
||||
UserSettings.Default = new UserSettings();
|
||||
|
||||
ApplicationService.ApplicationView.Restart();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private string GetOperatingSystemProductName()
|
||||
{
|
||||
var productName = string.Empty;
|
||||
try
|
||||
{
|
||||
productName = BrandingFormatString("%WINDOWS_LONG%");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(productName))
|
||||
productName = Environment.OSVersion.VersionString;
|
||||
|
||||
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
|
||||
}
|
||||
|
||||
public static string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
|
||||
{
|
||||
using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path);
|
||||
if (rk != null)
|
||||
return rk.GetValue(name, null) as string;
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -4,40 +4,39 @@ using CUE4Parse.UE4.Objects.UObject;
|
|||
using FModel.Creator.Bases.FN;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.BB
|
||||
namespace FModel.Creator.Bases.BB;
|
||||
|
||||
public class BaseBreakersIcon : BaseIcon
|
||||
{
|
||||
public class BaseBreakersIcon : BaseIcon
|
||||
public BaseBreakersIcon(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
public BaseBreakersIcon(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
SeriesBackground = Utils.GetBitmap("WorldExplorers/Content/UMG/Materials/t_TextGradient.t_TextGradient");
|
||||
Background = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("636363")};
|
||||
Border = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF")};
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FSoftObjectPath iconTextureAssetData, "IconTextureAssetData", "UnlockPortraitGuideImage"))
|
||||
Preview = Utils.GetBitmap(iconTextureAssetData);
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "RegionDisplayName", "ZoneName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description", "RegionShortName", "ZoneDescription"))
|
||||
Description = description.Text;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
SeriesBackground = Utils.GetBitmap("WorldExplorers/Content/UMG/Materials/t_TextGradient.t_TextGradient");
|
||||
Background = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("636363") };
|
||||
Border = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") };
|
||||
}
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FSoftObjectPath iconTextureAssetData, "IconTextureAssetData", "UnlockPortraitGuideImage"))
|
||||
Preview = Utils.GetBitmap(iconTextureAssetData);
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "RegionDisplayName", "ZoneName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description", "RegionShortName", "ZoneDescription"))
|
||||
Description = description.Text;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
|
|
@ -8,134 +8,133 @@ using FModel.Framework;
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseBundle : UCreator
|
||||
{
|
||||
private IList<BaseQuest> _quests;
|
||||
private const int _headerHeight = 100;
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
public class BaseBundle : UCreator
|
||||
{
|
||||
private IList<BaseQuest> _quests;
|
||||
private const int _headerHeight = 100;
|
||||
|
||||
public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Width = 1024;
|
||||
Height = _headerHeight;
|
||||
Margin = 0;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
_quests = new List<BaseQuest>();
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :)
|
||||
{
|
||||
Width = 1024;
|
||||
Height = _headerHeight;
|
||||
Margin = 0;
|
||||
foreach (var quest in quests)
|
||||
{
|
||||
if (!quest.TryGetValue(out FSoftObjectPath questDefinition, "QuestDefinition")) continue;
|
||||
|
||||
BaseQuest q;
|
||||
var path = questDefinition.AssetPathName.Text;
|
||||
do
|
||||
{
|
||||
if (!Utils.TryLoadObject(path, out UObject uObject)) break;
|
||||
|
||||
q = new BaseQuest(uObject, Style);
|
||||
q.ParseForInfo();
|
||||
_quests.Add(q);
|
||||
path = path.SubstringBeforeWithLast('/') + q.NextQuestName + "." + q.NextQuestName;
|
||||
} while (!string.IsNullOrEmpty(q.NextQuestName));
|
||||
}
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
if (Object.TryGetValue(out FStructFallback[] completionRewards, "BundleCompletionRewards"))
|
||||
{
|
||||
_quests = new List<BaseQuest>();
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :)
|
||||
foreach (var completionReward in completionRewards)
|
||||
{
|
||||
foreach (var quest in quests)
|
||||
if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") ||
|
||||
!completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue;
|
||||
|
||||
foreach (var reward in rewards)
|
||||
{
|
||||
if (!quest.TryGetValue(out FSoftObjectPath questDefinition, "QuestDefinition")) continue;
|
||||
if (!reward.TryGetValue(out int quantity, "Quantity") ||
|
||||
!reward.TryGetValue(out string templateId, "TemplateId") ||
|
||||
!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue;
|
||||
|
||||
BaseQuest q;
|
||||
var path = questDefinition.AssetPathName.Text;
|
||||
do
|
||||
if (!itemDefinition.AssetPathName.IsNone &&
|
||||
!itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") &&
|
||||
!itemDefinition.AssetPathName.Text.Contains("/Items/Quests"))
|
||||
{
|
||||
if (!Utils.TryLoadObject(path, out UObject uObject)) break;
|
||||
|
||||
q = new BaseQuest(uObject, Style);
|
||||
q.ParseForInfo();
|
||||
_quests.Add(q);
|
||||
path = path.SubstringBeforeWithLast('/') + q.NextQuestName + "." + q.NextQuestName;
|
||||
} while (!string.IsNullOrEmpty(q.NextQuestName));
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] completionRewards, "BundleCompletionRewards"))
|
||||
{
|
||||
foreach (var completionReward in completionRewards)
|
||||
{
|
||||
if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") ||
|
||||
!completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue;
|
||||
|
||||
foreach (var reward in rewards)
|
||||
_quests.Add(new BaseQuest(completionCount, itemDefinition, Style));
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(templateId))
|
||||
{
|
||||
if (!reward.TryGetValue(out int quantity, "Quantity") ||
|
||||
!reward.TryGetValue(out string templateId, "TemplateId") ||
|
||||
!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue;
|
||||
|
||||
if (!itemDefinition.AssetPathName.IsNone &&
|
||||
!itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") &&
|
||||
!itemDefinition.AssetPathName.Text.Contains("/Items/Quests"))
|
||||
{
|
||||
_quests.Add(new BaseQuest(completionCount, itemDefinition, Style));
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(templateId))
|
||||
{
|
||||
_quests.Add(new BaseQuest(completionCount, quantity, templateId, Style));
|
||||
}
|
||||
_quests.Add(new BaseQuest(completionCount, quantity, templateId, Style));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Height += 256 * _quests.Count;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
Height += 256 * _quests.Count;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
DrawDisplayName(c);
|
||||
DrawQuests(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private readonly SKPaint _headerPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
|
||||
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
|
||||
};
|
||||
|
||||
private void DrawHeader(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
|
||||
|
||||
var background = _quests.Count > 0 ? _quests[0].Background : Background;
|
||||
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] { background[0].WithAlpha(50), background[1].WithAlpha(50) }, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
|
||||
new[] { SKColors.Black.WithAlpha(25), background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
|
||||
}
|
||||
|
||||
private new void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName)) return;
|
||||
|
||||
_headerPaint.Shader = null;
|
||||
_headerPaint.Color = SKColors.White;
|
||||
while (_headerPaint.MeasureText(DisplayName) > Width)
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
DrawDisplayName(c);
|
||||
DrawQuests(c);
|
||||
|
||||
return new []{ret};
|
||||
_headerPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
private readonly SKPaint _headerPaint = new()
|
||||
var shaper = new CustomSKShaper(_headerPaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, _headerPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint);
|
||||
}
|
||||
|
||||
private void DrawQuests(SKCanvas c)
|
||||
{
|
||||
var y = _headerHeight;
|
||||
foreach (var quest in _quests)
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
|
||||
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
|
||||
};
|
||||
|
||||
private void DrawHeader(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
|
||||
|
||||
var background = _quests.Count > 0 ? _quests[0].Background : Background;
|
||||
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] {background[0].WithAlpha(50), background[1].WithAlpha(50)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
|
||||
new[] {SKColors.Black.WithAlpha(25), background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
|
||||
}
|
||||
|
||||
private new void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName)) return;
|
||||
|
||||
_headerPaint.Shader = null;
|
||||
_headerPaint.Color = SKColors.White;
|
||||
while (_headerPaint.MeasureText(DisplayName) > Width)
|
||||
{
|
||||
_headerPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_headerPaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, _headerPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint);
|
||||
}
|
||||
|
||||
private void DrawQuests(SKCanvas c)
|
||||
{
|
||||
var y = _headerHeight;
|
||||
foreach (var quest in _quests)
|
||||
{
|
||||
quest.DrawQuest(c, y);
|
||||
y += quest.Height;
|
||||
}
|
||||
quest.DrawQuest(c, y);
|
||||
y += quest.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,231 +11,145 @@ using FModel.ViewModels.ApiEndpoints.Models;
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseCommunity : BaseIcon
|
||||
{
|
||||
public class BaseCommunity : BaseIcon
|
||||
private readonly CommunityDesign _design;
|
||||
private string _rarityName;
|
||||
private string _source;
|
||||
private string _season;
|
||||
private bool _lowerDrawn;
|
||||
|
||||
public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style)
|
||||
{
|
||||
private readonly CommunityDesign _design;
|
||||
private string _rarityName;
|
||||
private string _source;
|
||||
private string _season;
|
||||
private bool _lowerDrawn;
|
||||
Margin = 0;
|
||||
_lowerDrawn = false;
|
||||
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
|
||||
}
|
||||
|
||||
public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style)
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
|
||||
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export))
|
||||
_rarityName = export.Name;
|
||||
else
|
||||
_rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription();
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||
CheckGameplayTags(gameplayTags);
|
||||
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
||||
CosmeticSource = cosmeticItem.Name.ToUpper();
|
||||
|
||||
DisplayName = DisplayName.ToUpper();
|
||||
Description = Description.ToUpper();
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
if (_design == null)
|
||||
{
|
||||
Margin = 0;
|
||||
_lowerDrawn = false;
|
||||
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
|
||||
base.Draw(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
if (_design.DrawSeason && _design.Fonts.TryGetValue("Season", out var font))
|
||||
DrawToBottom(c, font, _season);
|
||||
if (_design.DrawSource && _design.Fonts.TryGetValue("Source", out font))
|
||||
DrawToBottom(c, font, _source);
|
||||
DrawUserFacingFlags(c, _design.GameplayTags.DrawCustomOnly);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
||||
{
|
||||
if (_design == null) return;
|
||||
if (_design.DrawSource)
|
||||
{
|
||||
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
|
||||
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export))
|
||||
_rarityName = export.Name;
|
||||
else
|
||||
_rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription();
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||
CheckGameplayTags(gameplayTags);
|
||||
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
||||
CosmeticSource = cosmeticItem.Name.ToUpper();
|
||||
|
||||
DisplayName = DisplayName.ToUpper();
|
||||
Description = Description.ToUpper();
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
|
||||
_source = source.Text["Cosmetics.Source.".Length..].ToUpper();
|
||||
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
|
||||
_source = action.Text["Athena.ItemAction.".Length..].ToUpper();
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
if (_design.DrawSet && gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
|
||||
Description += GetCosmeticSet(set.Text, _design.DrawSetShort);
|
||||
if (_design.DrawSeason && gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
|
||||
_season = GetCosmeticSeason(season.Text, _design.DrawSeasonShort);
|
||||
|
||||
var triggers = _design.GameplayTags.DrawCustomOnly ? new[] { "Cosmetics.UserFacingFlags." } : new[] { "Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender." };
|
||||
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(triggers));
|
||||
}
|
||||
|
||||
private string GetCosmeticSet(string setName, bool bShort)
|
||||
{
|
||||
return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName);
|
||||
}
|
||||
|
||||
private string GetCosmeticSeason(string seasonNumber, bool bShort)
|
||||
{
|
||||
if (!bShort) return base.GetCosmeticSeason(seasonNumber);
|
||||
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
||||
var number = int.Parse(s);
|
||||
|
||||
switch (number)
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
if (_design == null)
|
||||
{
|
||||
base.Draw(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
if (_design.DrawSeason && _design.Fonts.TryGetValue("Season", out var font))
|
||||
DrawToBottom(c, font, _season);
|
||||
if (_design.DrawSource && _design.Fonts.TryGetValue("Source", out font))
|
||||
DrawToBottom(c, font, _source);
|
||||
DrawUserFacingFlags(c, _design.GameplayTags.DrawCustomOnly);
|
||||
}
|
||||
|
||||
return new []{ret};
|
||||
case 10:
|
||||
s = "X";
|
||||
break;
|
||||
case > 18:
|
||||
number += 2;
|
||||
s = number.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
||||
return $"C{number / 10 + 1} S{s[^1..]}";
|
||||
}
|
||||
|
||||
private new void DrawBackground(SKCanvas c)
|
||||
{
|
||||
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
|
||||
{
|
||||
if (_design == null) return;
|
||||
if (_design.DrawSource)
|
||||
{
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
|
||||
_source = source.Text["Cosmetics.Source.".Length..].ToUpper();
|
||||
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
|
||||
_source = action.Text["Athena.ItemAction.".Length..].ToUpper();
|
||||
}
|
||||
|
||||
if (_design.DrawSet && gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
|
||||
Description += GetCosmeticSet(set.Text, _design.DrawSetShort);
|
||||
if (_design.DrawSeason && gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
|
||||
_season = GetCosmeticSeason(season.Text, _design.DrawSeasonShort);
|
||||
|
||||
var triggers = _design.GameplayTags.DrawCustomOnly ? new[] {"Cosmetics.UserFacingFlags."} : new[] {"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."};
|
||||
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(triggers));
|
||||
c.DrawBitmap(rarity.Background, 0, 0, ImagePaint);
|
||||
c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint);
|
||||
}
|
||||
|
||||
private string GetCosmeticSet(string setName, bool bShort)
|
||||
else
|
||||
{
|
||||
return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName);
|
||||
base.DrawBackground(c);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCosmeticSeason(string seasonNumber, bool bShort)
|
||||
private new void DrawTextBackground(SKCanvas c)
|
||||
{
|
||||
if (!_lowerDrawn && string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
|
||||
|
||||
_lowerDrawn = true;
|
||||
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
|
||||
{
|
||||
if (!bShort) return base.GetCosmeticSeason(seasonNumber);
|
||||
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
||||
var number = int.Parse(s);
|
||||
|
||||
switch (number)
|
||||
{
|
||||
case 10:
|
||||
s = "X";
|
||||
break;
|
||||
case > 18:
|
||||
number += 2;
|
||||
s = number.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
return $"C{number / 10 + 1} S{s[^1..]}";
|
||||
c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint);
|
||||
}
|
||||
|
||||
private new void DrawBackground(SKCanvas c)
|
||||
else
|
||||
{
|
||||
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
|
||||
{
|
||||
c.DrawBitmap(rarity.Background, 0, 0, ImagePaint);
|
||||
c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DrawBackground(c);
|
||||
}
|
||||
base.DrawTextBackground(c);
|
||||
}
|
||||
}
|
||||
|
||||
private new void DrawTextBackground(SKCanvas c)
|
||||
private new void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName)) return;
|
||||
if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font))
|
||||
{
|
||||
if (!_lowerDrawn && string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
|
||||
|
||||
_lowerDrawn = true;
|
||||
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
|
||||
{
|
||||
c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DrawTextBackground(c);
|
||||
}
|
||||
}
|
||||
|
||||
private new void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName)) return;
|
||||
if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font))
|
||||
{
|
||||
DisplayNamePaint.TextSize = font.FontSize;
|
||||
DisplayNamePaint.TextScaleX = font.FontScale;
|
||||
DisplayNamePaint.Color = font.FontColor;
|
||||
DisplayNamePaint.TextSkewX = font.SkewValue;
|
||||
DisplayNamePaint.TextAlign = font.Alignment;
|
||||
if (font.ShadowValue > 0)
|
||||
DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
|
||||
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
|
||||
font.Typeface.TryGetValue(ELanguage.English, out path))
|
||||
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
|
||||
|
||||
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
|
||||
{
|
||||
DisplayNamePaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
var x = font.Alignment switch
|
||||
{
|
||||
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
|
||||
_ => font.X
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DrawDisplayName(c);
|
||||
}
|
||||
}
|
||||
|
||||
private new void DrawDescription(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Description)) return;
|
||||
if (_design.Fonts.TryGetValue(nameof(Description), out var font))
|
||||
{
|
||||
DescriptionPaint.TextSize = font.FontSize;
|
||||
DescriptionPaint.TextScaleX = font.FontScale;
|
||||
DescriptionPaint.Color = font.FontColor;
|
||||
DescriptionPaint.TextSkewX = font.SkewValue;
|
||||
DescriptionPaint.TextAlign = font.Alignment;
|
||||
if (font.ShadowValue > 0)
|
||||
DescriptionPaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
|
||||
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
|
||||
font.Typeface.TryGetValue(ELanguage.English, out path))
|
||||
DescriptionPaint.Typeface = Utils.Typefaces.OnTheFly(path);
|
||||
|
||||
while (DescriptionPaint.MeasureText(Description) > Width - Margin * 2)
|
||||
{
|
||||
DescriptionPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
|
||||
var shapedText = shaper.Shape(Description, DescriptionPaint);
|
||||
var x = font.Alignment switch
|
||||
{
|
||||
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
|
||||
_ => font.X
|
||||
};
|
||||
|
||||
if (font.MaxLineCount < 2)
|
||||
{
|
||||
c.DrawShapedText(shaper, Description, x, font.Y, DescriptionPaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.DrawMultilineText(c, Description, Width - Margin * 2, Margin, DescriptionPaint.TextAlign,
|
||||
new SKRect(Margin, font.Y, Width - Margin, Height), DescriptionPaint, out _);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DrawDescription(c);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawToBottom(SKCanvas c, FontDesign font, string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return;
|
||||
if (!_lowerDrawn)
|
||||
{
|
||||
_lowerDrawn = true;
|
||||
DrawTextBackground(c);
|
||||
}
|
||||
|
||||
DisplayNamePaint.TextSize = font.FontSize;
|
||||
DisplayNamePaint.TextScaleX = font.FontScale;
|
||||
DisplayNamePaint.Color = font.FontColor;
|
||||
|
|
@ -247,30 +161,115 @@ namespace FModel.Creator.Bases.FN
|
|||
font.Typeface.TryGetValue(ELanguage.English, out path))
|
||||
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
|
||||
|
||||
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
|
||||
{
|
||||
DisplayNamePaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(text, DisplayNamePaint);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
var x = font.Alignment switch
|
||||
{
|
||||
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
|
||||
SKTextAlign.Right => font.X - DisplayNamePaint.MeasureText(text),
|
||||
_ => font.X
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint);
|
||||
c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint);
|
||||
}
|
||||
|
||||
private void DrawUserFacingFlags(SKCanvas c, bool customOnly)
|
||||
else
|
||||
{
|
||||
if (UserFacingFlags == null || UserFacingFlags.Count < 1) return;
|
||||
if (customOnly)
|
||||
base.DrawDisplayName(c);
|
||||
}
|
||||
}
|
||||
|
||||
private new void DrawDescription(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Description)) return;
|
||||
if (_design.Fonts.TryGetValue(nameof(Description), out var font))
|
||||
{
|
||||
DescriptionPaint.TextSize = font.FontSize;
|
||||
DescriptionPaint.TextScaleX = font.FontScale;
|
||||
DescriptionPaint.Color = font.FontColor;
|
||||
DescriptionPaint.TextSkewX = font.SkewValue;
|
||||
DescriptionPaint.TextAlign = font.Alignment;
|
||||
if (font.ShadowValue > 0)
|
||||
DescriptionPaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
|
||||
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
|
||||
font.Typeface.TryGetValue(ELanguage.English, out path))
|
||||
DescriptionPaint.Typeface = Utils.Typefaces.OnTheFly(path);
|
||||
|
||||
while (DescriptionPaint.MeasureText(Description) > Width - Margin * 2)
|
||||
{
|
||||
c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint);
|
||||
DescriptionPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
|
||||
var shapedText = shaper.Shape(Description, DescriptionPaint);
|
||||
var x = font.Alignment switch
|
||||
{
|
||||
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
|
||||
_ => font.X
|
||||
};
|
||||
|
||||
if (font.MaxLineCount < 2)
|
||||
{
|
||||
c.DrawShapedText(shaper, Description, x, font.Y, DescriptionPaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add size to api
|
||||
// draw
|
||||
Utils.DrawMultilineText(c, Description, Width - Margin * 2, Margin, DescriptionPaint.TextAlign,
|
||||
new SKRect(Margin, font.Y, Width - Margin, Height), DescriptionPaint, out _);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DrawDescription(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawToBottom(SKCanvas c, FontDesign font, string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return;
|
||||
if (!_lowerDrawn)
|
||||
{
|
||||
_lowerDrawn = true;
|
||||
DrawTextBackground(c);
|
||||
}
|
||||
|
||||
DisplayNamePaint.TextSize = font.FontSize;
|
||||
DisplayNamePaint.TextScaleX = font.FontScale;
|
||||
DisplayNamePaint.Color = font.FontColor;
|
||||
DisplayNamePaint.TextSkewX = font.SkewValue;
|
||||
DisplayNamePaint.TextAlign = font.Alignment;
|
||||
if (font.ShadowValue > 0)
|
||||
DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
|
||||
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
|
||||
font.Typeface.TryGetValue(ELanguage.English, out path))
|
||||
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(text, DisplayNamePaint);
|
||||
var x = font.Alignment switch
|
||||
{
|
||||
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
|
||||
SKTextAlign.Right => font.X - DisplayNamePaint.MeasureText(text),
|
||||
_ => font.X
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint);
|
||||
}
|
||||
|
||||
private void DrawUserFacingFlags(SKCanvas c, bool customOnly)
|
||||
{
|
||||
if (UserFacingFlags == null || UserFacingFlags.Count < 1) return;
|
||||
if (customOnly)
|
||||
{
|
||||
c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add size to api
|
||||
// draw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,272 +16,269 @@ using CUE4Parse_Fortnite.Enums;
|
|||
using FModel.Settings;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseIcon : UCreator
|
||||
{
|
||||
public class BaseIcon : UCreator
|
||||
public SKBitmap SeriesBackground { get; protected set; }
|
||||
protected string ShortDescription { get; set; }
|
||||
protected string CosmeticSource { get; set; }
|
||||
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
|
||||
|
||||
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
public SKBitmap SeriesBackground { get; protected set; }
|
||||
protected string ShortDescription { get; set; }
|
||||
protected string CosmeticSource { get; set; }
|
||||
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
|
||||
}
|
||||
|
||||
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
public void ParseForReward(bool isUsingDisplayAsset)
|
||||
{
|
||||
// rarity
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
|
||||
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
|
||||
|
||||
// preview
|
||||
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
|
||||
Preview = preview;
|
||||
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
|
||||
Preview = Utils.GetBitmap(itemDefinition);
|
||||
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
|
||||
Preview = Utils.GetBitmap(largePreview);
|
||||
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
|
||||
Preview = Utils.GetBitmap(s);
|
||||
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
|
||||
Preview = Utils.GetBitmap(otherPreview);
|
||||
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
|
||||
Preview = Utils.GetBitmap(materialInstancePreview);
|
||||
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
|
||||
Preview = Utils.GetBitmap(res);
|
||||
|
||||
// text
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
|
||||
Description = description.Text;
|
||||
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
|
||||
Description = string.Join('\n', descriptions.Select(x => x.Text));
|
||||
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
|
||||
ShortDescription = shortDescription.Text;
|
||||
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
|
||||
ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
|
||||
|
||||
// Only works on non-cataba designs
|
||||
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
|
||||
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
|
||||
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
|
||||
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
|
||||
{
|
||||
Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) };
|
||||
Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) };
|
||||
}
|
||||
|
||||
public void ParseForReward(bool isUsingDisplayAsset)
|
||||
Description = Utils.RemoveHtmlTags(Description);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||
CheckGameplayTags(gameplayTags);
|
||||
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
||||
CosmeticSource = cosmeticItem.Name;
|
||||
}
|
||||
|
||||
protected void Draw(SKCanvas c)
|
||||
{
|
||||
switch (Style)
|
||||
{
|
||||
// rarity
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
|
||||
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawUserFacingFlags(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
|
||||
if (Description != ShortDescription)
|
||||
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
|
||||
DrawUserFacingFlags(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// preview
|
||||
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
|
||||
Preview = preview;
|
||||
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
|
||||
Preview = Utils.GetBitmap(itemDefinition);
|
||||
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
|
||||
Preview = Utils.GetBitmap(largePreview);
|
||||
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
|
||||
Preview = Utils.GetBitmap(s);
|
||||
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
|
||||
Preview = Utils.GetBitmap(otherPreview);
|
||||
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
|
||||
Preview = Utils.GetBitmap(materialInstancePreview);
|
||||
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
|
||||
Preview = Utils.GetBitmap(res);
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
// text
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
|
||||
Description = description.Text;
|
||||
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
|
||||
Description = string.Join('\n', descriptions.Select(x => x.Text));
|
||||
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
|
||||
ShortDescription = shortDescription.Text;
|
||||
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
|
||||
ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
|
||||
Draw(c);
|
||||
|
||||
// Only works on non-cataba designs
|
||||
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
|
||||
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
|
||||
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
|
||||
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private void GetSeries(FPackageIndex s)
|
||||
{
|
||||
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
|
||||
|
||||
GetSeries(export);
|
||||
}
|
||||
|
||||
protected void GetSeries(UObject uObject)
|
||||
{
|
||||
if (uObject is UTexture2D texture2D)
|
||||
{
|
||||
SeriesBackground = texture2D.Decode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
|
||||
{
|
||||
SeriesBackground = Utils.GetBitmap(backgroundTexture);
|
||||
}
|
||||
|
||||
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
|
||||
colors.TryGetValue(out FLinearColor color1, "Color1") &&
|
||||
colors.TryGetValue(out FLinearColor color2, "Color2") &&
|
||||
colors.TryGetValue(out FLinearColor color3, "Color3"))
|
||||
{
|
||||
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
|
||||
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
|
||||
}
|
||||
|
||||
if (uObject.Name.Equals("PlatformSeries") &&
|
||||
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
|
||||
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
|
||||
{
|
||||
foreach (var vectorParameter in material.VectorParameterValues)
|
||||
{
|
||||
Background = new[] {SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex)};
|
||||
Border = new[] {SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex)};
|
||||
}
|
||||
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
|
||||
continue;
|
||||
|
||||
Description = Utils.RemoveHtmlTags(Description);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||
CheckGameplayTags(gameplayTags);
|
||||
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
||||
CosmeticSource = cosmeticItem.Name;
|
||||
}
|
||||
|
||||
protected void Draw(SKCanvas c)
|
||||
{
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawUserFacingFlags(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
|
||||
if (Description != ShortDescription)
|
||||
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
|
||||
DrawUserFacingFlags(c);
|
||||
break;
|
||||
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
private void GetRarity(EFortRarity r)
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
|
||||
|
||||
if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
|
||||
data.TryGetValue(out FLinearColor color1, "Color1") &&
|
||||
data.TryGetValue(out FLinearColor color2, "Color2") &&
|
||||
data.TryGetValue(out FLinearColor color3, "Color3"))
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
|
||||
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
|
||||
}
|
||||
}
|
||||
|
||||
Draw(c);
|
||||
protected string GetCosmeticSet(string setName)
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
|
||||
return string.Empty;
|
||||
|
||||
return new []{ret};
|
||||
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
|
||||
return string.Empty;
|
||||
|
||||
var name = string.Empty;
|
||||
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
|
||||
name = displayName.Text;
|
||||
|
||||
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
|
||||
return string.Format(format, name);
|
||||
}
|
||||
|
||||
protected string GetCosmeticSeason(string seasonNumber)
|
||||
{
|
||||
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
||||
var number = int.Parse(s);
|
||||
|
||||
switch (number)
|
||||
{
|
||||
case 10:
|
||||
s = "X";
|
||||
break;
|
||||
case > 18:
|
||||
number += 2;
|
||||
s = number.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
private void GetSeries(FPackageIndex s)
|
||||
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
|
||||
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
|
||||
if (number <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
|
||||
|
||||
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
|
||||
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
|
||||
var d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..]));
|
||||
return Utils.RemoveHtmlTags(string.Format(introduced, d));
|
||||
}
|
||||
|
||||
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
||||
{
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
|
||||
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
|
||||
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
|
||||
CosmeticSource = action.Text["Athena.ItemAction.".Length..];
|
||||
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
|
||||
Description += GetCosmeticSet(set.Text);
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
|
||||
Description += GetCosmeticSeason(season.Text);
|
||||
|
||||
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
|
||||
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
|
||||
}
|
||||
|
||||
protected void GetUserFacingFlags(IList<string> userFacingFlags)
|
||||
{
|
||||
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
|
||||
return;
|
||||
|
||||
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
|
||||
return;
|
||||
|
||||
UserFacingFlags = new Dictionary<string, SKBitmap>(userFacingFlags.Count);
|
||||
foreach (var flag in userFacingFlags)
|
||||
{
|
||||
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
|
||||
|
||||
GetSeries(export);
|
||||
}
|
||||
|
||||
protected void GetSeries(UObject uObject)
|
||||
{
|
||||
if (uObject is UTexture2D texture2D)
|
||||
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SeriesBackground = texture2D.Decode();
|
||||
return;
|
||||
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
|
||||
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
|
||||
else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
|
||||
}
|
||||
|
||||
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
|
||||
else
|
||||
{
|
||||
SeriesBackground = Utils.GetBitmap(backgroundTexture);
|
||||
}
|
||||
|
||||
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
|
||||
colors.TryGetValue(out FLinearColor color1, "Color1") &&
|
||||
colors.TryGetValue(out FLinearColor color2, "Color2") &&
|
||||
colors.TryGetValue(out FLinearColor color3, "Color3"))
|
||||
{
|
||||
Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)};
|
||||
Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)};
|
||||
}
|
||||
|
||||
if (uObject.Name.Equals("PlatformSeries") &&
|
||||
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
|
||||
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
|
||||
{
|
||||
foreach (var vectorParameter in material.VectorParameterValues)
|
||||
foreach (var category in tertiaryCategories)
|
||||
{
|
||||
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
|
||||
continue;
|
||||
|
||||
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetRarity(EFortRarity r)
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
|
||||
|
||||
if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
|
||||
data.TryGetValue(out FLinearColor color1, "Color1") &&
|
||||
data.TryGetValue(out FLinearColor color2, "Color2") &&
|
||||
data.TryGetValue(out FLinearColor color3, "Color3"))
|
||||
{
|
||||
Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)};
|
||||
Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)};
|
||||
}
|
||||
}
|
||||
|
||||
protected string GetCosmeticSet(string setName)
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
|
||||
return string.Empty;
|
||||
|
||||
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
|
||||
return string.Empty;
|
||||
|
||||
var name = string.Empty;
|
||||
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
|
||||
name = displayName.Text;
|
||||
|
||||
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
|
||||
return string.Format(format, name);
|
||||
}
|
||||
|
||||
protected string GetCosmeticSeason(string seasonNumber)
|
||||
{
|
||||
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
||||
var number = int.Parse(s);
|
||||
|
||||
switch (number)
|
||||
{
|
||||
case 10:
|
||||
s = "X";
|
||||
break;
|
||||
case > 18:
|
||||
number += 2;
|
||||
s = number.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
|
||||
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
|
||||
if (number <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
|
||||
|
||||
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
|
||||
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
|
||||
var d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..]));
|
||||
return Utils.RemoveHtmlTags(string.Format(introduced, d));
|
||||
}
|
||||
|
||||
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
||||
{
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
|
||||
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
|
||||
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
|
||||
CosmeticSource = action.Text["Athena.ItemAction.".Length..];
|
||||
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
|
||||
Description += GetCosmeticSet(set.Text);
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
|
||||
Description += GetCosmeticSeason(season.Text);
|
||||
|
||||
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
|
||||
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
|
||||
}
|
||||
|
||||
protected void GetUserFacingFlags(IList<string> userFacingFlags)
|
||||
{
|
||||
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
|
||||
return;
|
||||
|
||||
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
|
||||
return;
|
||||
|
||||
UserFacingFlags = new Dictionary<string, SKBitmap>(userFacingFlags.Count);
|
||||
foreach (var flag in userFacingFlags)
|
||||
{
|
||||
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
|
||||
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
|
||||
else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var category in tertiaryCategories)
|
||||
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
|
||||
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
|
||||
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
|
||||
{
|
||||
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
|
||||
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
|
||||
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
|
||||
{
|
||||
UserFacingFlags[flag] = Utils.GetBitmap(texture);
|
||||
}
|
||||
UserFacingFlags[flag] = Utils.GetBitmap(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawUserFacingFlags(SKCanvas c)
|
||||
private void DrawUserFacingFlags(SKCanvas c)
|
||||
{
|
||||
if (UserFacingFlags == null) return;
|
||||
|
||||
const int size = 25;
|
||||
var x = Margin * (int) 2.5;
|
||||
foreach (var flag in UserFacingFlags.Values.Where(flag => flag != null))
|
||||
{
|
||||
if (UserFacingFlags == null) return;
|
||||
|
||||
const int size = 25;
|
||||
var x = Margin * (int) 2.5;
|
||||
foreach (var flag in UserFacingFlags.Values)
|
||||
{
|
||||
if (flag == null) continue;
|
||||
|
||||
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
|
||||
x += size;
|
||||
}
|
||||
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
|
||||
x += size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,280 +13,279 @@ using FModel.Framework;
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseIconStats : BaseIcon
|
||||
{
|
||||
public class BaseIconStats : BaseIcon
|
||||
private readonly IList<IconStat> _statistics;
|
||||
private const int _headerHeight = 128;
|
||||
private bool _screenLayer;
|
||||
|
||||
public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
private readonly IList<IconStat> _statistics;
|
||||
private const int _headerHeight = 128;
|
||||
private bool _screenLayer;
|
||||
|
||||
public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Width = 1024;
|
||||
Height = _headerHeight;
|
||||
Margin = 0;
|
||||
_statistics = new List<IconStat>();
|
||||
_screenLayer = uObject.ExportType.Equals("FortAccoladeItemDefinition", StringComparison.OrdinalIgnoreCase);
|
||||
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
base.ParseForInfo();
|
||||
DisplayName = DisplayName.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FName accoladeType, "AccoladeType") &&
|
||||
accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_screenLayer = false;
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") &&
|
||||
Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) &&
|
||||
uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
|
||||
{
|
||||
foreach (var location in poiLocations)
|
||||
{
|
||||
var locationName = "Unknown";
|
||||
foreach (var poi in challengeMapPoiData)
|
||||
{
|
||||
if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") ||
|
||||
!tagName.Text.Equals(location.Text, StringComparison.OrdinalIgnoreCase) || !poi.TryGetValue(out FText text, "Text")) continue;
|
||||
|
||||
locationName = text.Text;
|
||||
break;
|
||||
}
|
||||
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "B0C091D7409B1657423C5F97E9CF4C77", "LOCATION NAME"), locationName.ToUpper()));
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback maxStackSize, "MaxStackSize"))
|
||||
{
|
||||
if (maxStackSize.TryGetValue(out float v, "Value") && v > 0)
|
||||
{
|
||||
_statistics.Add(new IconStat("Max Stack", v , 15));
|
||||
}
|
||||
else if (TryGetCurveTableStat(maxStackSize, out var s))
|
||||
{
|
||||
_statistics.Add(new IconStat("Max Stack", s , 15));
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback xpRewardAmount, "XpRewardAmount") && TryGetCurveTableStat(xpRewardAmount, out var x))
|
||||
{
|
||||
_statistics.Add(new IconStat("XP Amount", x));
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback weaponStatHandle, "WeaponStatHandle") &&
|
||||
weaponStatHandle.TryGetValue(out FName weaponRowName, "RowName") &&
|
||||
weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") &&
|
||||
dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue))
|
||||
{
|
||||
if (weaponRowValue.TryGetValue(out int bpc, "BulletsPerCartridge"))
|
||||
{
|
||||
var multiplier = bpc != 0f ? bpc : 1;
|
||||
if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200));
|
||||
}
|
||||
|
||||
if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"))
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200));
|
||||
}
|
||||
}
|
||||
|
||||
if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50));
|
||||
}
|
||||
|
||||
if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15));
|
||||
}
|
||||
|
||||
if (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125));
|
||||
}
|
||||
|
||||
if (weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime") && reloadTime != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 15));
|
||||
}
|
||||
|
||||
if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) ||
|
||||
Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) &&
|
||||
weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") &&
|
||||
weaponRowValue.TryGetValue(out FName durabilityRowName, "DurabilityRowName") &&
|
||||
durabilityTable.TryGetDataTableRow(durabilityRowName.Text, StringComparison.OrdinalIgnoreCase, out var durability) &&
|
||||
durability.TryGetValue(out int duraByRarity, Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription()))
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20));
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
Height += 40 + (int) _informationPaint.TextSize * Utils.SplitLines(Description, _informationPaint, Width - 20).Count;
|
||||
Height += 50 * _statistics.Count;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
DrawDisplayName(c);
|
||||
DrawStatistics(c);
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
|
||||
private bool TryGetCurveTableStat(FStructFallback property, out float statValue)
|
||||
{
|
||||
if (property.TryGetValue(out FStructFallback curve, "Curve") &&
|
||||
curve.TryGetValue(out FName rowName, "RowName") &&
|
||||
curve.TryGetValue(out UCurveTable curveTable, "CurveTable") &&
|
||||
curveTable.TryFindCurve(rowName, out var rowValue) &&
|
||||
rowValue is FSimpleCurve s && s.Keys.Length > 0)
|
||||
{
|
||||
statValue = s.Keys[0].Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
statValue = 0F;
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly SKPaint _informationPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColor.Parse("#262630"), TextSize = 16,
|
||||
Typeface = Utils.Typefaces.Description
|
||||
};
|
||||
|
||||
private void DrawHeader(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] {Background[0].WithAlpha(180), Background[1].WithAlpha(220)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(_headerHeight, 0, Width, _headerHeight), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] {SKColor.Parse("#262630"), SKColor.Parse("#1f1f26")}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, _headerHeight, Width, Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
|
||||
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
|
||||
using var rect = new SKPath {FillType = SKPathFillType.EvenOdd};
|
||||
rect.MoveTo(0, 0);
|
||||
rect.LineTo(_headerHeight + _headerHeight / 3, 0);
|
||||
rect.LineTo(_headerHeight, _headerHeight);
|
||||
rect.LineTo(0, _headerHeight);
|
||||
rect.Close();
|
||||
c.DrawPath(rect, _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(_headerHeight / 2, _headerHeight / 2), new SKPoint(_headerHeight / 2 + 100, _headerHeight / 2),
|
||||
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawPath(rect, _informationPaint);
|
||||
|
||||
_informationPaint.Shader = null;
|
||||
|
||||
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
|
||||
c.DrawBitmap((Preview ?? DefaultPreview).Resize(_headerHeight), 0, 0, ImagePaint);
|
||||
}
|
||||
|
||||
private new void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName)) return;
|
||||
|
||||
_informationPaint.TextSize = 50;
|
||||
_informationPaint.Color = SKColors.White;
|
||||
_informationPaint.Typeface = Utils.Typefaces.Bundle;
|
||||
while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2)
|
||||
{
|
||||
_informationPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_informationPaint.Typeface);
|
||||
shaper.Shape(DisplayName, _informationPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2 + _informationPaint.TextSize / 3, _informationPaint);
|
||||
}
|
||||
|
||||
private void DrawStatistics(SKCanvas c)
|
||||
{
|
||||
var outY = _headerHeight + 25f;
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
{
|
||||
_informationPaint.TextSize = 16;
|
||||
_informationPaint.Color = SKColors.White.WithAlpha(175);
|
||||
_informationPaint.Typeface = Utils.Typefaces.Description;
|
||||
Utils.DrawMultilineText(c, Description, Width - 40, 0, SKTextAlign.Center,
|
||||
new SKRect(20, outY, Width - 20, Height), _informationPaint, out outY);
|
||||
outY += 25;
|
||||
}
|
||||
|
||||
foreach (var stat in _statistics)
|
||||
{
|
||||
stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY);
|
||||
outY += 50;
|
||||
}
|
||||
}
|
||||
Width = 1024;
|
||||
Height = _headerHeight;
|
||||
Margin = 0;
|
||||
_statistics = new List<IconStat>();
|
||||
_screenLayer = uObject.ExportType.Equals("FortAccoladeItemDefinition", StringComparison.OrdinalIgnoreCase);
|
||||
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
|
||||
}
|
||||
|
||||
public class IconStat
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
private readonly string _statName;
|
||||
private readonly object _value;
|
||||
private readonly float _maxValue;
|
||||
base.ParseForInfo();
|
||||
DisplayName = DisplayName.ToUpperInvariant();
|
||||
|
||||
public IconStat(string statName, object value, float maxValue = 0)
|
||||
if (Object.TryGetValue(out FName accoladeType, "AccoladeType") &&
|
||||
accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_statName = statName.ToUpperInvariant();
|
||||
_value = value;
|
||||
_maxValue = maxValue;
|
||||
_screenLayer = false;
|
||||
}
|
||||
|
||||
private readonly SKPaint _statPaint = new()
|
||||
if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") &&
|
||||
Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) &&
|
||||
uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
TextSize = 25, Typeface = Utils.Typefaces.DisplayName,
|
||||
Color = SKColors.White
|
||||
};
|
||||
|
||||
public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y)
|
||||
{
|
||||
while (_statPaint.MeasureText(_statName) > height * 2 - 40)
|
||||
foreach (var location in poiLocations)
|
||||
{
|
||||
_statPaint.TextSize -= 1;
|
||||
var locationName = "Unknown";
|
||||
foreach (var poi in challengeMapPoiData)
|
||||
{
|
||||
if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") ||
|
||||
!tagName.Text.Equals(location.Text, StringComparison.OrdinalIgnoreCase) || !poi.TryGetValue(out FText text, "Text")) continue;
|
||||
|
||||
locationName = text.Text;
|
||||
break;
|
||||
}
|
||||
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "B0C091D7409B1657423C5F97E9CF4C77", "LOCATION NAME"), locationName.ToUpper()));
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback maxStackSize, "MaxStackSize"))
|
||||
{
|
||||
if (maxStackSize.TryGetValue(out float v, "Value") && v > 0)
|
||||
{
|
||||
_statistics.Add(new IconStat("Max Stack", v, 15));
|
||||
}
|
||||
else if (TryGetCurveTableStat(maxStackSize, out var s))
|
||||
{
|
||||
_statistics.Add(new IconStat("Max Stack", s, 15));
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback xpRewardAmount, "XpRewardAmount") && TryGetCurveTableStat(xpRewardAmount, out var x))
|
||||
{
|
||||
_statistics.Add(new IconStat("XP Amount", x));
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback weaponStatHandle, "WeaponStatHandle") &&
|
||||
weaponStatHandle.TryGetValue(out FName weaponRowName, "RowName") &&
|
||||
weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") &&
|
||||
dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue))
|
||||
{
|
||||
if (weaponRowValue.TryGetValue(out int bpc, "BulletsPerCartridge"))
|
||||
{
|
||||
var multiplier = bpc != 0f ? bpc : 1;
|
||||
if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200));
|
||||
}
|
||||
|
||||
if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"))
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200));
|
||||
}
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_statPaint.Typeface);
|
||||
shaper.Shape(_statName, _statPaint);
|
||||
c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint);
|
||||
if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50));
|
||||
}
|
||||
|
||||
_statPaint.TextAlign = SKTextAlign.Right;
|
||||
_statPaint.Typeface = Utils.Typefaces.BundleNumber;
|
||||
_statPaint.Color = sliderColor;
|
||||
var sliderRight = width - 100 - _statPaint.MeasureText(_value.ToString());
|
||||
c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint);
|
||||
if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15));
|
||||
}
|
||||
|
||||
_statPaint.Color = SKColors.White;
|
||||
c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint);
|
||||
if (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125));
|
||||
}
|
||||
|
||||
if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return;
|
||||
if (floatValue < 0)
|
||||
floatValue = 0;
|
||||
var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue);
|
||||
c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint);
|
||||
if (weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime") && reloadTime != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 15));
|
||||
}
|
||||
|
||||
if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) ||
|
||||
Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) &&
|
||||
weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") &&
|
||||
weaponRowValue.TryGetValue(out FName durabilityRowName, "DurabilityRowName") &&
|
||||
durabilityTable.TryGetDataTableRow(durabilityRowName.Text, StringComparison.OrdinalIgnoreCase, out var durability) &&
|
||||
durability.TryGetValue(out int duraByRarity, Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription()))
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20));
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
Height += 40 + (int) _informationPaint.TextSize * Utils.SplitLines(Description, _informationPaint, Width - 20).Count;
|
||||
Height += 50 * _statistics.Count;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
DrawDisplayName(c);
|
||||
DrawStatistics(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private bool TryGetCurveTableStat(FStructFallback property, out float statValue)
|
||||
{
|
||||
if (property.TryGetValue(out FStructFallback curve, "Curve") &&
|
||||
curve.TryGetValue(out FName rowName, "RowName") &&
|
||||
curve.TryGetValue(out UCurveTable curveTable, "CurveTable") &&
|
||||
curveTable.TryFindCurve(rowName, out var rowValue) &&
|
||||
rowValue is FSimpleCurve s && s.Keys.Length > 0)
|
||||
{
|
||||
statValue = s.Keys[0].Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
statValue = 0F;
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly SKPaint _informationPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColor.Parse("#262630"), TextSize = 16,
|
||||
Typeface = Utils.Typefaces.Description
|
||||
};
|
||||
|
||||
private void DrawHeader(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] { Background[0].WithAlpha(180), Background[1].WithAlpha(220) }, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(_headerHeight, 0, Width, _headerHeight), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] { SKColor.Parse("#262630"), SKColor.Parse("#1f1f26") }, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, _headerHeight, Width, Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
|
||||
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
|
||||
using var rect = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
rect.MoveTo(0, 0);
|
||||
rect.LineTo(_headerHeight + _headerHeight / 3, 0);
|
||||
rect.LineTo(_headerHeight, _headerHeight);
|
||||
rect.LineTo(0, _headerHeight);
|
||||
rect.Close();
|
||||
c.DrawPath(rect, _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(_headerHeight / 2, _headerHeight / 2), new SKPoint(_headerHeight / 2 + 100, _headerHeight / 2),
|
||||
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
|
||||
c.DrawPath(rect, _informationPaint);
|
||||
|
||||
_informationPaint.Shader = null;
|
||||
|
||||
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
|
||||
c.DrawBitmap((Preview ?? DefaultPreview).Resize(_headerHeight), 0, 0, ImagePaint);
|
||||
}
|
||||
|
||||
private new void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName)) return;
|
||||
|
||||
_informationPaint.TextSize = 50;
|
||||
_informationPaint.Color = SKColors.White;
|
||||
_informationPaint.Typeface = Utils.Typefaces.Bundle;
|
||||
while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2)
|
||||
{
|
||||
_informationPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_informationPaint.Typeface);
|
||||
shaper.Shape(DisplayName, _informationPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2 + _informationPaint.TextSize / 3, _informationPaint);
|
||||
}
|
||||
|
||||
private void DrawStatistics(SKCanvas c)
|
||||
{
|
||||
var outY = _headerHeight + 25f;
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
{
|
||||
_informationPaint.TextSize = 16;
|
||||
_informationPaint.Color = SKColors.White.WithAlpha(175);
|
||||
_informationPaint.Typeface = Utils.Typefaces.Description;
|
||||
Utils.DrawMultilineText(c, Description, Width - 40, 0, SKTextAlign.Center,
|
||||
new SKRect(20, outY, Width - 20, Height), _informationPaint, out outY);
|
||||
outY += 25;
|
||||
}
|
||||
|
||||
foreach (var stat in _statistics)
|
||||
{
|
||||
stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY);
|
||||
outY += 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class IconStat
|
||||
{
|
||||
private readonly string _statName;
|
||||
private readonly object _value;
|
||||
private readonly float _maxValue;
|
||||
|
||||
public IconStat(string statName, object value, float maxValue = 0)
|
||||
{
|
||||
_statName = statName.ToUpperInvariant();
|
||||
_value = value;
|
||||
_maxValue = maxValue;
|
||||
}
|
||||
|
||||
private readonly SKPaint _statPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
TextSize = 25, Typeface = Utils.Typefaces.DisplayName,
|
||||
Color = SKColors.White
|
||||
};
|
||||
|
||||
public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y)
|
||||
{
|
||||
while (_statPaint.MeasureText(_statName) > height * 2 - 40)
|
||||
{
|
||||
_statPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_statPaint.Typeface);
|
||||
shaper.Shape(_statName, _statPaint);
|
||||
c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint);
|
||||
|
||||
_statPaint.TextAlign = SKTextAlign.Right;
|
||||
_statPaint.Typeface = Utils.Typefaces.BundleNumber;
|
||||
_statPaint.Color = sliderColor;
|
||||
var sliderRight = width - 100 - _statPaint.MeasureText(_value.ToString());
|
||||
c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint);
|
||||
|
||||
_statPaint.Color = SKColors.White;
|
||||
c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint);
|
||||
|
||||
if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return;
|
||||
if (floatValue < 0)
|
||||
floatValue = 0;
|
||||
var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue);
|
||||
c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,96 +5,95 @@ using FModel.Framework;
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseItemAccessToken : UCreator
|
||||
{
|
||||
public class BaseItemAccessToken : UCreator
|
||||
private readonly SKBitmap _locked, _unlocked;
|
||||
private string _unlockedDescription, _exportName;
|
||||
private BaseIcon _icon;
|
||||
|
||||
public BaseItemAccessToken(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
private readonly SKBitmap _locked, _unlocked;
|
||||
private string _unlockedDescription, _exportName;
|
||||
private BaseIcon _icon;
|
||||
|
||||
public BaseItemAccessToken(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
_unlocked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128.T-Icon-Unlocked-128").Resize(24);
|
||||
_locked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.T-Icon-Lock-128").Resize(24);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FPackageIndex accessItem, "access_item") &&
|
||||
Utils.TryGetPackageIndexExport(accessItem, out UObject uObject))
|
||||
{
|
||||
_exportName = uObject.Name;
|
||||
_icon = new BaseIcon(uObject, EIconStyle.Default);
|
||||
_icon.ParseForReward(false);
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName") && displayName.Text != "TBD")
|
||||
DisplayName = displayName.Text;
|
||||
else
|
||||
DisplayName = _icon?.DisplayName;
|
||||
|
||||
Description = Object.TryGetValue(out FText description, "Description") ? description.Text : _icon?.Description;
|
||||
if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
Preview = _icon.Preview;
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
Preview = _icon.Preview;
|
||||
_icon.DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
break;
|
||||
default:
|
||||
_icon.DrawBackground(c);
|
||||
DrawInformation(c);
|
||||
DrawToBottom(c, SKTextAlign.Right, _exportName);
|
||||
break;
|
||||
}
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
|
||||
private void DrawInformation(SKCanvas c)
|
||||
{
|
||||
var size = 45;
|
||||
var left = Width / 2;
|
||||
|
||||
while (DisplayNamePaint.MeasureText(DisplayName) > Width - _icon.Margin * 2)
|
||||
{
|
||||
DisplayNamePaint.TextSize = size -= 2;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
c.DrawShapedText(shaper, DisplayName, left - shapedText.Points[^1].X / 2, _icon.Margin * 8 + size, DisplayNamePaint);
|
||||
|
||||
float topBase = _icon.Margin + size * 2;
|
||||
if (!string.IsNullOrEmpty(_unlockedDescription))
|
||||
{
|
||||
c.DrawBitmap(_locked, new SKRect(50, topBase, 50 + _locked.Width, topBase + _locked.Height), ImagePaint);
|
||||
Utils.DrawMultilineText(c, _unlockedDescription, Width, _icon.Margin, SKTextAlign.Left,
|
||||
new SKRect(70 + _locked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
{
|
||||
c.DrawBitmap(_unlocked, new SKRect(50, topBase, 50 + _unlocked.Width, topBase + _unlocked.Height), ImagePaint);
|
||||
Utils.DrawMultilineText(c, Description, Width, _icon.Margin, SKTextAlign.Left,
|
||||
new SKRect(70 + _unlocked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
|
||||
}
|
||||
|
||||
var h = Width - _icon.Margin - topBase;
|
||||
c.DrawBitmap(_icon.Preview ?? _icon.DefaultPreview, new SKRect(left - h / 2, topBase, left + h / 2, Width - _icon.Margin), ImagePaint);
|
||||
}
|
||||
_unlocked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128.T-Icon-Unlocked-128").Resize(24);
|
||||
_locked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.T-Icon-Lock-128").Resize(24);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FPackageIndex accessItem, "access_item") &&
|
||||
Utils.TryGetPackageIndexExport(accessItem, out UObject uObject))
|
||||
{
|
||||
_exportName = uObject.Name;
|
||||
_icon = new BaseIcon(uObject, EIconStyle.Default);
|
||||
_icon.ParseForReward(false);
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName") && displayName.Text != "TBD")
|
||||
DisplayName = displayName.Text;
|
||||
else
|
||||
DisplayName = _icon?.DisplayName;
|
||||
|
||||
Description = Object.TryGetValue(out FText description, "Description") ? description.Text : _icon?.Description;
|
||||
if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
Preview = _icon.Preview;
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
Preview = _icon.Preview;
|
||||
_icon.DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
break;
|
||||
default:
|
||||
_icon.DrawBackground(c);
|
||||
DrawInformation(c);
|
||||
DrawToBottom(c, SKTextAlign.Right, _exportName);
|
||||
break;
|
||||
}
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private void DrawInformation(SKCanvas c)
|
||||
{
|
||||
var size = 45;
|
||||
var left = Width / 2;
|
||||
|
||||
while (DisplayNamePaint.MeasureText(DisplayName) > Width - _icon.Margin * 2)
|
||||
{
|
||||
DisplayNamePaint.TextSize = size -= 2;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
c.DrawShapedText(shaper, DisplayName, left - shapedText.Points[^1].X / 2, _icon.Margin * 8 + size, DisplayNamePaint);
|
||||
|
||||
float topBase = _icon.Margin + size * 2;
|
||||
if (!string.IsNullOrEmpty(_unlockedDescription))
|
||||
{
|
||||
c.DrawBitmap(_locked, new SKRect(50, topBase, 50 + _locked.Width, topBase + _locked.Height), ImagePaint);
|
||||
Utils.DrawMultilineText(c, _unlockedDescription, Width, _icon.Margin, SKTextAlign.Left,
|
||||
new SKRect(70 + _locked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
{
|
||||
c.DrawBitmap(_unlocked, new SKRect(50, topBase, 50 + _unlocked.Width, topBase + _unlocked.Height), ImagePaint);
|
||||
Utils.DrawMultilineText(c, Description, Width, _icon.Margin, SKTextAlign.Left,
|
||||
new SKRect(70 + _unlocked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
|
||||
}
|
||||
|
||||
var h = Width - _icon.Margin - topBase;
|
||||
c.DrawBitmap(_icon.Preview ?? _icon.DefaultPreview, new SKRect(left - h / 2, topBase, left + h / 2, Width - _icon.Margin), ImagePaint);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,83 +5,82 @@ using CUE4Parse.UE4.Assets.Exports.Texture;
|
|||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseMaterialInstance : BaseIcon
|
||||
{
|
||||
public class BaseMaterialInstance : BaseIcon
|
||||
public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
Background = new[] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
|
||||
Border = new[] { SKColor.Parse("9092AB") };
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object is not UMaterialInstanceConstant material) return;
|
||||
|
||||
texture_finding:
|
||||
foreach (var textureParameter in material.TextureParameterValues) // get texture from base material
|
||||
{
|
||||
Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")};
|
||||
Border = new[] {SKColor.Parse("9092AB")};
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object is not UMaterialInstanceConstant material) return;
|
||||
|
||||
texture_finding:
|
||||
foreach (var textureParameter in material.TextureParameterValues) // get texture from base material
|
||||
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture) || Preview != null) continue;
|
||||
switch (textureParameter.ParameterInfo.Name.Text)
|
||||
{
|
||||
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture) || Preview != null) continue;
|
||||
switch (textureParameter.ParameterInfo.Name.Text)
|
||||
{
|
||||
case "SeriesTexture":
|
||||
GetSeries(texture);
|
||||
break;
|
||||
case "TextureA":
|
||||
case "TextureB":
|
||||
case "OfferImage":
|
||||
Preview = Utils.GetBitmap(texture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (material.VectorParameterValues.Length == 0 || // try to get color from parent if not found here
|
||||
material.VectorParameterValues.All(x => x.ParameterInfo.Name.Text.Equals("FallOff_Color"))) // use parent if it only contains FallOff_Color
|
||||
{
|
||||
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
|
||||
Utils.TryGetPackageIndexExport(parent, out material);
|
||||
else return;
|
||||
|
||||
if (material == null) return;
|
||||
}
|
||||
|
||||
if (Preview == null)
|
||||
goto texture_finding;
|
||||
|
||||
foreach (var vectorParameter in material.VectorParameterValues)
|
||||
{
|
||||
if (vectorParameter.ParameterValue == null) continue;
|
||||
switch (vectorParameter.ParameterInfo.Name.Text)
|
||||
{
|
||||
case "Background_Color_A":
|
||||
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
|
||||
Border[0] = Background[0];
|
||||
break;
|
||||
case "Background_Color_B": // Border color can be defaulted here in some case where Background_Color_A should be taken from parent but Background_Color_B from base
|
||||
Background[1] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
|
||||
break;
|
||||
}
|
||||
case "SeriesTexture":
|
||||
GetSeries(texture);
|
||||
break;
|
||||
case "TextureA":
|
||||
case "TextureB":
|
||||
case "OfferImage":
|
||||
Preview = Utils.GetBitmap(texture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
while (material.VectorParameterValues.Length == 0 || // try to get color from parent if not found here
|
||||
material.VectorParameterValues.All(x => x.ParameterInfo.Name.Text.Equals("FallOff_Color"))) // use parent if it only contains FallOff_Color
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
|
||||
Utils.TryGetPackageIndexExport(parent, out material);
|
||||
else return;
|
||||
|
||||
switch (Style)
|
||||
if (material == null) return;
|
||||
}
|
||||
|
||||
if (Preview == null)
|
||||
goto texture_finding;
|
||||
|
||||
foreach (var vectorParameter in material.VectorParameterValues)
|
||||
{
|
||||
if (vectorParameter.ParameterValue == null) continue;
|
||||
switch (vectorParameter.ParameterInfo.Name.Text)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
case "Background_Color_A":
|
||||
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
|
||||
Border[0] = Background[0];
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
case "Background_Color_B": // Border color can be defaulted here in some case where Background_Color_A should be taken from parent but Background_Color_B from base
|
||||
Background[1] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
|
||||
break;
|
||||
}
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
break;
|
||||
}
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
|
|
@ -5,81 +5,80 @@ using CUE4Parse.UE4.Objects.Core.Math;
|
|||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseMtxOffer : UCreator
|
||||
{
|
||||
public class BaseMtxOffer : UCreator
|
||||
public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
Background = new[] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
|
||||
Border = new[] { SKColor.Parse("9092AB") };
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FStructFallback typeImage, "DetailsImage", "TileImage") &&
|
||||
typeImage.TryGetValue(out FPackageIndex resource, "ResourceObject"))
|
||||
{
|
||||
Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")};
|
||||
Border = new[] {SKColor.Parse("9092AB")};
|
||||
Preview = Utils.GetBitmap(resource);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
if (Object.TryGetValue(out FStructFallback gradient, "Gradient") &&
|
||||
gradient.TryGetValue(out FLinearColor start, "Start") &&
|
||||
gradient.TryGetValue(out FLinearColor stop, "Stop"))
|
||||
{
|
||||
if (Object.TryGetValue(out FStructFallback typeImage, "DetailsImage", "TileImage") &&
|
||||
typeImage.TryGetValue(out FPackageIndex resource, "ResourceObject"))
|
||||
{
|
||||
Preview = Utils.GetBitmap(resource);
|
||||
}
|
||||
Background = new[] { SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex) };
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback gradient, "Gradient") &&
|
||||
gradient.TryGetValue(out FLinearColor start, "Start") &&
|
||||
gradient.TryGetValue(out FLinearColor stop, "Stop"))
|
||||
{
|
||||
Background = new[] {SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex)};
|
||||
}
|
||||
if (Object.TryGetValue(out FLinearColor background, "Background"))
|
||||
Border = new[] { SKColor.Parse(background.Hex) };
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText shortDescription, "ShortDescription"))
|
||||
Description = shortDescription.Text;
|
||||
|
||||
if (Object.TryGetValue(out FLinearColor background, "Background"))
|
||||
Border = new[] {SKColor.Parse(background.Hex)};
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText shortDescription, "ShortDescription"))
|
||||
Description = shortDescription.Text;
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] details, "DetailsAttributes"))
|
||||
if (Object.TryGetValue(out FStructFallback[] details, "DetailsAttributes"))
|
||||
{
|
||||
foreach (var detail in details)
|
||||
{
|
||||
foreach (var detail in details)
|
||||
if (detail.TryGetValue(out FText detailName, "Name"))
|
||||
{
|
||||
if (detail.TryGetValue(out FText detailName, "Name"))
|
||||
{
|
||||
Description += $"\n- {detailName.Text.TrimEnd()}";
|
||||
}
|
||||
Description += $"\n- {detailName.Text.TrimEnd()}";
|
||||
}
|
||||
|
||||
if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text))
|
||||
{
|
||||
Description += $" ({detailValue.Text})";
|
||||
}
|
||||
if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text))
|
||||
{
|
||||
Description += $" ({detailValue.Text})";
|
||||
}
|
||||
}
|
||||
|
||||
Description = Utils.RemoveHtmlTags(Description);
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
break;
|
||||
}
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
Description = Utils.RemoveHtmlTags(Description);
|
||||
}
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
break;
|
||||
}
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
|
|
@ -2,39 +2,38 @@ using CUE4Parse.UE4.Assets.Exports;
|
|||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseOfferDisplayData : UCreator
|
||||
{
|
||||
public class BaseOfferDisplayData : UCreator
|
||||
private BaseMaterialInstance[] _offerImages;
|
||||
|
||||
public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
private BaseMaterialInstance[] _offerImages;
|
||||
}
|
||||
|
||||
public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (!Object.TryGetValue(out UMaterialInterface[] presentations, "Presentations"))
|
||||
return;
|
||||
|
||||
_offerImages = new BaseMaterialInstance[presentations.Length];
|
||||
for (var i = 0; i < _offerImages.Length; i++)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (!Object.TryGetValue(out UMaterialInterface[] presentations, "Presentations"))
|
||||
return;
|
||||
|
||||
_offerImages = new BaseMaterialInstance[presentations.Length];
|
||||
for (int i = 0; i < _offerImages.Length; i++)
|
||||
{
|
||||
var offerImage = new BaseMaterialInstance(presentations[i], Style);
|
||||
offerImage.ParseForInfo();
|
||||
_offerImages[i] = offerImage;
|
||||
}
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap[_offerImages.Length];
|
||||
for (int i = 0; i < ret.Length; i++)
|
||||
{
|
||||
ret[i] = _offerImages[i].Draw()[0];
|
||||
}
|
||||
|
||||
return ret;
|
||||
var offerImage = new BaseMaterialInstance(presentations[i], Style);
|
||||
offerImage.ParseForInfo();
|
||||
_offerImages[i] = offerImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap[_offerImages.Length];
|
||||
for (var i = 0; i < ret.Length; i++)
|
||||
{
|
||||
ret[i] = _offerImages[i].Draw()[0];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,73 +6,72 @@ using FModel.Services;
|
|||
using FModel.ViewModels;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BasePlaylist : UCreator
|
||||
{
|
||||
public class BasePlaylist : UCreator
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
private SKBitmap _missionIcon;
|
||||
|
||||
public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
private SKBitmap _missionIcon;
|
||||
|
||||
public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Margin = 0;
|
||||
Width = 1024;
|
||||
Height = 512;
|
||||
Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Tiles/T_Athena_Tile_Matchmaking_Default.T_Athena_Tile_Matchmaking_Default");
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FText displayName, "UIDisplayName", "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "UIDescription", "Description"))
|
||||
Description = description.Text;
|
||||
if (Object.TryGetValue(out UTexture2D missionIcon, "MissionIcon"))
|
||||
_missionIcon = Utils.GetBitmap(missionIcon).Resize(25);
|
||||
|
||||
if (!Object.TryGetValue(out FName playlistName, "PlaylistName") || string.IsNullOrWhiteSpace(playlistName.Text))
|
||||
return;
|
||||
|
||||
var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text);
|
||||
if (!playlist.IsSuccess || !playlist.Data.Images.HasShowcase ||
|
||||
!_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image))
|
||||
return;
|
||||
|
||||
Preview = Utils.GetBitmap(image).ResizeWithRatio(1024, 512);
|
||||
Width = Preview.Width;
|
||||
Height = Preview.Height;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawPreview(c);
|
||||
DrawMissionIcon(c);
|
||||
break;
|
||||
default:
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
DrawMissionIcon(c);
|
||||
break;
|
||||
}
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
|
||||
private void DrawMissionIcon(SKCanvas c)
|
||||
{
|
||||
if (_missionIcon == null) return;
|
||||
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
|
||||
}
|
||||
Margin = 0;
|
||||
Width = 1024;
|
||||
Height = 512;
|
||||
Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Tiles/T_Athena_Tile_Matchmaking_Default.T_Athena_Tile_Matchmaking_Default");
|
||||
}
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FText displayName, "UIDisplayName", "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "UIDescription", "Description"))
|
||||
Description = description.Text;
|
||||
if (Object.TryGetValue(out UTexture2D missionIcon, "MissionIcon"))
|
||||
_missionIcon = Utils.GetBitmap(missionIcon).Resize(25);
|
||||
|
||||
if (!Object.TryGetValue(out FName playlistName, "PlaylistName") || string.IsNullOrWhiteSpace(playlistName.Text))
|
||||
return;
|
||||
|
||||
var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text);
|
||||
if (!playlist.IsSuccess || !playlist.Data.Images.HasShowcase ||
|
||||
!_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image))
|
||||
return;
|
||||
|
||||
Preview = Utils.GetBitmap(image).ResizeWithRatio(1024, 512);
|
||||
Width = Preview.Width;
|
||||
Height = Preview.Height;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawPreview(c);
|
||||
DrawMissionIcon(c);
|
||||
break;
|
||||
default:
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
DrawMissionIcon(c);
|
||||
break;
|
||||
}
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private void DrawMissionIcon(SKCanvas c)
|
||||
{
|
||||
if (_missionIcon == null) return;
|
||||
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,256 +12,255 @@ using FModel.Framework;
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseQuest : BaseIcon
|
||||
{
|
||||
public class BaseQuest : BaseIcon
|
||||
private int _count;
|
||||
private Reward _reward;
|
||||
private readonly bool _screenLayer;
|
||||
private readonly string[] _unauthorizedReward = { "Token", "ChallengeBundle", "GiftBox" };
|
||||
|
||||
public string NextQuestName { get; private set; }
|
||||
|
||||
public BaseQuest(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
private int _count;
|
||||
private Reward _reward;
|
||||
private readonly bool _screenLayer;
|
||||
private readonly string[] _unauthorizedReward = {"Token", "ChallengeBundle", "GiftBox"};
|
||||
|
||||
public string NextQuestName { get; private set; }
|
||||
|
||||
public BaseQuest(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
Margin = 0;
|
||||
Width = 1024;
|
||||
Height = 256;
|
||||
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
|
||||
if (uObject != null)
|
||||
{
|
||||
Margin = 0;
|
||||
Width = 1024;
|
||||
Height = 256;
|
||||
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
|
||||
if (uObject != null)
|
||||
{
|
||||
_screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
private BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion
|
||||
{
|
||||
var description = completionCount < 0 ?
|
||||
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">all {0} challenges</> to earn the reward item") :
|
||||
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">any {0} challenges</> to earn the reward item");
|
||||
|
||||
DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0);
|
||||
}
|
||||
|
||||
public BaseQuest(int completionCount, FSoftObjectPath itemDefinition, EIconStyle style) : this(completionCount, style) // completion
|
||||
{
|
||||
_reward = Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject) ? new Reward(uObject) : new Reward();
|
||||
}
|
||||
|
||||
public BaseQuest(int completionCount, int quantity, string reward, EIconStyle style) : this(completionCount, style) // completion
|
||||
{
|
||||
_reward = new Reward(quantity, reward);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
ParseForReward(false);
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback urgentQuestData, "UrgentQuestData"))
|
||||
{
|
||||
if (urgentQuestData.TryGetValue(out FText eventTitle, "EventTitle"))
|
||||
DisplayName = eventTitle.Text;
|
||||
if (urgentQuestData.TryGetValue(out FText eventDescription, "EventDescription"))
|
||||
Description = eventDescription.Text;
|
||||
if (urgentQuestData.TryGetValue(out FPackageIndex alertIcon, "AlertIcon", "BountyPriceImage"))
|
||||
Preview = Utils.GetBitmap(alertIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
Description = ShortDescription;
|
||||
if (Object.TryGetValue(out FText completionText, "CompletionText"))
|
||||
Description += "\n" + completionText.Text;
|
||||
if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
|
||||
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
|
||||
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") &&
|
||||
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out UObject iconObject))
|
||||
{
|
||||
Preview = iconObject switch
|
||||
{
|
||||
UTexture2D text => Utils.GetBitmap(text),
|
||||
UMaterialInstanceConstant mat => Utils.GetBitmap(mat),
|
||||
_ => Preview
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out int objectiveCompletionCount, "ObjectiveCompletionCount"))
|
||||
_count = objectiveCompletionCount;
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0)
|
||||
{
|
||||
// actual description doesn't exist
|
||||
if (string.IsNullOrEmpty(Description) && objectives[0].TryGetValue(out FText description, "Description"))
|
||||
Description = description.Text;
|
||||
|
||||
// ObjectiveCompletionCount doesn't exist
|
||||
if (_count == 0)
|
||||
{
|
||||
if (objectives[0].TryGetValue(out int count, "Count") && count > 1)
|
||||
_count = count;
|
||||
else
|
||||
_count = objectives.Length;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] rewards, "Rewards"))
|
||||
{
|
||||
foreach (var reward in rewards)
|
||||
{
|
||||
if (!reward.TryGetValue(out FStructFallback itemPrimaryAssetId, "ItemPrimaryAssetId") ||
|
||||
!reward.TryGetValue(out int quantity, "Quantity")) continue;
|
||||
|
||||
if (!itemPrimaryAssetId.TryGetValue(out FStructFallback primaryAssetType, "PrimaryAssetType") ||
|
||||
!itemPrimaryAssetId.TryGetValue(out FName primaryAssetName, "PrimaryAssetName") ||
|
||||
!primaryAssetType.TryGetValue(out FName name, "Name")) continue;
|
||||
|
||||
if (name.Text.Equals("Quest", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
NextQuestName = primaryAssetName.Text;
|
||||
}
|
||||
else if (!_unauthorizedReward.Contains(name.Text))
|
||||
{
|
||||
_reward = new Reward(quantity, $"{name}:{primaryAssetName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_reward == null && Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
|
||||
{
|
||||
if (rewardsTable.TryGetDataTableRow("Default", StringComparison.InvariantCulture, out var row))
|
||||
{
|
||||
if (row.TryGetValue(out FName templateId, "TemplateId") &&
|
||||
row.TryGetValue(out int quantity, "Quantity"))
|
||||
{
|
||||
_reward = new Reward(quantity, templateId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_reward == null && Object.TryGetValue(out FStructFallback[] hiddenRewards, "HiddenRewards"))
|
||||
{
|
||||
foreach (var hiddenReward in hiddenRewards)
|
||||
{
|
||||
if (!hiddenReward.TryGetValue(out FName templateId, "TemplateId") ||
|
||||
!hiddenReward.TryGetValue(out int quantity, "Quantity")) continue;
|
||||
|
||||
_reward = new Reward(quantity, templateId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_reward ??= new Reward();
|
||||
}
|
||||
|
||||
public void DrawQuest(SKCanvas c, int y)
|
||||
{
|
||||
DrawBackground(c, y);
|
||||
DrawPreview(c, y);
|
||||
DrawTexts(c, y);
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawQuest(c, 0);
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
|
||||
private string ReformatString(string s, string completionCount, bool isAll)
|
||||
{
|
||||
s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "<text color=\"FFF\" case=\"upper\" fontface=\"black\">{0}</>");
|
||||
var index = s.IndexOf("{0}|plural(", StringComparison.OrdinalIgnoreCase);
|
||||
if (index > -1)
|
||||
{
|
||||
var p = s.Substring(index, s[index..].IndexOf(')') + 1);
|
||||
s = s.Replace(p, string.Empty);
|
||||
s = s.Insert(s.IndexOf("</>", StringComparison.OrdinalIgnoreCase), p.SubstringAfter("(").SubstringAfter("=").SubstringBefore(","));
|
||||
}
|
||||
|
||||
var upper = s.SubstringAfter(">").SubstringBefore("</>");
|
||||
return string.Format(Utils.RemoveHtmlTags(s.Replace(upper, upper.ToUpper())), isAll ? string.Empty : completionCount).Replace(" ", " ");
|
||||
}
|
||||
|
||||
private readonly SKPaint _informationPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColor.Parse("#262630")
|
||||
};
|
||||
|
||||
private void DrawBackground(SKCanvas c, int y)
|
||||
{
|
||||
c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4,
|
||||
new[] {Background[0].WithAlpha(50), Background[1].WithAlpha(50)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Height / 2, y, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, y + Height), new SKPoint(Width / 2, 75),
|
||||
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, y + 75, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Margin, y, Height / 2, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Height / 2, y + Height / 2), new SKPoint(Height / 2 + 100, y + Height / 2),
|
||||
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Height / 2, y, Height / 2 + 100, y + Height), _informationPaint);
|
||||
}
|
||||
|
||||
private void DrawPreview(SKCanvas c, int y)
|
||||
{
|
||||
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
|
||||
c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, Height - Margin, y + Height), ImagePaint);
|
||||
}
|
||||
|
||||
private void DrawTexts(SKCanvas c, int y)
|
||||
{
|
||||
_informationPaint.Shader = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(DisplayName))
|
||||
{
|
||||
_informationPaint.TextSize = 40;
|
||||
_informationPaint.Color = SKColors.White;
|
||||
_informationPaint.Typeface = Utils.Typefaces.Bundle;
|
||||
while (_informationPaint.MeasureText(DisplayName) > Width - Height - 10)
|
||||
{
|
||||
_informationPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_informationPaint.Typeface);
|
||||
shaper.Shape(DisplayName, _informationPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint);
|
||||
}
|
||||
|
||||
var outY = y + 75f;
|
||||
if (!string.IsNullOrWhiteSpace(Description))
|
||||
{
|
||||
_informationPaint.TextSize = 16;
|
||||
_informationPaint.Color = SKColors.White.WithAlpha(175);
|
||||
_informationPaint.Typeface = Utils.Typefaces.Description;
|
||||
Utils.DrawMultilineText(c, Description, Width - Height, y, SKTextAlign.Left,
|
||||
new SKRect(Height, outY, Width - 10, y + Height), _informationPaint, out outY);
|
||||
}
|
||||
|
||||
_informationPaint.Color = Border[0].WithAlpha(100);
|
||||
c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint);
|
||||
|
||||
if (_count > 0)
|
||||
{
|
||||
_informationPaint.TextSize = 25;
|
||||
_informationPaint.Color = SKColors.White;
|
||||
_informationPaint.Typeface = Utils.Typefaces.BundleNumber;
|
||||
c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint);
|
||||
|
||||
_informationPaint.Color = Border[0];
|
||||
c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _informationPaint);
|
||||
}
|
||||
|
||||
_reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25));
|
||||
_screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion
|
||||
{
|
||||
var description = completionCount < 0 ?
|
||||
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">all {0} challenges</> to earn the reward item") :
|
||||
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">any {0} challenges</> to earn the reward item");
|
||||
|
||||
DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0);
|
||||
}
|
||||
|
||||
public BaseQuest(int completionCount, FSoftObjectPath itemDefinition, EIconStyle style) : this(completionCount, style) // completion
|
||||
{
|
||||
_reward = Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject) ? new Reward(uObject) : new Reward();
|
||||
}
|
||||
|
||||
public BaseQuest(int completionCount, int quantity, string reward, EIconStyle style) : this(completionCount, style) // completion
|
||||
{
|
||||
_reward = new Reward(quantity, reward);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
ParseForReward(false);
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback urgentQuestData, "UrgentQuestData"))
|
||||
{
|
||||
if (urgentQuestData.TryGetValue(out FText eventTitle, "EventTitle"))
|
||||
DisplayName = eventTitle.Text;
|
||||
if (urgentQuestData.TryGetValue(out FText eventDescription, "EventDescription"))
|
||||
Description = eventDescription.Text;
|
||||
if (urgentQuestData.TryGetValue(out FPackageIndex alertIcon, "AlertIcon", "BountyPriceImage"))
|
||||
Preview = Utils.GetBitmap(alertIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
Description = ShortDescription;
|
||||
if (Object.TryGetValue(out FText completionText, "CompletionText"))
|
||||
Description += "\n" + completionText.Text;
|
||||
if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
|
||||
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
|
||||
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") &&
|
||||
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out UObject iconObject))
|
||||
{
|
||||
Preview = iconObject switch
|
||||
{
|
||||
UTexture2D text => Utils.GetBitmap(text),
|
||||
UMaterialInstanceConstant mat => Utils.GetBitmap(mat),
|
||||
_ => Preview
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out int objectiveCompletionCount, "ObjectiveCompletionCount"))
|
||||
_count = objectiveCompletionCount;
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0)
|
||||
{
|
||||
// actual description doesn't exist
|
||||
if (string.IsNullOrEmpty(Description) && objectives[0].TryGetValue(out FText description, "Description"))
|
||||
Description = description.Text;
|
||||
|
||||
// ObjectiveCompletionCount doesn't exist
|
||||
if (_count == 0)
|
||||
{
|
||||
if (objectives[0].TryGetValue(out int count, "Count") && count > 1)
|
||||
_count = count;
|
||||
else
|
||||
_count = objectives.Length;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] rewards, "Rewards"))
|
||||
{
|
||||
foreach (var reward in rewards)
|
||||
{
|
||||
if (!reward.TryGetValue(out FStructFallback itemPrimaryAssetId, "ItemPrimaryAssetId") ||
|
||||
!reward.TryGetValue(out int quantity, "Quantity")) continue;
|
||||
|
||||
if (!itemPrimaryAssetId.TryGetValue(out FStructFallback primaryAssetType, "PrimaryAssetType") ||
|
||||
!itemPrimaryAssetId.TryGetValue(out FName primaryAssetName, "PrimaryAssetName") ||
|
||||
!primaryAssetType.TryGetValue(out FName name, "Name")) continue;
|
||||
|
||||
if (name.Text.Equals("Quest", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
NextQuestName = primaryAssetName.Text;
|
||||
}
|
||||
else if (!_unauthorizedReward.Contains(name.Text))
|
||||
{
|
||||
_reward = new Reward(quantity, $"{name}:{primaryAssetName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_reward == null && Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
|
||||
{
|
||||
if (rewardsTable.TryGetDataTableRow("Default", StringComparison.InvariantCulture, out var row))
|
||||
{
|
||||
if (row.TryGetValue(out FName templateId, "TemplateId") &&
|
||||
row.TryGetValue(out int quantity, "Quantity"))
|
||||
{
|
||||
_reward = new Reward(quantity, templateId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_reward == null && Object.TryGetValue(out FStructFallback[] hiddenRewards, "HiddenRewards"))
|
||||
{
|
||||
foreach (var hiddenReward in hiddenRewards)
|
||||
{
|
||||
if (!hiddenReward.TryGetValue(out FName templateId, "TemplateId") ||
|
||||
!hiddenReward.TryGetValue(out int quantity, "Quantity")) continue;
|
||||
|
||||
_reward = new Reward(quantity, templateId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_reward ??= new Reward();
|
||||
}
|
||||
|
||||
public void DrawQuest(SKCanvas c, int y)
|
||||
{
|
||||
DrawBackground(c, y);
|
||||
DrawPreview(c, y);
|
||||
DrawTexts(c, y);
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawQuest(c, 0);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private string ReformatString(string s, string completionCount, bool isAll)
|
||||
{
|
||||
s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "<text color=\"FFF\" case=\"upper\" fontface=\"black\">{0}</>");
|
||||
var index = s.IndexOf("{0}|plural(", StringComparison.OrdinalIgnoreCase);
|
||||
if (index > -1)
|
||||
{
|
||||
var p = s.Substring(index, s[index..].IndexOf(')') + 1);
|
||||
s = s.Replace(p, string.Empty);
|
||||
s = s.Insert(s.IndexOf("</>", StringComparison.OrdinalIgnoreCase), p.SubstringAfter("(").SubstringAfter("=").SubstringBefore(","));
|
||||
}
|
||||
|
||||
var upper = s.SubstringAfter(">").SubstringBefore("</>");
|
||||
return string.Format(Utils.RemoveHtmlTags(s.Replace(upper, upper.ToUpper())), isAll ? string.Empty : completionCount).Replace(" ", " ");
|
||||
}
|
||||
|
||||
private readonly SKPaint _informationPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColor.Parse("#262630")
|
||||
};
|
||||
|
||||
private void DrawBackground(SKCanvas c, int y)
|
||||
{
|
||||
c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4,
|
||||
new[] { Background[0].WithAlpha(50), Background[1].WithAlpha(50) }, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Height / 2, y, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, y + Height), new SKPoint(Width / 2, 75),
|
||||
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, y + 75, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Margin, y, Height / 2, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Height / 2, y + Height / 2), new SKPoint(Height / 2 + 100, y + Height / 2),
|
||||
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Height / 2, y, Height / 2 + 100, y + Height), _informationPaint);
|
||||
}
|
||||
|
||||
private void DrawPreview(SKCanvas c, int y)
|
||||
{
|
||||
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
|
||||
c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, Height - Margin, y + Height), ImagePaint);
|
||||
}
|
||||
|
||||
private void DrawTexts(SKCanvas c, int y)
|
||||
{
|
||||
_informationPaint.Shader = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(DisplayName))
|
||||
{
|
||||
_informationPaint.TextSize = 40;
|
||||
_informationPaint.Color = SKColors.White;
|
||||
_informationPaint.Typeface = Utils.Typefaces.Bundle;
|
||||
while (_informationPaint.MeasureText(DisplayName) > Width - Height - 10)
|
||||
{
|
||||
_informationPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_informationPaint.Typeface);
|
||||
shaper.Shape(DisplayName, _informationPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint);
|
||||
}
|
||||
|
||||
var outY = y + 75f;
|
||||
if (!string.IsNullOrWhiteSpace(Description))
|
||||
{
|
||||
_informationPaint.TextSize = 16;
|
||||
_informationPaint.Color = SKColors.White.WithAlpha(175);
|
||||
_informationPaint.Typeface = Utils.Typefaces.Description;
|
||||
Utils.DrawMultilineText(c, Description, Width - Height, y, SKTextAlign.Left,
|
||||
new SKRect(Height, outY, Width - 10, y + Height), _informationPaint, out outY);
|
||||
}
|
||||
|
||||
_informationPaint.Color = Border[0].WithAlpha(100);
|
||||
c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint);
|
||||
|
||||
if (_count > 0)
|
||||
{
|
||||
_informationPaint.TextSize = 25;
|
||||
_informationPaint.Color = SKColors.White;
|
||||
_informationPaint.Typeface = Utils.Typefaces.BundleNumber;
|
||||
c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint);
|
||||
|
||||
_informationPaint.Color = Border[0];
|
||||
c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _informationPaint);
|
||||
}
|
||||
|
||||
_reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25));
|
||||
}
|
||||
}
|
||||
|
|
@ -8,158 +8,152 @@ using FModel.Framework;
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class Page
|
||||
{
|
||||
public class Page
|
||||
public int LevelsNeededForUnlock;
|
||||
public int RewardsNeededForUnlock;
|
||||
public Reward[] RewardEntryList;
|
||||
}
|
||||
|
||||
public class BaseSeason : UCreator
|
||||
{
|
||||
private Reward _firstWinReward;
|
||||
private Page[] _bookXpSchedule;
|
||||
private const int _headerHeight = 150;
|
||||
// keep the list because rewards are ordered by least to most important
|
||||
// we only care about the most but we also have filters so we can't just take the last reward
|
||||
|
||||
public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
public int LevelsNeededForUnlock;
|
||||
public int RewardsNeededForUnlock;
|
||||
public Reward[] RewardEntryList;
|
||||
Width = 1024;
|
||||
Height = _headerHeight + 50;
|
||||
Margin = 0;
|
||||
}
|
||||
|
||||
public class BaseSeason : UCreator
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
private Reward _firstWinReward;
|
||||
private Page[] _bookXpSchedule;
|
||||
private const int _headerHeight = 150;
|
||||
// keep the list because rewards are ordered by least to most important
|
||||
// we only care about the most but we also have filters so we can't just take the last reward
|
||||
_bookXpSchedule = Array.Empty<Page>();
|
||||
|
||||
public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&
|
||||
seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards"))
|
||||
{
|
||||
Width = 1024;
|
||||
Height = _headerHeight + 50;
|
||||
Margin = 0;
|
||||
foreach (var reward in rewards)
|
||||
{
|
||||
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
|
||||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
|
||||
|
||||
_firstWinReward = new Reward(uObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData"))
|
||||
{
|
||||
_bookXpSchedule = Array.Empty<Page>();
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&
|
||||
seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards"))
|
||||
foreach (var data in additionalSeasonData)
|
||||
{
|
||||
foreach (var reward in rewards)
|
||||
if (!Utils.TryGetPackageIndexExport(data, out UObject packageIndex) ||
|
||||
!packageIndex.TryGetValue(out FStructFallback[] pageList, "PageList")) continue;
|
||||
|
||||
var i = 0;
|
||||
_bookXpSchedule = new Page[pageList.Length];
|
||||
foreach (var page in pageList)
|
||||
{
|
||||
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
|
||||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
|
||||
if (!page.TryGetValue(out int levelsNeededForUnlock, "LevelsNeededForUnlock") ||
|
||||
!page.TryGetValue(out int rewardsNeededForUnlock, "RewardsNeededForUnlock") ||
|
||||
!page.TryGetValue(out FPackageIndex[] rewardEntryList, "RewardEntryList"))
|
||||
continue;
|
||||
|
||||
_firstWinReward = new Reward(uObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData"))
|
||||
{
|
||||
foreach (var data in additionalSeasonData)
|
||||
{
|
||||
if (!Utils.TryGetPackageIndexExport(data, out UObject packageIndex) ||
|
||||
!packageIndex.TryGetValue(out FStructFallback[] pageList, "PageList")) continue;
|
||||
|
||||
var i = 0;
|
||||
_bookXpSchedule = new Page[pageList.Length];
|
||||
foreach (var page in pageList)
|
||||
var p = new Page
|
||||
{
|
||||
if (!page.TryGetValue(out int levelsNeededForUnlock, "LevelsNeededForUnlock") ||
|
||||
!page.TryGetValue(out int rewardsNeededForUnlock, "RewardsNeededForUnlock") ||
|
||||
!page.TryGetValue(out FPackageIndex[] rewardEntryList, "RewardEntryList"))
|
||||
continue;
|
||||
LevelsNeededForUnlock = levelsNeededForUnlock,
|
||||
RewardsNeededForUnlock = rewardsNeededForUnlock,
|
||||
RewardEntryList = new Reward[rewardEntryList.Length]
|
||||
};
|
||||
|
||||
var p = new Page
|
||||
{
|
||||
LevelsNeededForUnlock = levelsNeededForUnlock,
|
||||
RewardsNeededForUnlock = rewardsNeededForUnlock,
|
||||
RewardEntryList = new Reward[rewardEntryList.Length]
|
||||
};
|
||||
for (var j = 0; j < p.RewardEntryList.Length; j++)
|
||||
{
|
||||
if (!Utils.TryGetPackageIndexExport(rewardEntryList[j], out packageIndex) ||
|
||||
!packageIndex.TryGetValue(out FStructFallback battlePassOffer, "BattlePassOffer") ||
|
||||
!battlePassOffer.TryGetValue(out FStructFallback rewardItem, "RewardItem") ||
|
||||
!rewardItem.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
|
||||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
|
||||
|
||||
for (var j = 0; j < p.RewardEntryList.Length; j++)
|
||||
{
|
||||
if (!Utils.TryGetPackageIndexExport(rewardEntryList[j], out packageIndex) ||
|
||||
!packageIndex.TryGetValue(out FStructFallback battlePassOffer, "BattlePassOffer") ||
|
||||
!battlePassOffer.TryGetValue(out FStructFallback rewardItem, "RewardItem") ||
|
||||
!rewardItem.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
|
||||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
|
||||
|
||||
p.RewardEntryList[j] = new Reward(uObject);
|
||||
}
|
||||
|
||||
_bookXpSchedule[i++] = p;
|
||||
p.RewardEntryList[j] = new Reward(uObject);
|
||||
}
|
||||
|
||||
break;
|
||||
_bookXpSchedule[i++] = p;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Height += 100 * _bookXpSchedule.Sum(x => x.RewardEntryList.Length) / _bookXpSchedule.Length;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
Height += 100 * _bookXpSchedule.Sum(x => x.RewardEntryList.Length) / _bookXpSchedule.Length;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
_firstWinReward?.DrawSeasonWin(c, _headerHeight);
|
||||
DrawBookSchedule(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private const int _DEFAULT_AREA_SIZE = 80;
|
||||
private readonly SKPaint _headerPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
|
||||
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
|
||||
};
|
||||
|
||||
public void DrawHeader(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] { SKColors.SkyBlue.WithAlpha(50), SKColors.Blue.WithAlpha(50) }, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
|
||||
new[] { SKColors.Black.WithAlpha(25), SKColors.Blue.WithAlpha(0) }, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = null;
|
||||
_headerPaint.Color = SKColors.White;
|
||||
while (_headerPaint.MeasureText(DisplayName) > Width)
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
_firstWinReward?.DrawSeasonWin(c, _headerHeight);
|
||||
DrawBookSchedule(c);
|
||||
|
||||
return new []{ret};
|
||||
_headerPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
private const int _DEFAULT_AREA_SIZE = 80;
|
||||
private readonly SKPaint _headerPaint = new()
|
||||
var shaper = new CustomSKShaper(_headerPaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, _headerPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint);
|
||||
}
|
||||
|
||||
private void DrawBookSchedule(SKCanvas c)
|
||||
{
|
||||
var x = 20;
|
||||
var y = _headerHeight + 50;
|
||||
foreach (var page in _bookXpSchedule)
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
|
||||
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
|
||||
};
|
||||
private readonly SKPaint _bookPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.BundleNumber,
|
||||
Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 15
|
||||
};
|
||||
|
||||
public void DrawHeader(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] {SKColors.SkyBlue.WithAlpha(50), SKColors.Blue.WithAlpha(50)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
|
||||
new[] {SKColors.Black.WithAlpha(25), SKColors.Blue.WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = null;
|
||||
_headerPaint.Color = SKColors.White;
|
||||
while (_headerPaint.MeasureText(DisplayName) > Width)
|
||||
foreach (var reward in page.RewardEntryList)
|
||||
{
|
||||
_headerPaint.TextSize -= 1;
|
||||
reward.DrawSeason(c, x, y, _DEFAULT_AREA_SIZE);
|
||||
x += _DEFAULT_AREA_SIZE + 20;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_headerPaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, _headerPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint);
|
||||
}
|
||||
|
||||
private void DrawBookSchedule(SKCanvas c)
|
||||
{
|
||||
var x = 20;
|
||||
var y = _headerHeight + 50;
|
||||
foreach (var page in _bookXpSchedule)
|
||||
{
|
||||
foreach (var reward in page.RewardEntryList)
|
||||
{
|
||||
reward.DrawSeason(c, x, y, _DEFAULT_AREA_SIZE);
|
||||
x += _DEFAULT_AREA_SIZE + 20;
|
||||
}
|
||||
y += _DEFAULT_AREA_SIZE + 20;
|
||||
x = 20;
|
||||
}
|
||||
y += _DEFAULT_AREA_SIZE + 20;
|
||||
x = 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,26 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseSeries : BaseIcon
|
||||
{
|
||||
public class BaseSeries : BaseIcon
|
||||
public BaseSeries(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
public BaseSeries(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
GetSeries(Object);
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
GetSeries(Object);
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
}
|
||||
|
|
@ -9,221 +9,220 @@ using FModel.Settings;
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseTandem : BaseIcon
|
||||
{
|
||||
public class BaseTandem : BaseIcon
|
||||
private string _generalDescription, _additionalDescription;
|
||||
|
||||
public BaseTandem(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
private string _generalDescription, _additionalDescription;
|
||||
|
||||
public BaseTandem(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/BattleRoyale/FeaturedItems/Outfit/T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy.T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy");
|
||||
Margin = 0;
|
||||
Width = 690;
|
||||
Height = 1080;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
base.ParseForInfo();
|
||||
|
||||
string sidePanel = string.Empty, entryList = string.Empty;
|
||||
|
||||
if (Object.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
|
||||
sidePanel = sidePanelIcon.AssetPathName.Text;
|
||||
if (Object.TryGetValue(out FSoftObjectPath entryListIcon, "EntryListIcon"))
|
||||
entryList = entryListIcon.AssetPathName.Text;
|
||||
|
||||
// Overrides for generic "default" images Epic uses for Quest-only or unfinished NPCs
|
||||
if (sidePanel.Contains("Clown") && entryList.Contains("Clown"))
|
||||
Preview = null;
|
||||
else if (sidePanel.Contains("Bane") && !Object.Name.Contains("Sorana"))
|
||||
Preview = Utils.GetBitmap(entryList);
|
||||
else if (!string.IsNullOrWhiteSpace(sidePanel) && !sidePanel.Contains("Clown"))
|
||||
Preview = Utils.GetBitmap(sidePanel);
|
||||
else if ((string.IsNullOrWhiteSpace(sidePanel) || sidePanel.Contains("Clown")) && !string.IsNullOrWhiteSpace(entryList))
|
||||
Preview = Utils.GetBitmap(entryList);
|
||||
|
||||
if (Object.TryGetValue(out FText genDesc, "GeneralDescription"))
|
||||
_generalDescription = genDesc.Text;
|
||||
if (Object.TryGetValue(out FText addDesc, "AdditionalDescription"))
|
||||
_additionalDescription = addDesc.Text;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawHaze(c);
|
||||
|
||||
// Korean is slightly smaller than other languages, so the font size is increased slightly
|
||||
DrawName(c);
|
||||
DrawGeneralDescription(c);
|
||||
DrawAdditionalDescription(c);
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
|
||||
private readonly SKPaint _panelPaint = new() { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = SKColor.Parse("#0045C7") };
|
||||
|
||||
private new void DrawBackground(SKCanvas c)
|
||||
{
|
||||
c.DrawBitmap(SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/npcleftside.png"))?.Stream).Resize(Width, Height), 0, 0, new SKPaint { IsAntialias = false, FilterQuality = SKFilterQuality.None, ImageFilter = SKImageFilter.CreateBlur(0, 25) });
|
||||
|
||||
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
_panelPaint.Color = SKColor.Parse("#002A8C");
|
||||
rect1.MoveTo(29, 0);
|
||||
rect1.LineTo(62, Height);
|
||||
rect1.LineTo(Width, Height);
|
||||
rect1.LineTo(Width, 0);
|
||||
rect1.LineTo(29, 0);
|
||||
rect1.Close();
|
||||
c.DrawPath(rect1, _panelPaint);
|
||||
|
||||
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(29, 0), new SKPoint(Width, Height),
|
||||
new[] { SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
|
||||
c.DrawPath(rect1, _panelPaint);
|
||||
|
||||
_panelPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(348, 196), 300, new[] { SKColor.Parse("#0049CE"), SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
|
||||
c.DrawPath(rect1, _panelPaint);
|
||||
|
||||
using var rect2 = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
|
||||
rect2.MoveTo(10, 0);
|
||||
rect2.LineTo(30, 0);
|
||||
rect2.LineTo(63, Height);
|
||||
rect2.LineTo(56, Height);
|
||||
rect2.LineTo(10, 0);
|
||||
rect2.Close();
|
||||
c.DrawPath(rect2, _panelPaint);
|
||||
|
||||
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(10, 0), new SKPoint(62, Height),
|
||||
new[] { SKColor.Parse("#0045C7") }, SKShaderTileMode.Clamp);
|
||||
c.DrawPath(rect2, _panelPaint);
|
||||
}
|
||||
|
||||
private new void DrawPreview(SKCanvas c)
|
||||
{
|
||||
var previewToUse = Preview ?? DefaultPreview;
|
||||
|
||||
if (Preview == null)
|
||||
{
|
||||
previewToUse = DefaultPreview;
|
||||
ImagePaint.BlendMode = SKBlendMode.DstOut;
|
||||
ImagePaint.Color = SKColor.Parse("#00175F");
|
||||
}
|
||||
|
||||
var x = -125;
|
||||
|
||||
switch (previewToUse.Width)
|
||||
{
|
||||
case 512 when previewToUse.Height == 1024:
|
||||
previewToUse = previewToUse.ResizeWithRatio(500, 1000);
|
||||
x = 100;
|
||||
break;
|
||||
case 512 when previewToUse.Height == 512:
|
||||
case 128 when previewToUse.Height == 128:
|
||||
previewToUse = previewToUse.Resize(512);
|
||||
x = 125;
|
||||
break;
|
||||
default:
|
||||
previewToUse = previewToUse.Resize(1000, 1000);
|
||||
break;
|
||||
}
|
||||
|
||||
c.DrawBitmap(previewToUse, x, 30, ImagePaint);
|
||||
}
|
||||
|
||||
private void DrawHaze(SKCanvas c)
|
||||
{
|
||||
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
rect1.MoveTo(29, 0);
|
||||
rect1.LineTo(62, Height);
|
||||
rect1.LineTo(Width, Height);
|
||||
rect1.LineTo(Width, 0);
|
||||
rect1.LineTo(29, 0);
|
||||
rect1.Close();
|
||||
|
||||
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(343, 0), new SKPoint(343, Height),
|
||||
new[] { SKColors.Transparent, SKColor.Parse("#001E70FF"), SKColor.Parse("#001E70").WithAlpha(200), SKColor.Parse("#001E70").WithAlpha(245), SKColor.Parse("#001E70") }, new[] { 0, (float) .1, (float) .65, (float) .85, 1 }, SKShaderTileMode.Clamp);
|
||||
c.DrawPath(rect1, _panelPaint);
|
||||
}
|
||||
|
||||
private void DrawName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(DisplayName)) return;
|
||||
|
||||
DisplayNamePaint.TextSize = UserSettings.Default.AssetLanguage switch
|
||||
{
|
||||
ELanguage.Korean => 56,
|
||||
_ => 42
|
||||
};
|
||||
|
||||
DisplayNamePaint.TextScaleX = (float) 1.1;
|
||||
DisplayNamePaint.Color = SKColors.White;
|
||||
DisplayNamePaint.TextSkewX = (float) -.25;
|
||||
DisplayNamePaint.TextAlign = SKTextAlign.Left;
|
||||
|
||||
var typeface = Utils.Typefaces.TandemDisplayName;
|
||||
if (typeface == Utils.Typefaces.Default)
|
||||
{
|
||||
DisplayNamePaint.TextSize = 30;
|
||||
}
|
||||
|
||||
DisplayNamePaint.Typeface = typeface;
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
c.DrawShapedText(shaper, DisplayName.ToUpper(), 97, 900, DisplayNamePaint);
|
||||
}
|
||||
|
||||
private void DrawGeneralDescription(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_generalDescription)) return;
|
||||
|
||||
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
|
||||
{
|
||||
ELanguage.Korean => 20,
|
||||
_ => 17
|
||||
};
|
||||
|
||||
DescriptionPaint.Color = SKColor.Parse("#00FFFB");
|
||||
DescriptionPaint.TextAlign = SKTextAlign.Left;
|
||||
|
||||
var typeface = Utils.Typefaces.TandemGenDescription;
|
||||
if (typeface == Utils.Typefaces.Default)
|
||||
{
|
||||
DescriptionPaint.TextSize = 21;
|
||||
}
|
||||
|
||||
DescriptionPaint.Typeface = typeface;
|
||||
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
|
||||
c.DrawShapedText(shaper, _generalDescription.ToUpper(), 97, 930, DescriptionPaint);
|
||||
}
|
||||
|
||||
private void DrawAdditionalDescription(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_additionalDescription)) return;
|
||||
|
||||
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
|
||||
{
|
||||
ELanguage.Korean => 22,
|
||||
_ => 18
|
||||
};
|
||||
|
||||
DescriptionPaint.Color = SKColor.Parse("#89D8FF");
|
||||
DescriptionPaint.TextAlign = SKTextAlign.Left;
|
||||
|
||||
var typeface = Utils.Typefaces.TandemAddDescription;
|
||||
if (typeface == Utils.Typefaces.Default)
|
||||
{
|
||||
DescriptionPaint.TextSize = 20;
|
||||
}
|
||||
|
||||
DescriptionPaint.Typeface = typeface;
|
||||
Utils.DrawMultilineText(c, _additionalDescription, Width, 0, SKTextAlign.Left,
|
||||
new SKRect(97, 960, Width - 10, Height), DescriptionPaint, out _);
|
||||
}
|
||||
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/BattleRoyale/FeaturedItems/Outfit/T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy.T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy");
|
||||
Margin = 0;
|
||||
Width = 690;
|
||||
Height = 1080;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
base.ParseForInfo();
|
||||
|
||||
string sidePanel = string.Empty, entryList = string.Empty;
|
||||
|
||||
if (Object.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
|
||||
sidePanel = sidePanelIcon.AssetPathName.Text;
|
||||
if (Object.TryGetValue(out FSoftObjectPath entryListIcon, "EntryListIcon"))
|
||||
entryList = entryListIcon.AssetPathName.Text;
|
||||
|
||||
// Overrides for generic "default" images Epic uses for Quest-only or unfinished NPCs
|
||||
if (sidePanel.Contains("Clown") && entryList.Contains("Clown"))
|
||||
Preview = null;
|
||||
else if (sidePanel.Contains("Bane") && !Object.Name.Contains("Sorana"))
|
||||
Preview = Utils.GetBitmap(entryList);
|
||||
else if (!string.IsNullOrWhiteSpace(sidePanel) && !sidePanel.Contains("Clown"))
|
||||
Preview = Utils.GetBitmap(sidePanel);
|
||||
else if ((string.IsNullOrWhiteSpace(sidePanel) || sidePanel.Contains("Clown")) && !string.IsNullOrWhiteSpace(entryList))
|
||||
Preview = Utils.GetBitmap(entryList);
|
||||
|
||||
if (Object.TryGetValue(out FText genDesc, "GeneralDescription"))
|
||||
_generalDescription = genDesc.Text;
|
||||
if (Object.TryGetValue(out FText addDesc, "AdditionalDescription"))
|
||||
_additionalDescription = addDesc.Text;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawHaze(c);
|
||||
|
||||
// Korean is slightly smaller than other languages, so the font size is increased slightly
|
||||
DrawName(c);
|
||||
DrawGeneralDescription(c);
|
||||
DrawAdditionalDescription(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private readonly SKPaint _panelPaint = new() { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = SKColor.Parse("#0045C7") };
|
||||
|
||||
private new void DrawBackground(SKCanvas c)
|
||||
{
|
||||
c.DrawBitmap(SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/npcleftside.png"))?.Stream).Resize(Width, Height), 0, 0, new SKPaint { IsAntialias = false, FilterQuality = SKFilterQuality.None, ImageFilter = SKImageFilter.CreateBlur(0, 25) });
|
||||
|
||||
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
_panelPaint.Color = SKColor.Parse("#002A8C");
|
||||
rect1.MoveTo(29, 0);
|
||||
rect1.LineTo(62, Height);
|
||||
rect1.LineTo(Width, Height);
|
||||
rect1.LineTo(Width, 0);
|
||||
rect1.LineTo(29, 0);
|
||||
rect1.Close();
|
||||
c.DrawPath(rect1, _panelPaint);
|
||||
|
||||
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(29, 0), new SKPoint(Width, Height),
|
||||
new[] { SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
|
||||
c.DrawPath(rect1, _panelPaint);
|
||||
|
||||
_panelPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(348, 196), 300, new[] { SKColor.Parse("#0049CE"), SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
|
||||
c.DrawPath(rect1, _panelPaint);
|
||||
|
||||
using var rect2 = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
|
||||
rect2.MoveTo(10, 0);
|
||||
rect2.LineTo(30, 0);
|
||||
rect2.LineTo(63, Height);
|
||||
rect2.LineTo(56, Height);
|
||||
rect2.LineTo(10, 0);
|
||||
rect2.Close();
|
||||
c.DrawPath(rect2, _panelPaint);
|
||||
|
||||
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(10, 0), new SKPoint(62, Height),
|
||||
new[] { SKColor.Parse("#0045C7") }, SKShaderTileMode.Clamp);
|
||||
c.DrawPath(rect2, _panelPaint);
|
||||
}
|
||||
|
||||
private new void DrawPreview(SKCanvas c)
|
||||
{
|
||||
var previewToUse = Preview ?? DefaultPreview;
|
||||
|
||||
if (Preview == null)
|
||||
{
|
||||
previewToUse = DefaultPreview;
|
||||
ImagePaint.BlendMode = SKBlendMode.DstOut;
|
||||
ImagePaint.Color = SKColor.Parse("#00175F");
|
||||
}
|
||||
|
||||
var x = -125;
|
||||
|
||||
switch (previewToUse.Width)
|
||||
{
|
||||
case 512 when previewToUse.Height == 1024:
|
||||
previewToUse = previewToUse.ResizeWithRatio(500, 1000);
|
||||
x = 100;
|
||||
break;
|
||||
case 512 when previewToUse.Height == 512:
|
||||
case 128 when previewToUse.Height == 128:
|
||||
previewToUse = previewToUse.Resize(512);
|
||||
x = 125;
|
||||
break;
|
||||
default:
|
||||
previewToUse = previewToUse.Resize(1000, 1000);
|
||||
break;
|
||||
}
|
||||
|
||||
c.DrawBitmap(previewToUse, x, 30, ImagePaint);
|
||||
}
|
||||
|
||||
private void DrawHaze(SKCanvas c)
|
||||
{
|
||||
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
rect1.MoveTo(29, 0);
|
||||
rect1.LineTo(62, Height);
|
||||
rect1.LineTo(Width, Height);
|
||||
rect1.LineTo(Width, 0);
|
||||
rect1.LineTo(29, 0);
|
||||
rect1.Close();
|
||||
|
||||
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(343, 0), new SKPoint(343, Height),
|
||||
new[] { SKColors.Transparent, SKColor.Parse("#001E70FF"), SKColor.Parse("#001E70").WithAlpha(200), SKColor.Parse("#001E70").WithAlpha(245), SKColor.Parse("#001E70") }, new[] { 0, (float) .1, (float) .65, (float) .85, 1 }, SKShaderTileMode.Clamp);
|
||||
c.DrawPath(rect1, _panelPaint);
|
||||
}
|
||||
|
||||
private void DrawName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(DisplayName)) return;
|
||||
|
||||
DisplayNamePaint.TextSize = UserSettings.Default.AssetLanguage switch
|
||||
{
|
||||
ELanguage.Korean => 56,
|
||||
_ => 42
|
||||
};
|
||||
|
||||
DisplayNamePaint.TextScaleX = (float) 1.1;
|
||||
DisplayNamePaint.Color = SKColors.White;
|
||||
DisplayNamePaint.TextSkewX = (float) -.25;
|
||||
DisplayNamePaint.TextAlign = SKTextAlign.Left;
|
||||
|
||||
var typeface = Utils.Typefaces.TandemDisplayName;
|
||||
if (typeface == Utils.Typefaces.Default)
|
||||
{
|
||||
DisplayNamePaint.TextSize = 30;
|
||||
}
|
||||
|
||||
DisplayNamePaint.Typeface = typeface;
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
c.DrawShapedText(shaper, DisplayName.ToUpper(), 97, 900, DisplayNamePaint);
|
||||
}
|
||||
|
||||
private void DrawGeneralDescription(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_generalDescription)) return;
|
||||
|
||||
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
|
||||
{
|
||||
ELanguage.Korean => 20,
|
||||
_ => 17
|
||||
};
|
||||
|
||||
DescriptionPaint.Color = SKColor.Parse("#00FFFB");
|
||||
DescriptionPaint.TextAlign = SKTextAlign.Left;
|
||||
|
||||
var typeface = Utils.Typefaces.TandemGenDescription;
|
||||
if (typeface == Utils.Typefaces.Default)
|
||||
{
|
||||
DescriptionPaint.TextSize = 21;
|
||||
}
|
||||
|
||||
DescriptionPaint.Typeface = typeface;
|
||||
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
|
||||
c.DrawShapedText(shaper, _generalDescription.ToUpper(), 97, 930, DescriptionPaint);
|
||||
}
|
||||
|
||||
private void DrawAdditionalDescription(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_additionalDescription)) return;
|
||||
|
||||
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
|
||||
{
|
||||
ELanguage.Korean => 22,
|
||||
_ => 18
|
||||
};
|
||||
|
||||
DescriptionPaint.Color = SKColor.Parse("#89D8FF");
|
||||
DescriptionPaint.TextAlign = SKTextAlign.Left;
|
||||
|
||||
var typeface = Utils.Typefaces.TandemAddDescription;
|
||||
if (typeface == Utils.Typefaces.Default)
|
||||
{
|
||||
DescriptionPaint.TextSize = 20;
|
||||
}
|
||||
|
||||
DescriptionPaint.Typeface = typeface;
|
||||
Utils.DrawMultilineText(c, _additionalDescription, Width, 0, SKTextAlign.Left,
|
||||
new SKRect(97, 960, Width - 10, Height), DescriptionPaint, out _);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,185 +9,184 @@ using FModel.Framework;
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseUserControl : UCreator
|
||||
{
|
||||
public class BaseUserControl : UCreator
|
||||
private List<Options> _optionValues = new();
|
||||
|
||||
private readonly SKPaint _displayNamePaint = new()
|
||||
{
|
||||
private List<Options> _optionValues = new();
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = 45,
|
||||
Color = SKColors.White, TextAlign = SKTextAlign.Left
|
||||
};
|
||||
private readonly SKPaint _descriptionPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = 25,
|
||||
Color = SKColor.Parse("88DBFF"), TextAlign = SKTextAlign.Left
|
||||
};
|
||||
|
||||
private readonly SKPaint _displayNamePaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = 45,
|
||||
Color = SKColors.White, TextAlign = SKTextAlign.Left
|
||||
};
|
||||
private readonly SKPaint _descriptionPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = 25,
|
||||
Color = SKColor.Parse("88DBFF"), TextAlign = SKTextAlign.Left
|
||||
};
|
||||
|
||||
public BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Width = 512;
|
||||
Height = 128;
|
||||
Margin = 32;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName"))
|
||||
DisplayName = optionDisplayName.Text.ToUpperInvariant();
|
||||
if (Object.TryGetValue(out FText optionDescription, "OptionDescription"))
|
||||
{
|
||||
Description = optionDescription.Text;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Description)) return;
|
||||
Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count;
|
||||
Height += (int) _descriptionPaint.TextSize;
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues"))
|
||||
{
|
||||
_optionValues = new List<Options>();
|
||||
foreach (var option in optionValues)
|
||||
{
|
||||
if (option.TryGetValue(out FText displayName, "DisplayName"))
|
||||
{
|
||||
var opt = new Options {Option = displayName.Text.ToUpperInvariant()};
|
||||
if (option.TryGetValue(out FLinearColor color, "Value"))
|
||||
opt.Color = SKColor.Parse(color.Hex).WithAlpha(150);
|
||||
|
||||
_optionValues.Add(opt);
|
||||
}
|
||||
else if (option.TryGetValue(out FName primaryAssetName, "PrimaryAssetName"))
|
||||
{
|
||||
_optionValues.Add(new Options {Option = primaryAssetName.Text});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText optionOnText, "OptionOnText"))
|
||||
_optionValues.Add(new Options {Option = optionOnText.Text});
|
||||
if (Object.TryGetValue(out FText optionOffText, "OptionOffText"))
|
||||
_optionValues.Add(new Options {Option = optionOffText.Text});
|
||||
|
||||
if (Object.TryGetValue(out int iMin, "Min", "DefaultValue") &&
|
||||
Object.TryGetValue(out int iMax, "Max"))
|
||||
{
|
||||
var increment = iMin;
|
||||
if (Object.TryGetValue(out int incrementValue, "IncrementValue"))
|
||||
increment = incrementValue;
|
||||
|
||||
var format = "{0}";
|
||||
if (Object.TryGetValue(out FText unitName, "UnitName"))
|
||||
format = unitName.Text;
|
||||
|
||||
for (var i = iMin; i <= iMax; i += increment)
|
||||
{
|
||||
_optionValues.Add(new Options {Option = string.Format(format, i)});
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out float fMin, "Min", "DefaultValue") &&
|
||||
Object.TryGetValue(out float fMax, "Max"))
|
||||
{
|
||||
var increment = fMin;
|
||||
if (Object.TryGetValue(out float incrementValue, "IncrementValue"))
|
||||
increment = incrementValue;
|
||||
|
||||
var format = "{0}";
|
||||
if (Object.TryGetValue(out FText unitName, "UnitName"))
|
||||
format = unitName.Text;
|
||||
|
||||
for (var i = fMin; i <= fMax; i += increment)
|
||||
{
|
||||
_optionValues.Add(new Options {Option = string.Format(format, i)});
|
||||
}
|
||||
}
|
||||
|
||||
Height += Margin;
|
||||
Height += 35 * _optionValues.Count;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawInformation(c);
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
|
||||
private new void DrawBackground(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(Width / 2, Height),
|
||||
new SKPoint(Width, Height / 4),
|
||||
new[] {SKColor.Parse("01369C"), SKColor.Parse("1273C8")},
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawInformation(SKCanvas c)
|
||||
{
|
||||
// display name
|
||||
while (_displayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
|
||||
{
|
||||
_displayNamePaint.TextSize -= 2;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_displayNamePaint.Typeface);
|
||||
shaper.Shape(DisplayName, _displayNamePaint);
|
||||
c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint);
|
||||
#if DEBUG
|
||||
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Margin + _displayNamePaint.TextSize), new SKPaint {Color = SKColors.Blue, IsStroke = true});
|
||||
#endif
|
||||
|
||||
// description
|
||||
float y = Margin;
|
||||
if (!string.IsNullOrEmpty(DisplayName)) y += _displayNamePaint.TextSize;
|
||||
if (!string.IsNullOrEmpty(Description)) y += _descriptionPaint.TextSize + Margin / 2F;
|
||||
|
||||
Utils.DrawMultilineText(c, Description, Width, Margin, SKTextAlign.Left,
|
||||
new SKRect(Margin, y, Width - Margin, 256), _descriptionPaint, out var top);
|
||||
|
||||
// options
|
||||
foreach (var option in _optionValues)
|
||||
{
|
||||
option.Draw(c, Margin, Width, ref top);
|
||||
}
|
||||
}
|
||||
public BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Width = 512;
|
||||
Height = 128;
|
||||
Margin = 32;
|
||||
}
|
||||
|
||||
public class Options
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
private const int _SPACE = 5;
|
||||
private const int _HEIGHT = 30;
|
||||
|
||||
private readonly SKPaint _optionPaint = new()
|
||||
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName"))
|
||||
DisplayName = optionDisplayName.Text.ToUpperInvariant();
|
||||
if (Object.TryGetValue(out FText optionDescription, "OptionDescription"))
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = 20,
|
||||
Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left
|
||||
};
|
||||
Description = optionDescription.Text;
|
||||
|
||||
public string Option;
|
||||
public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150);
|
||||
if (string.IsNullOrWhiteSpace(Description)) return;
|
||||
Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count;
|
||||
Height += (int) _descriptionPaint.TextSize;
|
||||
}
|
||||
|
||||
public void Draw(SKCanvas c, int margin, int width, ref float top)
|
||||
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues"))
|
||||
{
|
||||
c.DrawRect(new SKRect(margin, top, width - margin, top + _HEIGHT), new SKPaint {IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Color});
|
||||
c.DrawText(Option, margin + _SPACE * 2, top + 20 * 1.1f, _optionPaint);
|
||||
top += _HEIGHT + _SPACE;
|
||||
_optionValues = new List<Options>();
|
||||
foreach (var option in optionValues)
|
||||
{
|
||||
if (option.TryGetValue(out FText displayName, "DisplayName"))
|
||||
{
|
||||
var opt = new Options { Option = displayName.Text.ToUpperInvariant() };
|
||||
if (option.TryGetValue(out FLinearColor color, "Value"))
|
||||
opt.Color = SKColor.Parse(color.Hex).WithAlpha(150);
|
||||
|
||||
_optionValues.Add(opt);
|
||||
}
|
||||
else if (option.TryGetValue(out FName primaryAssetName, "PrimaryAssetName"))
|
||||
{
|
||||
_optionValues.Add(new Options { Option = primaryAssetName.Text });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText optionOnText, "OptionOnText"))
|
||||
_optionValues.Add(new Options { Option = optionOnText.Text });
|
||||
if (Object.TryGetValue(out FText optionOffText, "OptionOffText"))
|
||||
_optionValues.Add(new Options { Option = optionOffText.Text });
|
||||
|
||||
if (Object.TryGetValue(out int iMin, "Min", "DefaultValue") &&
|
||||
Object.TryGetValue(out int iMax, "Max"))
|
||||
{
|
||||
var increment = iMin;
|
||||
if (Object.TryGetValue(out int incrementValue, "IncrementValue"))
|
||||
increment = incrementValue;
|
||||
|
||||
var format = "{0}";
|
||||
if (Object.TryGetValue(out FText unitName, "UnitName"))
|
||||
format = unitName.Text;
|
||||
|
||||
for (var i = iMin; i <= iMax; i += increment)
|
||||
{
|
||||
_optionValues.Add(new Options { Option = string.Format(format, i) });
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out float fMin, "Min", "DefaultValue") &&
|
||||
Object.TryGetValue(out float fMax, "Max"))
|
||||
{
|
||||
var increment = fMin;
|
||||
if (Object.TryGetValue(out float incrementValue, "IncrementValue"))
|
||||
increment = incrementValue;
|
||||
|
||||
var format = "{0}";
|
||||
if (Object.TryGetValue(out FText unitName, "UnitName"))
|
||||
format = unitName.Text;
|
||||
|
||||
for (var i = fMin; i <= fMax; i += increment)
|
||||
{
|
||||
_optionValues.Add(new Options { Option = string.Format(format, i) });
|
||||
}
|
||||
}
|
||||
|
||||
Height += Margin;
|
||||
Height += 35 * _optionValues.Count;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawInformation(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private new void DrawBackground(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(Width / 2, Height),
|
||||
new SKPoint(Width, Height / 4),
|
||||
new[] { SKColor.Parse("01369C"), SKColor.Parse("1273C8") },
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawInformation(SKCanvas c)
|
||||
{
|
||||
// display name
|
||||
while (_displayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
|
||||
{
|
||||
_displayNamePaint.TextSize -= 2;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_displayNamePaint.Typeface);
|
||||
shaper.Shape(DisplayName, _displayNamePaint);
|
||||
c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint);
|
||||
#if DEBUG
|
||||
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Margin + _displayNamePaint.TextSize), new SKPaint { Color = SKColors.Blue, IsStroke = true });
|
||||
#endif
|
||||
|
||||
// description
|
||||
float y = Margin;
|
||||
if (!string.IsNullOrEmpty(DisplayName)) y += _displayNamePaint.TextSize;
|
||||
if (!string.IsNullOrEmpty(Description)) y += _descriptionPaint.TextSize + Margin / 2F;
|
||||
|
||||
Utils.DrawMultilineText(c, Description, Width, Margin, SKTextAlign.Left,
|
||||
new SKRect(Margin, y, Width - Margin, 256), _descriptionPaint, out var top);
|
||||
|
||||
// options
|
||||
foreach (var option in _optionValues)
|
||||
{
|
||||
option.Draw(c, Margin, Width, ref top);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Options
|
||||
{
|
||||
private const int _SPACE = 5;
|
||||
private const int _HEIGHT = 30;
|
||||
|
||||
private readonly SKPaint _optionPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = 20,
|
||||
Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left
|
||||
};
|
||||
|
||||
public string Option;
|
||||
public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150);
|
||||
|
||||
public void Draw(SKCanvas c, int margin, int width, ref float top)
|
||||
{
|
||||
c.DrawRect(new SKRect(margin, top, width - margin, top + _HEIGHT), new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Color });
|
||||
c.DrawText(Option, margin + _SPACE * 2, top + 20 * 1.1f, _optionPaint);
|
||||
top += _HEIGHT + _SPACE;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,45 +4,44 @@ using CUE4Parse.UE4.Objects.Core.Math;
|
|||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.SB
|
||||
namespace FModel.Creator.Bases.SB;
|
||||
|
||||
public class BaseDivision : UCreator
|
||||
{
|
||||
public class BaseDivision : UCreator
|
||||
public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FPackageIndex icon, "Icon", "IconNoTier"))
|
||||
{
|
||||
Preview = Utils.GetBitmap(icon);
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FLinearColor lightColor, "UILightColor") &&
|
||||
Object.TryGetValue(out FLinearColor mediumColor, "UIMediumColor") &&
|
||||
Object.TryGetValue(out FLinearColor darkColor, "UIDarkColor") &&
|
||||
Object.TryGetValue(out FLinearColor cardColor, "UICardColor"))
|
||||
{
|
||||
Background = new[] {SKColor.Parse(lightColor.Hex), SKColor.Parse(cardColor.Hex)};
|
||||
Border = new[] {SKColor.Parse(mediumColor.Hex), SKColor.Parse(darkColor.Hex)};
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FPackageIndex icon, "Icon", "IconNoTier"))
|
||||
{
|
||||
Preview = Utils.GetBitmap(icon);
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FLinearColor lightColor, "UILightColor") &&
|
||||
Object.TryGetValue(out FLinearColor mediumColor, "UIMediumColor") &&
|
||||
Object.TryGetValue(out FLinearColor darkColor, "UIDarkColor") &&
|
||||
Object.TryGetValue(out FLinearColor cardColor, "UICardColor"))
|
||||
{
|
||||
Background = new[] { SKColor.Parse(lightColor.Hex), SKColor.Parse(cardColor.Hex) };
|
||||
Border = new[] { SKColor.Parse(mediumColor.Hex), SKColor.Parse(darkColor.Hex) };
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
|
|
@ -4,47 +4,46 @@ using CUE4Parse.UE4.Assets.Exports.Texture;
|
|||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.SB
|
||||
namespace FModel.Creator.Bases.SB;
|
||||
|
||||
public class BaseGameModeInfo : UCreator
|
||||
{
|
||||
public class BaseGameModeInfo : UCreator
|
||||
private SKBitmap _icon;
|
||||
|
||||
public BaseGameModeInfo(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
private SKBitmap _icon;
|
||||
|
||||
public BaseGameModeInfo(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Width = 738;
|
||||
Height = 1024;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description"))
|
||||
Description = description.Text;
|
||||
if (Object.TryGetValue(out UMaterialInstanceConstant portrait, "Portrait"))
|
||||
Preview = Utils.GetBitmap(portrait);
|
||||
if (Object.TryGetValue(out UTexture2D icon, "Icon"))
|
||||
_icon = Utils.GetBitmap(icon).Resize(25);
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawIcon(c);
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
|
||||
private void DrawIcon(SKCanvas c)
|
||||
{
|
||||
if (_icon == null) return;
|
||||
c.DrawBitmap(_icon, new SKPoint(5, 5), ImagePaint);
|
||||
}
|
||||
Width = 738;
|
||||
Height = 1024;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description"))
|
||||
Description = description.Text;
|
||||
if (Object.TryGetValue(out UMaterialInstanceConstant portrait, "Portrait"))
|
||||
Preview = Utils.GetBitmap(portrait);
|
||||
if (Object.TryGetValue(out UTexture2D icon, "Icon"))
|
||||
_icon = Utils.GetBitmap(icon).Resize(25);
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawIcon(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private void DrawIcon(SKCanvas c)
|
||||
{
|
||||
if (_icon == null) return;
|
||||
c.DrawBitmap(_icon, new SKPoint(5, 5), ImagePaint);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,53 +3,52 @@ using CUE4Parse.UE4.Objects.Core.i18N;
|
|||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.SB
|
||||
namespace FModel.Creator.Bases.SB;
|
||||
|
||||
public class BaseLeague : UCreator
|
||||
{
|
||||
public class BaseLeague : UCreator
|
||||
private int _promotionXp, _xpLostPerMatch;
|
||||
|
||||
public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
private int _promotionXp, _xpLostPerMatch;
|
||||
|
||||
public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
_promotionXp = 0;
|
||||
_xpLostPerMatch = 0;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out int promotionXp, "PromotionXP"))
|
||||
_promotionXp = promotionXp;
|
||||
if (Object.TryGetValue(out int xpLostPerMatch, "XPLostPerMatch"))
|
||||
_xpLostPerMatch = xpLostPerMatch;
|
||||
|
||||
if (Object.TryGetValue(out FPackageIndex division, "Division") &&
|
||||
Utils.TryGetPackageIndexExport(division, out UObject div))
|
||||
{
|
||||
var d = new BaseDivision(div, Style);
|
||||
d.ParseForInfo();
|
||||
Preview = d.Preview;
|
||||
Background = d.Background;
|
||||
Border = d.Border;
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
|
||||
DrawToBottom(c, SKTextAlign.Left, $"PromotionXP: {_promotionXp}");
|
||||
DrawToBottom(c, SKTextAlign.Right, $"XPLostPerMatch: {_xpLostPerMatch}");
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
_promotionXp = 0;
|
||||
_xpLostPerMatch = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out int promotionXp, "PromotionXP"))
|
||||
_promotionXp = promotionXp;
|
||||
if (Object.TryGetValue(out int xpLostPerMatch, "XPLostPerMatch"))
|
||||
_xpLostPerMatch = xpLostPerMatch;
|
||||
|
||||
if (Object.TryGetValue(out FPackageIndex division, "Division") &&
|
||||
Utils.TryGetPackageIndexExport(division, out UObject div))
|
||||
{
|
||||
var d = new BaseDivision(div, Style);
|
||||
d.ParseForInfo();
|
||||
Preview = d.Preview;
|
||||
Background = d.Background;
|
||||
Border = d.Border;
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
|
||||
DrawToBottom(c, SKTextAlign.Left, $"PromotionXP: {_promotionXp}");
|
||||
DrawToBottom(c, SKTextAlign.Right, $"XPLostPerMatch: {_xpLostPerMatch}");
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
|
|
@ -7,92 +7,91 @@ using CUE4Parse.UE4.Objects.UObject;
|
|||
using FModel.Creator.Bases.FN;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.SB
|
||||
namespace FModel.Creator.Bases.SB;
|
||||
|
||||
public class BaseSpellIcon : BaseIcon
|
||||
{
|
||||
public class BaseSpellIcon : BaseIcon
|
||||
private SKBitmap _seriesBackground2;
|
||||
|
||||
private readonly SKPaint _overlayPaint = new()
|
||||
{
|
||||
private SKBitmap _seriesBackground2;
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
IsAntialias = true,
|
||||
Color = SKColors.Transparent.WithAlpha(75)
|
||||
};
|
||||
|
||||
private readonly SKPaint _overlayPaint = new()
|
||||
public BaseSpellIcon(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Background = new[] { SKColor.Parse("FFFFFF"), SKColor.Parse("636363") };
|
||||
Border = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") };
|
||||
Width = Object.ExportType.StartsWith("GCosmeticCard") ? 1536 : 512;
|
||||
Height = Object.ExportType.StartsWith("GCosmeticCard") ? 450 : 512;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FName rarity, "Rarity"))
|
||||
GetRarity(rarity);
|
||||
|
||||
if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture", "OfferTexture", "PortraitTexture"))
|
||||
Preview = Utils.GetBitmap(preview);
|
||||
else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture", "OfferTexture", "PortraitTexture"))
|
||||
Preview = Utils.GetBitmap(icon);
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "Title", "Name"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description"))
|
||||
Description = description.Text;
|
||||
|
||||
SeriesBackground = Utils.GetBitmap("g3/Content/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox");
|
||||
_seriesBackground2 = Utils.GetBitmap("g3/Content/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT");
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackgrounds(c);
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private void DrawBackgrounds(SKCanvas c)
|
||||
{
|
||||
if (SeriesBackground != null)
|
||||
c.DrawBitmap(SeriesBackground, new SKRect(0, 0, Width, Height), ImagePaint);
|
||||
if (_seriesBackground2 != null)
|
||||
c.DrawBitmap(_seriesBackground2, new SKRect(0, 0, Width, Height), _overlayPaint);
|
||||
|
||||
var x = Margin * (int) 2.5;
|
||||
const int radi = 15;
|
||||
c.DrawCircle(x + radi, x + radi, radi, new SKPaint
|
||||
{
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
IsAntialias = true,
|
||||
Color = SKColors.Transparent.WithAlpha(75)
|
||||
};
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateRadialGradient(
|
||||
new SKPoint(radi, radi), radi * 2 / 5 * 4,
|
||||
Background, SKShaderTileMode.Clamp)
|
||||
});
|
||||
}
|
||||
|
||||
public BaseSpellIcon(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
private void GetRarity(FName n)
|
||||
{
|
||||
if (!Utils.TryLoadObject("g3/Content/UI/UIKit/DT_RarityColors.DT_RarityColors", out UDataTable rarity)) return;
|
||||
|
||||
if (rarity.TryGetDataTableRow(n.Text["EXRarity::".Length..], StringComparison.Ordinal, out var row))
|
||||
{
|
||||
Background = new[] {SKColor.Parse("FFFFFF"), SKColor.Parse("636363")};
|
||||
Border = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF")};
|
||||
Width = Object.ExportType.StartsWith("GCosmeticCard") ? 1536 : 512;
|
||||
Height = Object.ExportType.StartsWith("GCosmeticCard") ? 450 : 512;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FName rarity, "Rarity"))
|
||||
GetRarity(rarity);
|
||||
|
||||
if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture", "OfferTexture", "PortraitTexture"))
|
||||
Preview = Utils.GetBitmap(preview);
|
||||
else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture", "OfferTexture", "PortraitTexture"))
|
||||
Preview = Utils.GetBitmap(icon);
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "Title", "Name"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description"))
|
||||
Description = description.Text;
|
||||
|
||||
SeriesBackground = Utils.GetBitmap("g3/Content/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox");
|
||||
_seriesBackground2 = Utils.GetBitmap("g3/Content/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT");
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackgrounds(c);
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
|
||||
return new []{ret};
|
||||
}
|
||||
|
||||
private void DrawBackgrounds(SKCanvas c)
|
||||
{
|
||||
if (SeriesBackground != null)
|
||||
c.DrawBitmap(SeriesBackground, new SKRect(0, 0, Width, Height), ImagePaint);
|
||||
if (_seriesBackground2 != null)
|
||||
c.DrawBitmap(_seriesBackground2, new SKRect(0, 0, Width, Height), _overlayPaint);
|
||||
|
||||
var x = Margin * (int) 2.5;
|
||||
const int radi = 15;
|
||||
c.DrawCircle(x + radi, x + radi, radi, new SKPaint
|
||||
if (row.TryGetValue(out FLinearColor[] colors, "Colors"))
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateRadialGradient(
|
||||
new SKPoint(radi, radi), radi * 2 / 5 * 4,
|
||||
Background, SKShaderTileMode.Clamp)
|
||||
});
|
||||
}
|
||||
|
||||
private void GetRarity(FName n)
|
||||
{
|
||||
if (!Utils.TryLoadObject("g3/Content/UI/UIKit/DT_RarityColors.DT_RarityColors", out UDataTable rarity)) return;
|
||||
|
||||
if (rarity.TryGetDataTableRow(n.Text["EXRarity::".Length..], StringComparison.Ordinal, out var row))
|
||||
{
|
||||
if (row.TryGetValue(out FLinearColor[] colors, "Colors"))
|
||||
{
|
||||
Background = new[] {SKColor.Parse(colors[0].Hex), SKColor.Parse(colors[2].Hex)};
|
||||
Border = new[] {SKColor.Parse(colors[1].Hex), SKColor.Parse(colors[0].Hex)};
|
||||
}
|
||||
Background = new[] { SKColor.Parse(colors[0].Hex), SKColor.Parse(colors[2].Hex) };
|
||||
Border = new[] { SKColor.Parse(colors[1].Hex), SKColor.Parse(colors[0].Hex) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,225 +6,224 @@ using FModel.Framework;
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
namespace FModel.Creator.Bases;
|
||||
|
||||
public abstract class UCreator
|
||||
{
|
||||
public abstract class UCreator
|
||||
protected UObject Object { get; }
|
||||
protected EIconStyle Style { get; }
|
||||
public SKBitmap DefaultPreview { get; set; }
|
||||
public SKBitmap Preview { get; set; }
|
||||
public SKColor[] Background { get; protected set; }
|
||||
public SKColor[] Border { get; protected set; }
|
||||
public string DisplayName { get; protected set; }
|
||||
public string Description { get; protected set; }
|
||||
public int Margin { get; protected set; }
|
||||
public int Width { get; protected set; }
|
||||
public int Height { get; protected set; }
|
||||
|
||||
public abstract void ParseForInfo();
|
||||
public abstract SKBitmap[] Draw();
|
||||
|
||||
protected UCreator(UObject uObject, EIconStyle style)
|
||||
{
|
||||
protected UObject Object { get; }
|
||||
protected EIconStyle Style { get; }
|
||||
public SKBitmap DefaultPreview { get; set; }
|
||||
public SKBitmap Preview { get; set; }
|
||||
public SKColor[] Background { get; protected set; }
|
||||
public SKColor[] Border { get; protected set; }
|
||||
public string DisplayName { get; protected set; }
|
||||
public string Description { get; protected set; }
|
||||
public int Margin { get; protected set; }
|
||||
public int Width { get; protected set; }
|
||||
public int Height { get; protected set; }
|
||||
DefaultPreview = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream);
|
||||
Background = new[] { SKColor.Parse("5BFD00"), SKColor.Parse("003700") };
|
||||
Border = new[] { SKColor.Parse("1E8500"), SKColor.Parse("5BFD00") };
|
||||
DisplayName = string.Empty;
|
||||
Description = string.Empty;
|
||||
Width = 512;
|
||||
Height = 512;
|
||||
Margin = 2;
|
||||
Object = uObject;
|
||||
Style = style;
|
||||
}
|
||||
|
||||
public abstract void ParseForInfo();
|
||||
public abstract SKBitmap[] Draw();
|
||||
private const int _STARTER_TEXT_POSITION = 380, _NAME_TEXT_SIZE = 45, _BOTTOM_TEXT_SIZE = 15;
|
||||
protected readonly SKPaint DisplayNamePaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = _NAME_TEXT_SIZE,
|
||||
Color = SKColors.White, TextAlign = SKTextAlign.Center
|
||||
};
|
||||
protected readonly SKPaint DescriptionPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.Description, TextSize = 13,
|
||||
Color = SKColors.White
|
||||
};
|
||||
protected readonly SKPaint ImagePaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High
|
||||
};
|
||||
private readonly SKPaint _textBackgroundPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = new SKColor(0, 0, 50, 75)
|
||||
};
|
||||
private readonly SKPaint _shortDescriptionPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColors.White
|
||||
};
|
||||
|
||||
protected UCreator(UObject uObject, EIconStyle style)
|
||||
{
|
||||
DefaultPreview = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream);
|
||||
Background = new[] {SKColor.Parse("5BFD00"), SKColor.Parse("003700")};
|
||||
Border = new[] {SKColor.Parse("1E8500"), SKColor.Parse("5BFD00")};
|
||||
DisplayName = string.Empty;
|
||||
Description = string.Empty;
|
||||
Width = 512;
|
||||
Height = 512;
|
||||
Margin = 2;
|
||||
Object = uObject;
|
||||
Style = style;
|
||||
}
|
||||
public void DrawBackground(SKCanvas c)
|
||||
{
|
||||
// reverse doesn't affect basic rarities
|
||||
if (Background[0] == Background[1]) Background[0] = Border[0];
|
||||
Background[0].ToHsl(out _, out _, out var l1);
|
||||
Background[1].ToHsl(out _, out _, out var l2);
|
||||
var reverse = l1 > l2;
|
||||
|
||||
private const int _STARTER_TEXT_POSITION = 380, _NAME_TEXT_SIZE = 45, _BOTTOM_TEXT_SIZE = 15;
|
||||
protected readonly SKPaint DisplayNamePaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = _NAME_TEXT_SIZE,
|
||||
Color = SKColors.White, TextAlign = SKTextAlign.Center
|
||||
};
|
||||
protected readonly SKPaint DescriptionPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.Description, TextSize = 13,
|
||||
Color = SKColors.White
|
||||
};
|
||||
protected readonly SKPaint ImagePaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High
|
||||
};
|
||||
private readonly SKPaint _textBackgroundPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = new SKColor(0, 0, 50, 75)
|
||||
};
|
||||
private readonly SKPaint _shortDescriptionPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColors.White
|
||||
};
|
||||
|
||||
public void DrawBackground(SKCanvas c)
|
||||
{
|
||||
// reverse doesn't affect basic rarities
|
||||
if (Background[0] == Background[1]) Background[0] = Border[0];
|
||||
Background[0].ToHsl(out _, out _, out var l1);
|
||||
Background[1].ToHsl(out _, out _, out var l2);
|
||||
var reverse = l1 > l2;
|
||||
|
||||
// border
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Border, SKShaderTileMode.Clamp)
|
||||
});
|
||||
|
||||
if (this is BaseIcon {SeriesBackground: { }} baseIcon)
|
||||
c.DrawBitmap(baseIcon.SeriesBackground, new SKRect(baseIcon.Margin, baseIcon.Margin, baseIcon.Width - baseIcon.Margin,
|
||||
baseIcon.Height - baseIcon.Margin), ImagePaint);
|
||||
else
|
||||
// border
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height),
|
||||
new SKPaint
|
||||
{
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.Flat:
|
||||
{
|
||||
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4),
|
||||
new[] {Background[reverse ? 0 : 1].WithAlpha(150), Border[0]}, SKShaderTileMode.Clamp)
|
||||
});
|
||||
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Border, SKShaderTileMode.Clamp)
|
||||
});
|
||||
|
||||
var pathTop = new SKPath {FillType = SKPathFillType.EvenOdd};
|
||||
pathTop.MoveTo(Margin, Margin);
|
||||
pathTop.LineTo(Margin + Width / 17 * 10, Margin);
|
||||
pathTop.LineTo(Margin, Margin + Height / 17);
|
||||
pathTop.Close();
|
||||
c.DrawPath(pathTop, new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = Background[1].WithAlpha(75)
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, Height / 2), Width / 5 * 4,
|
||||
new[] {Background[reverse ? 0 : 1], Background[reverse ? 1 : 0]},
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawPreview(SKCanvas c)
|
||||
=> c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, Margin, Width - Margin, Height - Margin), ImagePaint);
|
||||
|
||||
protected void DrawTextBackground(SKCanvas c)
|
||||
if (this is BaseIcon { SeriesBackground: { } } baseIcon)
|
||||
c.DrawBitmap(baseIcon.SeriesBackground, new SKRect(baseIcon.Margin, baseIcon.Margin, baseIcon.Width - baseIcon.Margin,
|
||||
baseIcon.Height - baseIcon.Margin), ImagePaint);
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.Flat:
|
||||
{
|
||||
var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd};
|
||||
pathBottom.MoveTo(Margin, Height - Margin);
|
||||
pathBottom.LineTo(Margin, Height - Margin - Height / 17 * 2.5f);
|
||||
pathBottom.LineTo(Width - Margin, Height - Margin - Height / 17 * 4.5f);
|
||||
pathBottom.LineTo(Width - Margin, Height - Margin);
|
||||
pathBottom.Close();
|
||||
c.DrawPath(pathBottom, _textBackgroundPaint);
|
||||
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4),
|
||||
new[] { Background[reverse ? 0 : 1].WithAlpha(150), Border[0] }, SKShaderTileMode.Clamp)
|
||||
});
|
||||
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
|
||||
|
||||
var pathTop = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
pathTop.MoveTo(Margin, Margin);
|
||||
pathTop.LineTo(Margin + Width / 17 * 10, Margin);
|
||||
pathTop.LineTo(Margin, Margin + Height / 17);
|
||||
pathTop.Close();
|
||||
c.DrawPath(pathTop, new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = Background[1].WithAlpha(75)
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, Height - Margin), _textBackgroundPaint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(DisplayName)) return;
|
||||
|
||||
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
|
||||
{
|
||||
DisplayNamePaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
var x = (Width - shapedText.Points[^1].X) / 2;
|
||||
var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.Flat:
|
||||
{
|
||||
DisplayNamePaint.TextAlign = SKTextAlign.Right;
|
||||
x = Width - Margin * 2 - shapedText.Points[^1].X;
|
||||
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, Height / 2), Width / 5 * 4,
|
||||
new[] { Background[reverse ? 0 : 1], Background[reverse ? 1 : 0] },
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
c.DrawLine(x, 0, x, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true});
|
||||
c.DrawLine(x + shapedText.Points[^1].X, 0, x + shapedText.Points[^1].X, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true});
|
||||
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, y), new SKPaint {Color = SKColors.Blue, IsStroke = true});
|
||||
#endif
|
||||
|
||||
c.DrawShapedText(shaper, DisplayName, x, y, DisplayNamePaint);
|
||||
}
|
||||
|
||||
protected void DrawDescription(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Description)) return;
|
||||
|
||||
var maxLine = string.IsNullOrEmpty(DisplayName) ? 8 : 4;
|
||||
var side = SKTextAlign.Center;
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.Flat:
|
||||
side = SKTextAlign.Right;
|
||||
break;
|
||||
}
|
||||
|
||||
Utils.DrawCenteredMultilineText(c, Description, maxLine, Width, Margin, side,
|
||||
new SKRect(Margin, string.IsNullOrEmpty(DisplayName) ? _STARTER_TEXT_POSITION : _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, Width - Margin, Height - _BOTTOM_TEXT_SIZE), DescriptionPaint);
|
||||
}
|
||||
|
||||
protected void DrawToBottom(SKCanvas c, SKTextAlign side, string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return;
|
||||
|
||||
_shortDescriptionPaint.TextAlign = side;
|
||||
_shortDescriptionPaint.TextSize = Utils.Typefaces.Bottom == null ? 15 : 13;
|
||||
switch (side)
|
||||
{
|
||||
case SKTextAlign.Left:
|
||||
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
|
||||
var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface);
|
||||
shaper.Shape(text, _shortDescriptionPaint);
|
||||
|
||||
c.DrawShapedText(shaper, text, Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
|
||||
break;
|
||||
case SKTextAlign.Right:
|
||||
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.Default;
|
||||
c.DrawText(text, Width - Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawPreview(SKCanvas c)
|
||||
=> c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, Margin, Width - Margin, Height - Margin), ImagePaint);
|
||||
|
||||
protected void DrawTextBackground(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.Flat:
|
||||
{
|
||||
var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
pathBottom.MoveTo(Margin, Height - Margin);
|
||||
pathBottom.LineTo(Margin, Height - Margin - Height / 17 * 2.5f);
|
||||
pathBottom.LineTo(Width - Margin, Height - Margin - Height / 17 * 4.5f);
|
||||
pathBottom.LineTo(Width - Margin, Height - Margin);
|
||||
pathBottom.Close();
|
||||
c.DrawPath(pathBottom, _textBackgroundPaint);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, Height - Margin), _textBackgroundPaint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(DisplayName)) return;
|
||||
|
||||
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
|
||||
{
|
||||
DisplayNamePaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
var x = (Width - shapedText.Points[^1].X) / 2;
|
||||
var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.Flat:
|
||||
{
|
||||
DisplayNamePaint.TextAlign = SKTextAlign.Right;
|
||||
x = Width - Margin * 2 - shapedText.Points[^1].X;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
c.DrawLine(x, 0, x, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true });
|
||||
c.DrawLine(x + shapedText.Points[^1].X, 0, x + shapedText.Points[^1].X, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true });
|
||||
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, y), new SKPaint { Color = SKColors.Blue, IsStroke = true });
|
||||
#endif
|
||||
|
||||
c.DrawShapedText(shaper, DisplayName, x, y, DisplayNamePaint);
|
||||
}
|
||||
|
||||
protected void DrawDescription(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Description)) return;
|
||||
|
||||
var maxLine = string.IsNullOrEmpty(DisplayName) ? 8 : 4;
|
||||
var side = SKTextAlign.Center;
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.Flat:
|
||||
side = SKTextAlign.Right;
|
||||
break;
|
||||
}
|
||||
|
||||
Utils.DrawCenteredMultilineText(c, Description, maxLine, Width, Margin, side,
|
||||
new SKRect(Margin, string.IsNullOrEmpty(DisplayName) ? _STARTER_TEXT_POSITION : _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, Width - Margin, Height - _BOTTOM_TEXT_SIZE), DescriptionPaint);
|
||||
}
|
||||
|
||||
protected void DrawToBottom(SKCanvas c, SKTextAlign side, string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return;
|
||||
|
||||
_shortDescriptionPaint.TextAlign = side;
|
||||
_shortDescriptionPaint.TextSize = Utils.Typefaces.Bottom == null ? 15 : 13;
|
||||
switch (side)
|
||||
{
|
||||
case SKTextAlign.Left:
|
||||
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
|
||||
var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface);
|
||||
shaper.Shape(text, _shortDescriptionPaint);
|
||||
|
||||
c.DrawShapedText(shaper, text, Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
|
||||
break;
|
||||
case SKTextAlign.Right:
|
||||
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.Default;
|
||||
c.DrawText(text, Width - Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,247 +6,246 @@ using FModel.Creator.Bases.BB;
|
|||
using FModel.Creator.Bases.FN;
|
||||
using FModel.Creator.Bases.SB;
|
||||
|
||||
namespace FModel.Creator
|
||||
namespace FModel.Creator;
|
||||
|
||||
public class CreatorPackage : IDisposable
|
||||
{
|
||||
public class CreatorPackage : IDisposable
|
||||
private UObject _object;
|
||||
private EIconStyle _style;
|
||||
|
||||
public CreatorPackage(UObject uObject, EIconStyle style)
|
||||
{
|
||||
private UObject _object;
|
||||
private EIconStyle _style;
|
||||
_object = uObject;
|
||||
_style = style;
|
||||
}
|
||||
|
||||
public CreatorPackage(UObject uObject, EIconStyle style)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UCreator ConstructCreator()
|
||||
{
|
||||
TryConstructCreator(out var creator);
|
||||
return creator;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryConstructCreator(out UCreator creator)
|
||||
{
|
||||
switch (_object.ExportType)
|
||||
{
|
||||
_object = uObject;
|
||||
_style = style;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UCreator ConstructCreator()
|
||||
{
|
||||
TryConstructCreator(out var creator);
|
||||
return creator;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryConstructCreator(out UCreator creator)
|
||||
{
|
||||
switch (_object.ExportType)
|
||||
{
|
||||
// Fortnite
|
||||
case "FortCreativeWeaponMeleeItemDefinition":
|
||||
case "AthenaConsumableEmoteItemDefinition":
|
||||
case "AthenaSkyDiveContrailItemDefinition":
|
||||
case "AthenaLoadingScreenItemDefinition":
|
||||
case "AthenaVictoryPoseItemDefinition":
|
||||
case "AthenaPetCarrierItemDefinition":
|
||||
case "AthenaMusicPackItemDefinition":
|
||||
case "AthenaBattleBusItemDefinition":
|
||||
case "AthenaCharacterItemDefinition":
|
||||
case "AthenaMapMarkerItemDefinition":
|
||||
case "AthenaBackpackItemDefinition":
|
||||
case "AthenaPickaxeItemDefinition":
|
||||
case "AthenaGadgetItemDefinition":
|
||||
case "AthenaGliderItemDefinition":
|
||||
case "AthenaSprayItemDefinition":
|
||||
case "AthenaDanceItemDefinition":
|
||||
case "AthenaEmojiItemDefinition":
|
||||
case "AthenaItemWrapDefinition":
|
||||
case "AthenaToyItemDefinition":
|
||||
case "FortHeroType":
|
||||
case "FortTokenType":
|
||||
case "FortAbilityKit":
|
||||
case "FortWorkerType":
|
||||
case "RewardGraphToken":
|
||||
case "FortBannerTokenType":
|
||||
case "FortVariantTokenType":
|
||||
case "FortDecoItemDefinition":
|
||||
case "FortStatItemDefinition":
|
||||
case "FortAmmoItemDefinition":
|
||||
case "FortEmoteItemDefinition":
|
||||
case "FortBadgeItemDefinition":
|
||||
case "FortAwardItemDefinition":
|
||||
case "FortGadgetItemDefinition":
|
||||
case "AthenaCharmItemDefinition":
|
||||
case "FortPlaysetItemDefinition":
|
||||
case "FortGiftBoxItemDefinition":
|
||||
case "FortOutpostItemDefinition":
|
||||
case "FortVehicleItemDefinition":
|
||||
case "FortCardPackItemDefinition":
|
||||
case "FortDefenderItemDefinition":
|
||||
case "FortCurrencyItemDefinition":
|
||||
case "FortResourceItemDefinition":
|
||||
case "FortBackpackItemDefinition":
|
||||
case "FortEventQuestMapDataAsset":
|
||||
case "FortWeaponModItemDefinition":
|
||||
case "FortCodeTokenItemDefinition":
|
||||
case "FortSchematicItemDefinition":
|
||||
case "FortWorldMultiItemDefinition":
|
||||
case "FortAlterationItemDefinition":
|
||||
case "FortExpeditionItemDefinition":
|
||||
case "FortIngredientItemDefinition":
|
||||
case "FortAccountBuffItemDefinition":
|
||||
case "FortWeaponMeleeItemDefinition":
|
||||
case "FortPlayerPerksItemDefinition":
|
||||
case "FortPlaysetPropItemDefinition":
|
||||
case "FortHomebaseNodeItemDefinition":
|
||||
case "FortNeverPersistItemDefinition":
|
||||
case "RadioContentSourceItemDefinition":
|
||||
case "FortPlaysetGrenadeItemDefinition":
|
||||
case "FortPersonalVehicleItemDefinition":
|
||||
case "FortGameplayModifierItemDefinition":
|
||||
case "FortHardcoreModifierItemDefinition":
|
||||
case "FortConsumableAccountItemDefinition":
|
||||
case "FortConversionControlItemDefinition":
|
||||
case "FortAccountBuffCreditItemDefinition":
|
||||
case "FortEventCurrencyItemDefinitionRedir":
|
||||
case "FortPersistentResourceItemDefinition":
|
||||
case "FortHomebaseBannerIconItemDefinition":
|
||||
case "FortCampaignHeroLoadoutItemDefinition":
|
||||
case "FortConditionalResourceItemDefinition":
|
||||
case "FortChallengeBundleScheduleDefinition":
|
||||
case "FortWeaponMeleeDualWieldItemDefinition":
|
||||
case "FortDailyRewardScheduleTokenDefinition":
|
||||
case "FortCreativeRealEstatePlotItemDefinition":
|
||||
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
|
||||
creator = _style switch
|
||||
{
|
||||
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
|
||||
_ => new BaseIcon(_object, _style)
|
||||
};
|
||||
return true;
|
||||
case "FortTandemCharacterData":
|
||||
creator = new BaseTandem(_object, _style);
|
||||
return true;
|
||||
case "FortTrapItemDefinition":
|
||||
case "FortSpyTechItemDefinition":
|
||||
case "FortAccoladeItemDefinition":
|
||||
case "FortContextTrapItemDefinition":
|
||||
case "FortWeaponRangedItemDefinition":
|
||||
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
|
||||
creator = new BaseIconStats(_object, _style);
|
||||
return true;
|
||||
case "FortItemSeriesDefinition":
|
||||
creator = new BaseSeries(_object, _style);
|
||||
return true;
|
||||
case "MaterialInstanceConstant"
|
||||
when _object.Owner != null &&
|
||||
(_object.Owner.Name.EndsWith($"/MI_OfferImages/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
|
||||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
|
||||
_object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)):
|
||||
creator = new BaseMaterialInstance(_object, _style);
|
||||
return true;
|
||||
case "AthenaItemShopOfferDisplayData":
|
||||
creator = new BaseOfferDisplayData(_object, _style);
|
||||
return true;
|
||||
case "FortMtxOfferData":
|
||||
creator = new BaseMtxOffer(_object, _style);
|
||||
return true;
|
||||
case "FortPlaylistAthena":
|
||||
creator = new BasePlaylist(_object, _style);
|
||||
return true;
|
||||
case "FortFeatItemDefinition":
|
||||
case "FortQuestItemDefinition":
|
||||
case "FortQuestItemDefinition_Athena":
|
||||
case "AthenaDailyQuestDefinition":
|
||||
case "FortUrgentQuestItemDefinition":
|
||||
creator = new BaseQuest(_object, _style);
|
||||
return true;
|
||||
case "FortCompendiumItemDefinition":
|
||||
case "FortChallengeBundleItemDefinition":
|
||||
creator = new BaseBundle(_object, _style);
|
||||
return true;
|
||||
case "AthenaSeasonItemDefinition":
|
||||
creator = new BaseSeason(_object, _style);
|
||||
return true;
|
||||
case "FortItemAccessTokenType":
|
||||
creator = new BaseItemAccessToken(_object, _style);
|
||||
return true;
|
||||
case "PlaylistUserOptionEnum":
|
||||
case "PlaylistUserOptionBool":
|
||||
case "PlaylistUserOptionString":
|
||||
case "PlaylistUserOptionIntEnum":
|
||||
case "PlaylistUserOptionIntRange":
|
||||
case "PlaylistUserOptionColorEnum":
|
||||
case "PlaylistUserOptionFloatEnum":
|
||||
case "PlaylistUserOptionFloatRange":
|
||||
case "PlaylistUserTintedIconIntEnum":
|
||||
case "PlaylistUserOptionPrimaryAsset":
|
||||
case "PlaylistUserOptionCollisionProfileEnum":
|
||||
creator = new BaseUserControl(_object, _style);
|
||||
return true;
|
||||
// Battle Breakers
|
||||
case "WExpGenericAccountItemDefinition":
|
||||
case "WExpGearAccountItemDefinition":
|
||||
case "WExpHQWorkerLodgesDefinition":
|
||||
case "WExpPersonalEventDefinition":
|
||||
case "WExpUpgradePotionDefinition":
|
||||
case "WExpAccountRewardDefinition":
|
||||
case "WExpHQBlacksmithDefinition":
|
||||
case "WExpHQSecretShopDefinition":
|
||||
case "WExpHQMonsterPitDefinition":
|
||||
case "WExpHQHeroTowerDefinition":
|
||||
case "WExpVoucherItemDefinition":
|
||||
case "WExpTreasureMapDefinition":
|
||||
case "WExpHammerChestDefinition":
|
||||
case "WExpHQWorkshopDefinition":
|
||||
case "WExpUnlockableDefinition":
|
||||
case "WExpHQSmelterDefinition":
|
||||
case "WExpContainerDefinition":
|
||||
case "WExpCharacterDefinition":
|
||||
case "WExpHQMarketDefinition":
|
||||
case "WExpGiftboxDefinition":
|
||||
case "WExpStandInDefinition":
|
||||
case "WExpRegionDefinition":
|
||||
case "WExpHQMineDefinition":
|
||||
case "WExpXpBookDefinition":
|
||||
case "WExpTokenDefinition":
|
||||
case "WExpItemDefinition":
|
||||
case "WExpZoneDefinition":
|
||||
creator = new BaseBreakersIcon(_object, EIconStyle.Default);
|
||||
return true;
|
||||
// Spellbreak
|
||||
case "GTargetedTeleportActiveSkill":
|
||||
case "GChronomasterV2ActiveSkill":
|
||||
case "GShadowstepActiveSkill":
|
||||
case "GGatewayActiveSkill":
|
||||
case "GStealthActiveSkill":
|
||||
case "GFeatherActiveSkill":
|
||||
case "GCosmeticDropTrail":
|
||||
case "GFlightActiveSkill":
|
||||
case "GCosmeticRunTrail":
|
||||
case "GCosmeticArtifact":
|
||||
case "GCosmeticTriumph":
|
||||
case "GWolfsbloodSkill":
|
||||
case "GDashActiveSkill":
|
||||
case "GCharacterPerk":
|
||||
case "GCosmeticTitle":
|
||||
case "GCosmeticBadge":
|
||||
case "GRMTStoreOffer":
|
||||
case "GCosmeticEmote":
|
||||
case "GCosmeticCard":
|
||||
case "GCosmeticSkin":
|
||||
case "GStoreOffer":
|
||||
case "GAccolade":
|
||||
case "GRuneItem":
|
||||
case "GQuest":
|
||||
creator = new BaseSpellIcon(_object, EIconStyle.Default);
|
||||
return true;
|
||||
case "GLeagueTier":
|
||||
creator = new BaseLeague(_object, EIconStyle.Default);
|
||||
return true;
|
||||
case "GLeagueDivision":
|
||||
creator = new BaseDivision(_object, EIconStyle.Default);
|
||||
return true;
|
||||
default:
|
||||
creator = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"{_object.ExportType} | {_style}";
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_object = null;
|
||||
// Fortnite
|
||||
case "FortCreativeWeaponMeleeItemDefinition":
|
||||
case "AthenaConsumableEmoteItemDefinition":
|
||||
case "AthenaSkyDiveContrailItemDefinition":
|
||||
case "AthenaLoadingScreenItemDefinition":
|
||||
case "AthenaVictoryPoseItemDefinition":
|
||||
case "AthenaPetCarrierItemDefinition":
|
||||
case "AthenaMusicPackItemDefinition":
|
||||
case "AthenaBattleBusItemDefinition":
|
||||
case "AthenaCharacterItemDefinition":
|
||||
case "AthenaMapMarkerItemDefinition":
|
||||
case "AthenaBackpackItemDefinition":
|
||||
case "AthenaPickaxeItemDefinition":
|
||||
case "AthenaGadgetItemDefinition":
|
||||
case "AthenaGliderItemDefinition":
|
||||
case "AthenaSprayItemDefinition":
|
||||
case "AthenaDanceItemDefinition":
|
||||
case "AthenaEmojiItemDefinition":
|
||||
case "AthenaItemWrapDefinition":
|
||||
case "AthenaToyItemDefinition":
|
||||
case "FortHeroType":
|
||||
case "FortTokenType":
|
||||
case "FortAbilityKit":
|
||||
case "FortWorkerType":
|
||||
case "RewardGraphToken":
|
||||
case "FortBannerTokenType":
|
||||
case "FortVariantTokenType":
|
||||
case "FortDecoItemDefinition":
|
||||
case "FortStatItemDefinition":
|
||||
case "FortAmmoItemDefinition":
|
||||
case "FortEmoteItemDefinition":
|
||||
case "FortBadgeItemDefinition":
|
||||
case "FortAwardItemDefinition":
|
||||
case "FortGadgetItemDefinition":
|
||||
case "AthenaCharmItemDefinition":
|
||||
case "FortPlaysetItemDefinition":
|
||||
case "FortGiftBoxItemDefinition":
|
||||
case "FortOutpostItemDefinition":
|
||||
case "FortVehicleItemDefinition":
|
||||
case "FortCardPackItemDefinition":
|
||||
case "FortDefenderItemDefinition":
|
||||
case "FortCurrencyItemDefinition":
|
||||
case "FortResourceItemDefinition":
|
||||
case "FortBackpackItemDefinition":
|
||||
case "FortEventQuestMapDataAsset":
|
||||
case "FortWeaponModItemDefinition":
|
||||
case "FortCodeTokenItemDefinition":
|
||||
case "FortSchematicItemDefinition":
|
||||
case "FortWorldMultiItemDefinition":
|
||||
case "FortAlterationItemDefinition":
|
||||
case "FortExpeditionItemDefinition":
|
||||
case "FortIngredientItemDefinition":
|
||||
case "FortAccountBuffItemDefinition":
|
||||
case "FortWeaponMeleeItemDefinition":
|
||||
case "FortPlayerPerksItemDefinition":
|
||||
case "FortPlaysetPropItemDefinition":
|
||||
case "FortHomebaseNodeItemDefinition":
|
||||
case "FortNeverPersistItemDefinition":
|
||||
case "RadioContentSourceItemDefinition":
|
||||
case "FortPlaysetGrenadeItemDefinition":
|
||||
case "FortPersonalVehicleItemDefinition":
|
||||
case "FortGameplayModifierItemDefinition":
|
||||
case "FortHardcoreModifierItemDefinition":
|
||||
case "FortConsumableAccountItemDefinition":
|
||||
case "FortConversionControlItemDefinition":
|
||||
case "FortAccountBuffCreditItemDefinition":
|
||||
case "FortEventCurrencyItemDefinitionRedir":
|
||||
case "FortPersistentResourceItemDefinition":
|
||||
case "FortHomebaseBannerIconItemDefinition":
|
||||
case "FortCampaignHeroLoadoutItemDefinition":
|
||||
case "FortConditionalResourceItemDefinition":
|
||||
case "FortChallengeBundleScheduleDefinition":
|
||||
case "FortWeaponMeleeDualWieldItemDefinition":
|
||||
case "FortDailyRewardScheduleTokenDefinition":
|
||||
case "FortCreativeRealEstatePlotItemDefinition":
|
||||
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
|
||||
creator = _style switch
|
||||
{
|
||||
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
|
||||
_ => new BaseIcon(_object, _style)
|
||||
};
|
||||
return true;
|
||||
case "FortTandemCharacterData":
|
||||
creator = new BaseTandem(_object, _style);
|
||||
return true;
|
||||
case "FortTrapItemDefinition":
|
||||
case "FortSpyTechItemDefinition":
|
||||
case "FortAccoladeItemDefinition":
|
||||
case "FortContextTrapItemDefinition":
|
||||
case "FortWeaponRangedItemDefinition":
|
||||
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
|
||||
creator = new BaseIconStats(_object, _style);
|
||||
return true;
|
||||
case "FortItemSeriesDefinition":
|
||||
creator = new BaseSeries(_object, _style);
|
||||
return true;
|
||||
case "MaterialInstanceConstant"
|
||||
when _object.Owner != null &&
|
||||
(_object.Owner.Name.EndsWith($"/MI_OfferImages/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
|
||||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
|
||||
_object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)):
|
||||
creator = new BaseMaterialInstance(_object, _style);
|
||||
return true;
|
||||
case "AthenaItemShopOfferDisplayData":
|
||||
creator = new BaseOfferDisplayData(_object, _style);
|
||||
return true;
|
||||
case "FortMtxOfferData":
|
||||
creator = new BaseMtxOffer(_object, _style);
|
||||
return true;
|
||||
case "FortPlaylistAthena":
|
||||
creator = new BasePlaylist(_object, _style);
|
||||
return true;
|
||||
case "FortFeatItemDefinition":
|
||||
case "FortQuestItemDefinition":
|
||||
case "FortQuestItemDefinition_Athena":
|
||||
case "AthenaDailyQuestDefinition":
|
||||
case "FortUrgentQuestItemDefinition":
|
||||
creator = new BaseQuest(_object, _style);
|
||||
return true;
|
||||
case "FortCompendiumItemDefinition":
|
||||
case "FortChallengeBundleItemDefinition":
|
||||
creator = new BaseBundle(_object, _style);
|
||||
return true;
|
||||
case "AthenaSeasonItemDefinition":
|
||||
creator = new BaseSeason(_object, _style);
|
||||
return true;
|
||||
case "FortItemAccessTokenType":
|
||||
creator = new BaseItemAccessToken(_object, _style);
|
||||
return true;
|
||||
case "PlaylistUserOptionEnum":
|
||||
case "PlaylistUserOptionBool":
|
||||
case "PlaylistUserOptionString":
|
||||
case "PlaylistUserOptionIntEnum":
|
||||
case "PlaylistUserOptionIntRange":
|
||||
case "PlaylistUserOptionColorEnum":
|
||||
case "PlaylistUserOptionFloatEnum":
|
||||
case "PlaylistUserOptionFloatRange":
|
||||
case "PlaylistUserTintedIconIntEnum":
|
||||
case "PlaylistUserOptionPrimaryAsset":
|
||||
case "PlaylistUserOptionCollisionProfileEnum":
|
||||
creator = new BaseUserControl(_object, _style);
|
||||
return true;
|
||||
// Battle Breakers
|
||||
case "WExpGenericAccountItemDefinition":
|
||||
case "WExpGearAccountItemDefinition":
|
||||
case "WExpHQWorkerLodgesDefinition":
|
||||
case "WExpPersonalEventDefinition":
|
||||
case "WExpUpgradePotionDefinition":
|
||||
case "WExpAccountRewardDefinition":
|
||||
case "WExpHQBlacksmithDefinition":
|
||||
case "WExpHQSecretShopDefinition":
|
||||
case "WExpHQMonsterPitDefinition":
|
||||
case "WExpHQHeroTowerDefinition":
|
||||
case "WExpVoucherItemDefinition":
|
||||
case "WExpTreasureMapDefinition":
|
||||
case "WExpHammerChestDefinition":
|
||||
case "WExpHQWorkshopDefinition":
|
||||
case "WExpUnlockableDefinition":
|
||||
case "WExpHQSmelterDefinition":
|
||||
case "WExpContainerDefinition":
|
||||
case "WExpCharacterDefinition":
|
||||
case "WExpHQMarketDefinition":
|
||||
case "WExpGiftboxDefinition":
|
||||
case "WExpStandInDefinition":
|
||||
case "WExpRegionDefinition":
|
||||
case "WExpHQMineDefinition":
|
||||
case "WExpXpBookDefinition":
|
||||
case "WExpTokenDefinition":
|
||||
case "WExpItemDefinition":
|
||||
case "WExpZoneDefinition":
|
||||
creator = new BaseBreakersIcon(_object, EIconStyle.Default);
|
||||
return true;
|
||||
// Spellbreak
|
||||
case "GTargetedTeleportActiveSkill":
|
||||
case "GChronomasterV2ActiveSkill":
|
||||
case "GShadowstepActiveSkill":
|
||||
case "GGatewayActiveSkill":
|
||||
case "GStealthActiveSkill":
|
||||
case "GFeatherActiveSkill":
|
||||
case "GCosmeticDropTrail":
|
||||
case "GFlightActiveSkill":
|
||||
case "GCosmeticRunTrail":
|
||||
case "GCosmeticArtifact":
|
||||
case "GCosmeticTriumph":
|
||||
case "GWolfsbloodSkill":
|
||||
case "GDashActiveSkill":
|
||||
case "GCharacterPerk":
|
||||
case "GCosmeticTitle":
|
||||
case "GCosmeticBadge":
|
||||
case "GRMTStoreOffer":
|
||||
case "GCosmeticEmote":
|
||||
case "GCosmeticCard":
|
||||
case "GCosmeticSkin":
|
||||
case "GStoreOffer":
|
||||
case "GAccolade":
|
||||
case "GRuneItem":
|
||||
case "GQuest":
|
||||
creator = new BaseSpellIcon(_object, EIconStyle.Default);
|
||||
return true;
|
||||
case "GLeagueTier":
|
||||
creator = new BaseLeague(_object, EIconStyle.Default);
|
||||
return true;
|
||||
case "GLeagueDivision":
|
||||
creator = new BaseDivision(_object, EIconStyle.Default);
|
||||
return true;
|
||||
default:
|
||||
creator = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"{_object.ExportType} | {_style}";
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_object = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,274 +6,273 @@ using FModel.Settings;
|
|||
using FModel.ViewModels;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator
|
||||
namespace FModel.Creator;
|
||||
|
||||
public class Typefaces
|
||||
{
|
||||
public class Typefaces
|
||||
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf");
|
||||
private const string _EXT = ".ufont";
|
||||
|
||||
// FortniteGame
|
||||
private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
|
||||
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
|
||||
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
|
||||
private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black";
|
||||
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
|
||||
private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
|
||||
private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset";
|
||||
private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite
|
||||
private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji";
|
||||
private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
|
||||
private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
|
||||
private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic";
|
||||
private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular";
|
||||
private const string _NOTO_SANS_ITALIC = "NotoSans-Italic";
|
||||
private const string _NOTO_SANS_REGULAR = "NotoSans-Regular";
|
||||
private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite
|
||||
private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold";
|
||||
private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
|
||||
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold";
|
||||
private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
|
||||
private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite
|
||||
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
|
||||
private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite
|
||||
private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular";
|
||||
private const string _BURBANK_SMALL_BLACK = "burbanksmall-black";
|
||||
private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold";
|
||||
|
||||
// Spellbreak
|
||||
private const string _SPELLBREAK_BASE_PATH = "/Game/UI/Fonts/";
|
||||
private const string _MONTSERRAT_SEMIBOLD = "Montserrat-Semibold";
|
||||
private const string _MONTSERRAT_SEMIBOLD_ITALIC = "Montserrat-SemiBoldItalic";
|
||||
private const string _NANUM_GOTHIC = "NanumGothic";
|
||||
private const string _QUADRAT_BOLD = "Quadrat_Bold";
|
||||
private const string _SEGOE_BOLD_ITALIC = "Segoe_Bold_Italic";
|
||||
|
||||
// WorldExplorers
|
||||
private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/";
|
||||
private const string _HEMIHEAD426 = "HemiHead426";
|
||||
private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular";
|
||||
private const string _LATO_BLACK = "Lato-Black";
|
||||
private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic";
|
||||
private const string _LATO_LIGHT = "Lato-Light";
|
||||
private const string _LATO_MEDIUM = "Lato-Medium";
|
||||
|
||||
private readonly CUE4ParseViewModel _viewModel;
|
||||
|
||||
public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...)
|
||||
public readonly SKTypeface DisplayName;
|
||||
public readonly SKTypeface Description;
|
||||
public readonly SKTypeface Bottom; // must be null for non-latin base languages
|
||||
public readonly SKTypeface Bundle;
|
||||
public readonly SKTypeface BundleNumber;
|
||||
public readonly SKTypeface TandemDisplayName;
|
||||
public readonly SKTypeface TandemGenDescription;
|
||||
public readonly SKTypeface TandemAddDescription;
|
||||
|
||||
public Typefaces(CUE4ParseViewModel viewModel)
|
||||
{
|
||||
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf");
|
||||
private const string _EXT = ".ufont";
|
||||
byte[] data;
|
||||
_viewModel = viewModel;
|
||||
var language = UserSettings.Default.AssetLanguage;
|
||||
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
|
||||
|
||||
// FortniteGame
|
||||
private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
|
||||
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
|
||||
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
|
||||
private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black";
|
||||
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
|
||||
private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
|
||||
private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset";
|
||||
private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite
|
||||
private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji";
|
||||
private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
|
||||
private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
|
||||
private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic";
|
||||
private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular";
|
||||
private const string _NOTO_SANS_ITALIC = "NotoSans-Italic";
|
||||
private const string _NOTO_SANS_REGULAR = "NotoSans-Regular";
|
||||
private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite
|
||||
private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold";
|
||||
private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
|
||||
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold";
|
||||
private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
|
||||
private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite
|
||||
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
|
||||
private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite
|
||||
private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular";
|
||||
private const string _BURBANK_SMALL_BLACK = "burbanksmall-black";
|
||||
private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold";
|
||||
|
||||
// Spellbreak
|
||||
private const string _SPELLBREAK_BASE_PATH = "/Game/UI/Fonts/";
|
||||
private const string _MONTSERRAT_SEMIBOLD = "Montserrat-Semibold";
|
||||
private const string _MONTSERRAT_SEMIBOLD_ITALIC = "Montserrat-SemiBoldItalic";
|
||||
private const string _NANUM_GOTHIC = "NanumGothic";
|
||||
private const string _QUADRAT_BOLD = "Quadrat_Bold";
|
||||
private const string _SEGOE_BOLD_ITALIC = "Segoe_Bold_Italic";
|
||||
|
||||
// WorldExplorers
|
||||
private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/";
|
||||
private const string _HEMIHEAD426 = "HemiHead426";
|
||||
private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular";
|
||||
private const string _LATO_BLACK = "Lato-Black";
|
||||
private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic";
|
||||
private const string _LATO_LIGHT = "Lato-Light";
|
||||
private const string _LATO_MEDIUM = "Lato-Medium";
|
||||
|
||||
private readonly CUE4ParseViewModel _viewModel;
|
||||
|
||||
public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...)
|
||||
public readonly SKTypeface DisplayName;
|
||||
public readonly SKTypeface Description;
|
||||
public readonly SKTypeface Bottom; // must be null for non-latin base languages
|
||||
public readonly SKTypeface Bundle;
|
||||
public readonly SKTypeface BundleNumber;
|
||||
public readonly SKTypeface TandemDisplayName;
|
||||
public readonly SKTypeface TandemGenDescription;
|
||||
public readonly SKTypeface TandemAddDescription;
|
||||
|
||||
public Typefaces(CUE4ParseViewModel viewModel)
|
||||
switch (viewModel.Game)
|
||||
{
|
||||
byte[] data;
|
||||
_viewModel = viewModel;
|
||||
var language = UserSettings.Default.AssetLanguage;
|
||||
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
|
||||
|
||||
switch (viewModel.Game)
|
||||
case FGame.FortniteGame:
|
||||
{
|
||||
case FGame.FortniteGame:
|
||||
var namePath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => string.Empty
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data))
|
||||
{
|
||||
var namePath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => string.Empty
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
DisplayName = SKTypeface.FromStream(m);
|
||||
}
|
||||
else DisplayName = Default;
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
DisplayName = SKTypeface.FromStream(m);
|
||||
}
|
||||
else DisplayName = Default;
|
||||
|
||||
var descriptionPath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_REGULAR,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_REGULAR,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
|
||||
_ => _NOTO_SANS_REGULAR
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
Description = SKTypeface.FromStream(m);
|
||||
}
|
||||
else Description = Default;
|
||||
var descriptionPath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_REGULAR,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_REGULAR,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
|
||||
_ => _NOTO_SANS_REGULAR
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
Description = SKTypeface.FromStream(m);
|
||||
}
|
||||
else Description = Default;
|
||||
|
||||
var bottomPath = _FORTNITE_BASE_PATH +
|
||||
var bottomPath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => string.Empty,
|
||||
ELanguage.Japanese => string.Empty,
|
||||
ELanguage.Arabic => string.Empty,
|
||||
ELanguage.TraditionalChinese => string.Empty,
|
||||
ELanguage.Chinese => string.Empty,
|
||||
_ => _BURBANK_SMALL_BOLD
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(bottomPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
Bottom = SKTypeface.FromStream(m);
|
||||
}
|
||||
// else keep it null
|
||||
|
||||
if (viewModel.Provider.TrySaveAsset(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
BundleNumber = SKTypeface.FromStream(m);
|
||||
}
|
||||
else BundleNumber = Default;
|
||||
|
||||
var bundleNamePath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => string.Empty,
|
||||
ELanguage.Japanese => string.Empty,
|
||||
ELanguage.Arabic => string.Empty,
|
||||
ELanguage.TraditionalChinese => string.Empty,
|
||||
ELanguage.Chinese => string.Empty,
|
||||
_ => _BURBANK_SMALL_BOLD
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => string.Empty
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(bottomPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
Bottom = SKTypeface.FromStream(m);
|
||||
}
|
||||
// else keep it null
|
||||
|
||||
if (viewModel.Provider.TrySaveAsset(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
BundleNumber = SKTypeface.FromStream(m);
|
||||
}
|
||||
else BundleNumber = Default;
|
||||
|
||||
var bundleNamePath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => string.Empty
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(bundleNamePath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
Bundle = SKTypeface.FromStream(m);
|
||||
}
|
||||
else Bundle = BundleNumber;
|
||||
|
||||
var tandemDisplayNamePath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => _BURBANK_BIG_REGULAR_BLACK
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(tandemDisplayNamePath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
TandemDisplayName = SKTypeface.FromStream(m);
|
||||
}
|
||||
else TandemDisplayName = Default;
|
||||
|
||||
var tandemGeneralDescPath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => _BURBANK_SMALL_BLACK
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(tandemGeneralDescPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
TandemGenDescription = SKTypeface.FromStream(m);
|
||||
}
|
||||
else TandemGenDescription = Default;
|
||||
|
||||
var tandemAdditionalDescPath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => _BURBANK_SMALL_BOLD
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(tandemAdditionalDescPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
TandemAddDescription = SKTypeface.FromStream(m);
|
||||
}
|
||||
else TandemAddDescription = Default;
|
||||
|
||||
break;
|
||||
}
|
||||
case FGame.WorldExplorers:
|
||||
if (viewModel.Provider.TrySaveAsset(bundleNamePath + _EXT, out data))
|
||||
{
|
||||
var namePath = _BATTLE_BREAKERS_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
|
||||
ELanguage.Russian => _LATO_BLACK,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
|
||||
_ => _HEMIHEAD426
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
DisplayName = SKTypeface.FromStream(m);
|
||||
}
|
||||
else DisplayName = Default;
|
||||
|
||||
var descriptionPath = _BATTLE_BREAKERS_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
|
||||
ELanguage.Russian => _LATO_BLACK,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
|
||||
_ => _HEMIHEAD426
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
Description = SKTypeface.FromStream(m);
|
||||
}
|
||||
else Description = Default;
|
||||
|
||||
break;
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
Bundle = SKTypeface.FromStream(m);
|
||||
}
|
||||
case FGame.g3:
|
||||
else Bundle = BundleNumber;
|
||||
|
||||
var tandemDisplayNamePath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => _BURBANK_BIG_REGULAR_BLACK
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(tandemDisplayNamePath + _EXT, out data))
|
||||
{
|
||||
if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
DisplayName = SKTypeface.FromStream(m);
|
||||
}
|
||||
else DisplayName = Default;
|
||||
|
||||
if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
Description = SKTypeface.FromStream(m);
|
||||
}
|
||||
else Description = Default;
|
||||
|
||||
break;
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
TandemDisplayName = SKTypeface.FromStream(m);
|
||||
}
|
||||
else TandemDisplayName = Default;
|
||||
|
||||
var tandemGeneralDescPath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => _BURBANK_SMALL_BLACK
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(tandemGeneralDescPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
TandemGenDescription = SKTypeface.FromStream(m);
|
||||
}
|
||||
else TandemGenDescription = Default;
|
||||
|
||||
var tandemAdditionalDescPath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => _BURBANK_SMALL_BOLD
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(tandemAdditionalDescPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
TandemAddDescription = SKTypeface.FromStream(m);
|
||||
}
|
||||
else TandemAddDescription = Default;
|
||||
|
||||
break;
|
||||
}
|
||||
case FGame.WorldExplorers:
|
||||
{
|
||||
var namePath = _BATTLE_BREAKERS_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
|
||||
ELanguage.Russian => _LATO_BLACK,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
|
||||
_ => _HEMIHEAD426
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
DisplayName = SKTypeface.FromStream(m);
|
||||
}
|
||||
else DisplayName = Default;
|
||||
|
||||
var descriptionPath = _BATTLE_BREAKERS_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
|
||||
ELanguage.Russian => _LATO_BLACK,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
|
||||
_ => _HEMIHEAD426
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
Description = SKTypeface.FromStream(m);
|
||||
}
|
||||
else Description = Default;
|
||||
|
||||
break;
|
||||
}
|
||||
case FGame.g3:
|
||||
{
|
||||
if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
DisplayName = SKTypeface.FromStream(m);
|
||||
}
|
||||
else DisplayName = Default;
|
||||
|
||||
if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
Description = SKTypeface.FromStream(m);
|
||||
}
|
||||
else Description = Default;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public SKTypeface OnTheFly(string path)
|
||||
{
|
||||
if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return Default;
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
return SKTypeface.FromStream(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SKTypeface OnTheFly(string path)
|
||||
{
|
||||
if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return Default;
|
||||
var m = new MemoryStream(data) { Position = 0 };
|
||||
return SKTypeface.FromStream(m);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
|
|
@ -19,382 +18,377 @@ using FModel.ViewModels;
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator
|
||||
namespace FModel.Creator;
|
||||
|
||||
public static class Utils
|
||||
{
|
||||
public static class Utils
|
||||
private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private static readonly Regex _htmlRegex = new("<.*?>");
|
||||
public static Typefaces Typefaces;
|
||||
|
||||
public static string RemoveHtmlTags(string s)
|
||||
{
|
||||
private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private static readonly Regex _htmlRegex = new("<.*?>");
|
||||
public static Typefaces Typefaces;
|
||||
|
||||
public static string RemoveHtmlTags(string s)
|
||||
var match = _htmlRegex.Match(s);
|
||||
while (match.Success)
|
||||
{
|
||||
var match = _htmlRegex.Match(s);
|
||||
while (match.Success)
|
||||
{
|
||||
s = s.Replace(match.Value, string.Empty);
|
||||
match = match.NextMatch();
|
||||
}
|
||||
|
||||
return s;
|
||||
s = s.Replace(match.Value, string.Empty);
|
||||
match = match.NextMatch();
|
||||
}
|
||||
|
||||
public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview)
|
||||
return s;
|
||||
}
|
||||
|
||||
public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview)
|
||||
{
|
||||
if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
|
||||
{
|
||||
if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
|
||||
{
|
||||
preview = GetBitmap(sidePanelIcon);
|
||||
return preview != null;
|
||||
}
|
||||
|
||||
var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}";
|
||||
if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition
|
||||
{
|
||||
if (!TryLoadObject($"{path[..path.LastIndexOf('_')]}_{path.SubstringAfterLast('_').ToLower()}", out material)) // Try to get MI with lowercase obfuscation
|
||||
TryLoadObject(path[..path.LastIndexOf('_')], out material); // hopefully gets obfuscated item definition
|
||||
}
|
||||
|
||||
preview = GetBitmap(material);
|
||||
preview = GetBitmap(sidePanelIcon);
|
||||
return preview != null;
|
||||
}
|
||||
|
||||
public static SKBitmap GetBitmap(FPackageIndex packageIndex)
|
||||
var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}";
|
||||
if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition
|
||||
{
|
||||
while (true)
|
||||
if (!TryLoadObject($"{path[..path.LastIndexOf('_')]}_{path.SubstringAfterLast('_').ToLower()}", out material)) // Try to get MI with lowercase obfuscation
|
||||
TryLoadObject(path[..path.LastIndexOf('_')], out material); // hopefully gets obfuscated item definition
|
||||
}
|
||||
|
||||
preview = GetBitmap(material);
|
||||
return preview != null;
|
||||
}
|
||||
|
||||
public static SKBitmap GetBitmap(FPackageIndex packageIndex)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!TryGetPackageIndexExport(packageIndex, out UObject export)) return null;
|
||||
switch (export)
|
||||
{
|
||||
if (!TryGetPackageIndexExport(packageIndex, out UObject export)) return null;
|
||||
switch (export)
|
||||
case UTexture2D texture:
|
||||
return GetBitmap(texture);
|
||||
case UMaterialInstanceConstant material:
|
||||
return GetBitmap(material);
|
||||
default:
|
||||
{
|
||||
case UTexture2D texture:
|
||||
return GetBitmap(texture);
|
||||
case UMaterialInstanceConstant material:
|
||||
return GetBitmap(material);
|
||||
default:
|
||||
if (export.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage);
|
||||
if (export.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
|
||||
if (export.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage"))
|
||||
{
|
||||
if (export.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage);
|
||||
if (export.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
|
||||
if (export.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage"))
|
||||
{
|
||||
packageIndex = smallPreview;
|
||||
continue;
|
||||
}
|
||||
|
||||
return null;
|
||||
packageIndex = smallPreview;
|
||||
continue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
|
||||
{
|
||||
if (material == null) return null;
|
||||
foreach (var textureParameter in material.TextureParameterValues)
|
||||
{
|
||||
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture)) continue;
|
||||
switch (textureParameter.ParameterInfo.Name.Text)
|
||||
{
|
||||
case "MainTex":
|
||||
case "TextureA":
|
||||
case "TextureB":
|
||||
case "OfferImage":
|
||||
case "KeyArtTexture":
|
||||
case "NPC-Portrait":
|
||||
{
|
||||
return GetBitmap(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) {Position = 0});
|
||||
public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text);
|
||||
public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null;
|
||||
public static SKBitmap GetBitmap(UTexture2D texture) => texture.IsVirtual ? null : texture.Decode(UserSettings.Default.OverridedPlatform);
|
||||
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
|
||||
|
||||
public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height)
|
||||
{
|
||||
var ratioX = width / me.Width;
|
||||
var ratioY = height / me.Height;
|
||||
var ratio = ratioX < ratioY ? ratioX : ratioY;
|
||||
return me.Resize(Convert.ToInt32(me.Width * ratio), Convert.ToInt32(me.Height * ratio));
|
||||
}
|
||||
public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size);
|
||||
public static SKBitmap Resize(this SKBitmap me, int width, int height)
|
||||
{
|
||||
var bmp = new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels);
|
||||
using var pixmap = bmp.PeekPixels();
|
||||
me.ScalePixels(pixmap, SKFilterQuality.Medium);
|
||||
return bmp;
|
||||
}
|
||||
|
||||
public static void ClearToTransparent(this SKBitmap me, SKColor colorToDelete) {
|
||||
var colors = me.Pixels;
|
||||
for (var n = 0; n < colors.Length; n++) {
|
||||
if (colors[n] != colorToDelete) continue;
|
||||
colors[n] = SKColors.Transparent;
|
||||
}
|
||||
me.Pixels = colors;
|
||||
}
|
||||
|
||||
public static bool TryGetPackageIndexExport<T>(FPackageIndex packageIndex, out T export) where T : UObject
|
||||
{
|
||||
return packageIndex.TryLoad(out export);
|
||||
}
|
||||
|
||||
// fullpath must be either without any extension or with the export objectname
|
||||
public static bool TryLoadObject<T>(string fullPath, out T export) where T : UObject
|
||||
{
|
||||
return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export);
|
||||
}
|
||||
|
||||
public static IEnumerable<UObject> LoadExports(string fullPath)
|
||||
{
|
||||
return _applicationView.CUE4Parse.Provider.LoadObjectExports(fullPath);
|
||||
}
|
||||
|
||||
public static float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f)
|
||||
{
|
||||
var max = maxFont;
|
||||
var min = 0f;
|
||||
var last = -1f;
|
||||
float value;
|
||||
while (true)
|
||||
{
|
||||
value = min + ((max - min) / 2);
|
||||
using (SKFont ft = new SKFont(typeface, value))
|
||||
using (SKPaint paint = new SKPaint(ft))
|
||||
{
|
||||
if (paint.MeasureText(text) > sectorSize)
|
||||
{
|
||||
last = value;
|
||||
max = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
min = value;
|
||||
if (Math.Abs(last - value) <= degreeOfCertainty)
|
||||
return last;
|
||||
|
||||
last = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLocalizedResource(string @namespace, string key, string defaultValue)
|
||||
{
|
||||
return _applicationView.CUE4Parse.Provider.GetLocalizedString(@namespace, key, defaultValue);
|
||||
}
|
||||
|
||||
public static string GetFullPath(string partialPath)
|
||||
{
|
||||
var regex = new Regex(partialPath, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
foreach (var path in _applicationView.CUE4Parse.Provider.Files.Keys)
|
||||
{
|
||||
if (regex.IsMatch(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static void DrawCenteredMultilineText(SKCanvas c, string text, int maxCount, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint)
|
||||
{
|
||||
var lineHeight = paint.TextSize * 1.2f;
|
||||
var lines = SplitLines(text, paint, area.Width - margin);
|
||||
|
||||
#if DEBUG
|
||||
c.DrawRect(new SKRect(area.Left, area.Top, area.Right, area.Bottom), new SKPaint {Color = SKColors.Red, IsStroke = true});
|
||||
#endif
|
||||
|
||||
if (lines == null) return;
|
||||
if (lines.Count <= maxCount) maxCount = lines.Count;
|
||||
var height = maxCount * lineHeight;
|
||||
var y = area.MidY - height / 2;
|
||||
|
||||
var shaper = new CustomSKShaper(paint.Typeface);
|
||||
for (var i = 0; i < maxCount; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (line == null) continue;
|
||||
|
||||
var lineText = line.Trim();
|
||||
var shapedText = shaper.Shape(lineText, paint);
|
||||
|
||||
y += lineHeight;
|
||||
var x = side switch
|
||||
{
|
||||
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
|
||||
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
|
||||
SKTextAlign.Left => margin,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, lineText, x, y, paint);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawMultilineText(SKCanvas c, string text, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint, out float yPos)
|
||||
{
|
||||
yPos = area.Top;
|
||||
var lineHeight = paint.TextSize * 1.2f;
|
||||
var lines = SplitLines(text, paint, area.Width);
|
||||
if (lines == null) return;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var fontSize = GetMaxFontSize(area.Width, paint.Typeface, line);
|
||||
if (paint.TextSize > fontSize) // if the text is not fitting in the line decrease the font size (CKJ languages)
|
||||
{
|
||||
paint.TextSize = fontSize;
|
||||
lineHeight = paint.TextSize * 1.2f;
|
||||
}
|
||||
|
||||
if (line == null) continue;
|
||||
var lineText = line.Trim();
|
||||
var shaper = new CustomSKShaper(paint.Typeface);
|
||||
var shapedText = shaper.Shape(lineText, paint);
|
||||
|
||||
var x = side switch
|
||||
{
|
||||
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
|
||||
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
|
||||
SKTextAlign.Left => area.Left,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, lineText, x, yPos, paint);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
c.DrawRect(new SKRect(area.Left, area.Top - paint.TextSize, area.Right, yPos), new SKPaint {Color = SKColors.Red, IsStroke = true});
|
||||
#endif
|
||||
}
|
||||
|
||||
#region Chinese, Korean and Japanese text split
|
||||
// https://github.com/YoungjaeKim/mikan.sharp/blob/master/MikanSharp/Mikan/Mikan.cs
|
||||
|
||||
static string joshi = @"(でなければ|について|かしら|くらい|けれど|なのか|ばかり|ながら|ことよ|こそ|こと|さえ|しか|した|たり|だけ|だに|だの|つつ|ても|てよ|でも|とも|から|など|なり|ので|のに|ほど|まで|もの|やら|より|って|で|と|な|に|ね|の|も|は|ば|へ|や|わ|を|か|が|さ|し|ぞ|て)";
|
||||
static string keywords = @"(\ |[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;
|
||||
bool isCJK = false;
|
||||
var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (words.Length <= 1 && UserSettings.Default.AssetLanguage is ELanguage.Japanese or ELanguage.Korean or ELanguage.Chinese or ELanguage.TraditionalChinese)
|
||||
{
|
||||
words = SplitCKJText(line);
|
||||
isCJK = true;
|
||||
}
|
||||
|
||||
var lineResult = new StringBuilder();
|
||||
foreach (var word in words)
|
||||
{
|
||||
var wordWidth = paint.MeasureText(word);
|
||||
var wordWithSpaceWidth = wordWidth + spaceWidth;
|
||||
var wordWithSpace = isCJK ? word : word + " ";
|
||||
|
||||
if (width + wordWidth > maxWidth)
|
||||
{
|
||||
ret.Add(lineResult.ToString());
|
||||
lineResult = new StringBuilder(wordWithSpace);
|
||||
width = wordWithSpaceWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
lineResult.Append(wordWithSpace);
|
||||
width += wordWithSpaceWidth;
|
||||
}
|
||||
}
|
||||
|
||||
ret.Add(lineResult.ToString());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
|
||||
{
|
||||
if (material == null) return null;
|
||||
foreach (var textureParameter in material.TextureParameterValues)
|
||||
{
|
||||
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture)) continue;
|
||||
switch (textureParameter.ParameterInfo.Name.Text)
|
||||
{
|
||||
case "MainTex":
|
||||
case "TextureA":
|
||||
case "TextureB":
|
||||
case "OfferImage":
|
||||
case "KeyArtTexture":
|
||||
case "NPC-Portrait":
|
||||
{
|
||||
return GetBitmap(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) { Position = 0 });
|
||||
public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text);
|
||||
public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null;
|
||||
public static SKBitmap GetBitmap(UTexture2D texture) => texture.IsVirtual ? null : texture.Decode(UserSettings.Default.OverridedPlatform);
|
||||
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
|
||||
|
||||
public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height)
|
||||
{
|
||||
var ratioX = width / me.Width;
|
||||
var ratioY = height / me.Height;
|
||||
var ratio = ratioX < ratioY ? ratioX : ratioY;
|
||||
return me.Resize(Convert.ToInt32(me.Width * ratio), Convert.ToInt32(me.Height * ratio));
|
||||
}
|
||||
|
||||
public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size);
|
||||
|
||||
public static SKBitmap Resize(this SKBitmap me, int width, int height)
|
||||
{
|
||||
var bmp = new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels);
|
||||
using var pixmap = bmp.PeekPixels();
|
||||
me.ScalePixels(pixmap, SKFilterQuality.Medium);
|
||||
return bmp;
|
||||
}
|
||||
|
||||
public static bool TryGetPackageIndexExport<T>(FPackageIndex packageIndex, out T export) where T : UObject
|
||||
{
|
||||
return packageIndex.TryLoad(out export);
|
||||
}
|
||||
|
||||
// fullpath must be either without any extension or with the export objectname
|
||||
public static bool TryLoadObject<T>(string fullPath, out T export) where T : UObject
|
||||
{
|
||||
return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export);
|
||||
}
|
||||
|
||||
public static IEnumerable<UObject> LoadExports(string fullPath)
|
||||
{
|
||||
return _applicationView.CUE4Parse.Provider.LoadObjectExports(fullPath);
|
||||
}
|
||||
|
||||
public static float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f)
|
||||
{
|
||||
var max = maxFont;
|
||||
var min = 0f;
|
||||
var last = -1f;
|
||||
float value;
|
||||
while (true)
|
||||
{
|
||||
value = min + ((max - min) / 2);
|
||||
using (SKFont ft = new SKFont(typeface, value))
|
||||
using (SKPaint paint = new SKPaint(ft))
|
||||
{
|
||||
if (paint.MeasureText(text) > sectorSize)
|
||||
{
|
||||
last = value;
|
||||
max = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
min = value;
|
||||
if (Math.Abs(last - value) <= degreeOfCertainty)
|
||||
return last;
|
||||
|
||||
last = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLocalizedResource(string @namespace, string key, string defaultValue)
|
||||
{
|
||||
return _applicationView.CUE4Parse.Provider.GetLocalizedString(@namespace, key, defaultValue);
|
||||
}
|
||||
|
||||
public static string GetFullPath(string partialPath)
|
||||
{
|
||||
var regex = new Regex(partialPath, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
foreach (var path in _applicationView.CUE4Parse.Provider.Files.Keys)
|
||||
{
|
||||
if (regex.IsMatch(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static void DrawCenteredMultilineText(SKCanvas c, string text, int maxCount, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint)
|
||||
{
|
||||
var lineHeight = paint.TextSize * 1.2f;
|
||||
var lines = SplitLines(text, paint, area.Width - margin);
|
||||
|
||||
#if DEBUG
|
||||
c.DrawRect(new SKRect(area.Left, area.Top, area.Right, area.Bottom), new SKPaint { Color = SKColors.Red, IsStroke = true });
|
||||
#endif
|
||||
|
||||
if (lines == null) return;
|
||||
if (lines.Count <= maxCount) maxCount = lines.Count;
|
||||
var height = maxCount * lineHeight;
|
||||
var y = area.MidY - height / 2;
|
||||
|
||||
var shaper = new CustomSKShaper(paint.Typeface);
|
||||
for (var i = 0; i < maxCount; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (line == null) continue;
|
||||
|
||||
var lineText = line.Trim();
|
||||
var shapedText = shaper.Shape(lineText, paint);
|
||||
|
||||
y += lineHeight;
|
||||
var x = side switch
|
||||
{
|
||||
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
|
||||
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
|
||||
SKTextAlign.Left => margin,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, lineText, x, y, paint);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawMultilineText(SKCanvas c, string text, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint, out float yPos)
|
||||
{
|
||||
yPos = area.Top;
|
||||
var lineHeight = paint.TextSize * 1.2f;
|
||||
var lines = SplitLines(text, paint, area.Width);
|
||||
if (lines == null) return;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var fontSize = GetMaxFontSize(area.Width, paint.Typeface, line);
|
||||
if (paint.TextSize > fontSize) // if the text is not fitting in the line decrease the font size (CKJ languages)
|
||||
{
|
||||
paint.TextSize = fontSize;
|
||||
lineHeight = paint.TextSize * 1.2f;
|
||||
}
|
||||
|
||||
if (line == null) continue;
|
||||
var lineText = line.Trim();
|
||||
var shaper = new CustomSKShaper(paint.Typeface);
|
||||
var shapedText = shaper.Shape(lineText, paint);
|
||||
|
||||
var x = side switch
|
||||
{
|
||||
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
|
||||
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
|
||||
SKTextAlign.Left => area.Left,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, lineText, x, yPos, paint);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
c.DrawRect(new SKRect(area.Left, area.Top - paint.TextSize, area.Right, yPos), new SKPaint { Color = SKColors.Red, IsStroke = true });
|
||||
#endif
|
||||
}
|
||||
|
||||
#region Chinese, Korean and Japanese text split
|
||||
|
||||
// https://github.com/YoungjaeKim/mikan.sharp/blob/master/MikanSharp/Mikan/Mikan.cs
|
||||
|
||||
static string joshi = @"(でなければ|について|かしら|くらい|けれど|なのか|ばかり|ながら|ことよ|こそ|こと|さえ|しか|した|たり|だけ|だに|だの|つつ|ても|てよ|でも|とも|から|など|なり|ので|のに|ほど|まで|もの|やら|より|って|で|と|な|に|ね|の|も|は|ば|へ|や|わ|を|か|が|さ|し|ぞ|て)";
|
||||
static string keywords = @"(\ |[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;
|
||||
}
|
||||
}
|
||||
275
FModel/Enums.cs
275
FModel/Enums.cs
|
|
@ -1,142 +1,141 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace FModel
|
||||
namespace FModel;
|
||||
|
||||
public enum EBuildKind
|
||||
{
|
||||
public enum EBuildKind
|
||||
{
|
||||
Debug,
|
||||
Release,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public enum EErrorKind
|
||||
{
|
||||
Ignore,
|
||||
Restart,
|
||||
ResetSettings
|
||||
}
|
||||
|
||||
public enum SettingsOut
|
||||
{
|
||||
Restart,
|
||||
ReloadLocres,
|
||||
CheckForUpdates,
|
||||
Nothing
|
||||
}
|
||||
|
||||
public enum EStatusKind
|
||||
{
|
||||
Ready, // ready
|
||||
Loading, // doing stuff
|
||||
Stopping, // trying to stop
|
||||
Stopped, // stopped
|
||||
Failed, // crashed
|
||||
Completed // worked
|
||||
}
|
||||
|
||||
public enum EAesReload
|
||||
{
|
||||
[Description("Always")]
|
||||
Always,
|
||||
[Description("Never")]
|
||||
Never,
|
||||
[Description("Once Per Day")]
|
||||
OncePerDay
|
||||
}
|
||||
|
||||
public enum EDiscordRpc
|
||||
{
|
||||
[Description("Always")]
|
||||
Always,
|
||||
[Description("Never")]
|
||||
Never
|
||||
}
|
||||
|
||||
public enum FGame
|
||||
{
|
||||
[Description("Unknown")]
|
||||
Unknown,
|
||||
[Description("Fortnite")]
|
||||
FortniteGame,
|
||||
[Description("Valorant")]
|
||||
ShooterGame,
|
||||
[Description("Dead By Daylight")]
|
||||
DeadByDaylight,
|
||||
[Description("Borderlands 3")]
|
||||
OakGame,
|
||||
[Description("Minecraft Dungeons")]
|
||||
Dungeons,
|
||||
[Description("Battle Breakers")]
|
||||
WorldExplorers,
|
||||
[Description("Spellbreak")]
|
||||
g3,
|
||||
[Description("State Of Decay 2")]
|
||||
StateOfDecay2,
|
||||
[Description("The Cycle")]
|
||||
Prospect,
|
||||
[Description("The Outer Worlds")]
|
||||
Indiana,
|
||||
[Description("Rogue Company")]
|
||||
RogueCompany,
|
||||
[Description("Star Wars: Jedi Fallen Order")]
|
||||
SwGame,
|
||||
[Description("Core")]
|
||||
Platform,
|
||||
[Description("Days Gone")]
|
||||
BendGame,
|
||||
[Description("PLAYERUNKNOWN'S BATTLEGROUNDS")]
|
||||
TslGame,
|
||||
[Description("Splitgate")]
|
||||
PortalWars,
|
||||
[Description("GTA: The Trilogy - Definitive Edition")]
|
||||
Gameface,
|
||||
[Description("Sea of Thieves")]
|
||||
Athena
|
||||
}
|
||||
|
||||
public enum ELoadingMode
|
||||
{
|
||||
[Description("Single")]
|
||||
Single,
|
||||
[Description("Multiple")]
|
||||
Multiple,
|
||||
[Description("All")]
|
||||
All,
|
||||
[Description("All (New)")]
|
||||
AllButNew,
|
||||
[Description("All (Modified)")]
|
||||
AllButModified
|
||||
}
|
||||
|
||||
public enum EUpdateMode
|
||||
{
|
||||
[Description("Stable")]
|
||||
Stable,
|
||||
[Description("Beta")]
|
||||
Beta
|
||||
}
|
||||
|
||||
public enum ECompressedAudio
|
||||
{
|
||||
[Description("Play the decompressed data")]
|
||||
PlayDecompressed,
|
||||
[Description("Play the compressed data (might not always be a valid audio data)")]
|
||||
PlayCompressed
|
||||
}
|
||||
|
||||
public enum EIconStyle
|
||||
{
|
||||
[Description("Default")]
|
||||
Default,
|
||||
[Description("No Background")]
|
||||
NoBackground,
|
||||
[Description("No Text")]
|
||||
NoText,
|
||||
[Description("Flat")]
|
||||
Flat,
|
||||
[Description("Cataba")]
|
||||
Cataba,
|
||||
// [Description("Community")]
|
||||
// CommunityMade
|
||||
}
|
||||
Debug,
|
||||
Release,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public enum EErrorKind
|
||||
{
|
||||
Ignore,
|
||||
Restart,
|
||||
ResetSettings
|
||||
}
|
||||
|
||||
public enum SettingsOut
|
||||
{
|
||||
Restart,
|
||||
ReloadLocres,
|
||||
CheckForUpdates,
|
||||
Nothing
|
||||
}
|
||||
|
||||
public enum EStatusKind
|
||||
{
|
||||
Ready, // ready
|
||||
Loading, // doing stuff
|
||||
Stopping, // trying to stop
|
||||
Stopped, // stopped
|
||||
Failed, // crashed
|
||||
Completed // worked
|
||||
}
|
||||
|
||||
public enum EAesReload
|
||||
{
|
||||
[Description("Always")]
|
||||
Always,
|
||||
[Description("Never")]
|
||||
Never,
|
||||
[Description("Once Per Day")]
|
||||
OncePerDay
|
||||
}
|
||||
|
||||
public enum EDiscordRpc
|
||||
{
|
||||
[Description("Always")]
|
||||
Always,
|
||||
[Description("Never")]
|
||||
Never
|
||||
}
|
||||
|
||||
public enum FGame
|
||||
{
|
||||
[Description("Unknown")]
|
||||
Unknown,
|
||||
[Description("Fortnite")]
|
||||
FortniteGame,
|
||||
[Description("Valorant")]
|
||||
ShooterGame,
|
||||
[Description("Dead By Daylight")]
|
||||
DeadByDaylight,
|
||||
[Description("Borderlands 3")]
|
||||
OakGame,
|
||||
[Description("Minecraft Dungeons")]
|
||||
Dungeons,
|
||||
[Description("Battle Breakers")]
|
||||
WorldExplorers,
|
||||
[Description("Spellbreak")]
|
||||
g3,
|
||||
[Description("State Of Decay 2")]
|
||||
StateOfDecay2,
|
||||
[Description("The Cycle")]
|
||||
Prospect,
|
||||
[Description("The Outer Worlds")]
|
||||
Indiana,
|
||||
[Description("Rogue Company")]
|
||||
RogueCompany,
|
||||
[Description("Star Wars: Jedi Fallen Order")]
|
||||
SwGame,
|
||||
[Description("Core")]
|
||||
Platform,
|
||||
[Description("Days Gone")]
|
||||
BendGame,
|
||||
[Description("PLAYERUNKNOWN'S BATTLEGROUNDS")]
|
||||
TslGame,
|
||||
[Description("Splitgate")]
|
||||
PortalWars,
|
||||
[Description("GTA: The Trilogy - Definitive Edition")]
|
||||
Gameface,
|
||||
[Description("Sea of Thieves")]
|
||||
Athena
|
||||
}
|
||||
|
||||
public enum ELoadingMode
|
||||
{
|
||||
[Description("Single")]
|
||||
Single,
|
||||
[Description("Multiple")]
|
||||
Multiple,
|
||||
[Description("All")]
|
||||
All,
|
||||
[Description("All (New)")]
|
||||
AllButNew,
|
||||
[Description("All (Modified)")]
|
||||
AllButModified
|
||||
}
|
||||
|
||||
public enum EUpdateMode
|
||||
{
|
||||
[Description("Stable")]
|
||||
Stable,
|
||||
[Description("Beta")]
|
||||
Beta
|
||||
}
|
||||
|
||||
public enum ECompressedAudio
|
||||
{
|
||||
[Description("Play the decompressed data")]
|
||||
PlayDecompressed,
|
||||
[Description("Play the compressed data (might not always be a valid audio data)")]
|
||||
PlayCompressed
|
||||
}
|
||||
|
||||
public enum EIconStyle
|
||||
{
|
||||
[Description("Default")]
|
||||
Default,
|
||||
[Description("No Background")]
|
||||
NoBackground,
|
||||
[Description("No Text")]
|
||||
NoText,
|
||||
[Description("Flat")]
|
||||
Flat,
|
||||
[Description("Cataba")]
|
||||
Cataba,
|
||||
// [Description("Community")]
|
||||
// CommunityMade
|
||||
}
|
||||
|
|
@ -4,47 +4,46 @@ using System.Xml;
|
|||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
|
||||
|
||||
namespace FModel.Extensions
|
||||
namespace FModel.Extensions;
|
||||
|
||||
public static class AvalonExtensions
|
||||
{
|
||||
public static class AvalonExtensions
|
||||
private static readonly IHighlightingDefinition _jsonHighlighter = LoadHighlighter("Json.xshd");
|
||||
private static readonly IHighlightingDefinition _iniHighlighter = LoadHighlighter("Ini.xshd");
|
||||
private static readonly IHighlightingDefinition _xmlHighlighter = LoadHighlighter("Xml.xshd");
|
||||
private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd");
|
||||
private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd");
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static IHighlightingDefinition LoadHighlighter(string resourceName)
|
||||
{
|
||||
private static readonly IHighlightingDefinition _jsonHighlighter = LoadHighlighter("Json.xshd");
|
||||
private static readonly IHighlightingDefinition _iniHighlighter = LoadHighlighter("Ini.xshd");
|
||||
private static readonly IHighlightingDefinition _xmlHighlighter = LoadHighlighter("Xml.xshd");
|
||||
private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd");
|
||||
private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd");
|
||||
var executingAssembly = Assembly.GetExecutingAssembly();
|
||||
using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{resourceName}");
|
||||
using var reader = new XmlTextReader(stream);
|
||||
return HighlightingLoader.Load(reader, HighlightingManager.Instance);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static IHighlightingDefinition LoadHighlighter(string resourceName)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IHighlightingDefinition HighlighterSelector(string ext)
|
||||
{
|
||||
switch (ext)
|
||||
{
|
||||
var executingAssembly = Assembly.GetExecutingAssembly();
|
||||
using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{resourceName}");
|
||||
using var reader = new XmlTextReader(stream);
|
||||
return HighlightingLoader.Load(reader, HighlightingManager.Instance);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IHighlightingDefinition HighlighterSelector(string ext)
|
||||
{
|
||||
switch (ext)
|
||||
{
|
||||
case "ini":
|
||||
case "csv":
|
||||
return _iniHighlighter;
|
||||
case "xml":
|
||||
return _xmlHighlighter;
|
||||
case "h":
|
||||
case "cpp":
|
||||
return _cppHighlighter;
|
||||
case "changelog":
|
||||
return _changelogHighlighter;
|
||||
case "bat":
|
||||
case "txt":
|
||||
case "po":
|
||||
return null;
|
||||
default:
|
||||
return _jsonHighlighter;
|
||||
}
|
||||
case "ini":
|
||||
case "csv":
|
||||
return _iniHighlighter;
|
||||
case "xml":
|
||||
return _xmlHighlighter;
|
||||
case "h":
|
||||
case "cpp":
|
||||
return _cppHighlighter;
|
||||
case "changelog":
|
||||
return _changelogHighlighter;
|
||||
case "bat":
|
||||
case "txt":
|
||||
case "po":
|
||||
return null;
|
||||
default:
|
||||
return _jsonHighlighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon>FModel.ico</ApplicationIcon>
|
||||
<Version>4.2.2</Version>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,40 +4,39 @@ using Newtonsoft.Json.Serialization;
|
|||
using RestSharp;
|
||||
using RestSharp.Serialization;
|
||||
|
||||
namespace FModel.Framework
|
||||
namespace FModel.Framework;
|
||||
|
||||
public class JsonNetSerializer : IRestSerializer
|
||||
{
|
||||
public class JsonNetSerializer : IRestSerializer
|
||||
public static readonly JsonSerializerSettings SerializerSettings = new()
|
||||
{
|
||||
public static readonly JsonSerializerSettings SerializerSettings = new()
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
|
||||
public string Serialize(object obj)
|
||||
{
|
||||
return JsonConvert.SerializeObject(obj);
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public string Serialize(Parameter parameter)
|
||||
{
|
||||
return JsonConvert.SerializeObject(parameter.Value);
|
||||
}
|
||||
|
||||
public T Deserialize<T>(IRestResponse response)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(response.Content, SerializerSettings);
|
||||
}
|
||||
|
||||
public string[] SupportedContentTypes { get; } =
|
||||
{
|
||||
"application/json", "application/json; charset=UTF-8"
|
||||
};
|
||||
|
||||
public string ContentType { get; set; } = "application/json; charset=UTF-8";
|
||||
|
||||
public DataFormat DataFormat => DataFormat.Json;
|
||||
public string Serialize(object obj)
|
||||
{
|
||||
return JsonConvert.SerializeObject(obj);
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public string Serialize(Parameter parameter)
|
||||
{
|
||||
return JsonConvert.SerializeObject(parameter.Value);
|
||||
}
|
||||
|
||||
public T Deserialize<T>(IRestResponse response)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(response.Content, SerializerSettings);
|
||||
}
|
||||
|
||||
public string[] SupportedContentTypes { get; } =
|
||||
{
|
||||
"application/json", "application/json; charset=UTF-8"
|
||||
};
|
||||
|
||||
public string ContentType { get; set; } = "application/json; charset=UTF-8";
|
||||
|
||||
public DataFormat DataFormat => DataFormat.Json;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -10,144 +10,143 @@ using FModel.Settings;
|
|||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace FModel.ViewModels
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class AesManagerViewModel : ViewModel
|
||||
{
|
||||
public class AesManagerViewModel : ViewModel
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
|
||||
public FullyObservableCollection<FileItem> AesKeys { get; private set; } // holds all aes keys even the main one
|
||||
public ICollectionView AesKeysView { get; private set; } // holds all aes key ordered by name for the ui
|
||||
public bool HasChange { get; set; }
|
||||
|
||||
private AesResponse _keysFromSettings;
|
||||
private HashSet<FGuid> _uniqueGuids;
|
||||
private readonly CUE4ParseViewModel _cue4Parse;
|
||||
private readonly FileItem _mainKey = new("Main Static Key", 0) { Guid = Constants.ZERO_GUID }; // just so main key gets refreshed in the ui
|
||||
|
||||
public AesManagerViewModel(CUE4ParseViewModel cue4Parse)
|
||||
{
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
_cue4Parse = cue4Parse;
|
||||
HasChange = false;
|
||||
}
|
||||
|
||||
public FullyObservableCollection<FileItem> AesKeys { get; private set; } // holds all aes keys even the main one
|
||||
public ICollectionView AesKeysView { get; private set; } // holds all aes key ordered by name for the ui
|
||||
public bool HasChange { get; set; }
|
||||
|
||||
private AesResponse _keysFromSettings;
|
||||
private HashSet<FGuid> _uniqueGuids;
|
||||
private readonly CUE4ParseViewModel _cue4Parse;
|
||||
private readonly FileItem _mainKey = new("Main Static Key", 0) {Guid = Constants.ZERO_GUID}; // just so main key gets refreshed in the ui
|
||||
|
||||
public AesManagerViewModel(CUE4ParseViewModel cue4Parse)
|
||||
public async Task InitAes()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_cue4Parse = cue4Parse;
|
||||
HasChange = false;
|
||||
}
|
||||
|
||||
public async Task InitAes()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
if (_cue4Parse.Game == FGame.Unknown &&
|
||||
UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings))
|
||||
{
|
||||
if (_cue4Parse.Game == FGame.Unknown &&
|
||||
UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings))
|
||||
{
|
||||
_keysFromSettings = settings.AesKeys;
|
||||
}
|
||||
else
|
||||
{
|
||||
_keysFromSettings = UserSettings.Default.AesKeys[_cue4Parse.Game];
|
||||
}
|
||||
|
||||
_keysFromSettings ??= new AesResponse
|
||||
{
|
||||
MainKey = string.Empty,
|
||||
DynamicKeys = null
|
||||
};
|
||||
|
||||
_mainKey.Key = FixKey(_keysFromSettings.MainKey);
|
||||
AesKeys = new FullyObservableCollection<FileItem>(EnumerateAesKeys());
|
||||
AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged;
|
||||
AesKeysView = new ListCollectionView(AesKeys) {SortDescriptions = {new SortDescription("Name", ListSortDirection.Ascending)}};
|
||||
});
|
||||
}
|
||||
|
||||
private void AesKeysOnItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != "Key" || sender is not FullyObservableCollection<FileItem> collection)
|
||||
return;
|
||||
|
||||
var key = FixKey(collection[e.CollectionIndex].Key);
|
||||
if (e.CollectionIndex == 0)
|
||||
{
|
||||
if (!HasChange)
|
||||
HasChange = FixKey(_keysFromSettings.MainKey) != key;
|
||||
|
||||
_keysFromSettings.MainKey = key;
|
||||
}
|
||||
else if (!_keysFromSettings.HasDynamicKeys)
|
||||
{
|
||||
HasChange = true;
|
||||
_keysFromSettings.DynamicKeys = new List<DynamicKey>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Key = key,
|
||||
FileName = collection[e.CollectionIndex].Name,
|
||||
Guid = collection[e.CollectionIndex].Guid.ToString()
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (_keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == collection[e.CollectionIndex].Guid.ToString()) is { } d)
|
||||
{
|
||||
if (!HasChange)
|
||||
HasChange = FixKey(d.Key) != key;
|
||||
|
||||
d.Key = key;
|
||||
_keysFromSettings = settings.AesKeys;
|
||||
}
|
||||
else
|
||||
{
|
||||
HasChange = true;
|
||||
_keysFromSettings.DynamicKeys.Add(new DynamicKey
|
||||
_keysFromSettings = UserSettings.Default.AesKeys[_cue4Parse.Game];
|
||||
}
|
||||
|
||||
_keysFromSettings ??= new AesResponse
|
||||
{
|
||||
MainKey = string.Empty,
|
||||
DynamicKeys = null
|
||||
};
|
||||
|
||||
_mainKey.Key = FixKey(_keysFromSettings.MainKey);
|
||||
AesKeys = new FullyObservableCollection<FileItem>(EnumerateAesKeys());
|
||||
AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged;
|
||||
AesKeysView = new ListCollectionView(AesKeys) { SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending) } };
|
||||
});
|
||||
}
|
||||
|
||||
private void AesKeysOnItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != "Key" || sender is not FullyObservableCollection<FileItem> collection)
|
||||
return;
|
||||
|
||||
var key = FixKey(collection[e.CollectionIndex].Key);
|
||||
if (e.CollectionIndex == 0)
|
||||
{
|
||||
if (!HasChange)
|
||||
HasChange = FixKey(_keysFromSettings.MainKey) != key;
|
||||
|
||||
_keysFromSettings.MainKey = key;
|
||||
}
|
||||
else if (!_keysFromSettings.HasDynamicKeys)
|
||||
{
|
||||
HasChange = true;
|
||||
_keysFromSettings.DynamicKeys = new List<DynamicKey>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Key = key,
|
||||
FileName = collection[e.CollectionIndex].Name,
|
||||
Guid = collection[e.CollectionIndex].Guid.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateProvider(bool isLaunch)
|
||||
{
|
||||
if (!isLaunch && !HasChange) return;
|
||||
|
||||
_cue4Parse.ClearProvider();
|
||||
await _cue4Parse.LoadVfs(AesKeys);
|
||||
|
||||
if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings;
|
||||
else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings;
|
||||
|
||||
Log.Information("{@Json}", UserSettings.Default);
|
||||
}
|
||||
|
||||
private string FixKey(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
return string.Empty;
|
||||
|
||||
if (key.StartsWith("0x"))
|
||||
key = key[2..];
|
||||
|
||||
return "0x" + key.ToUpper().Trim();
|
||||
}
|
||||
|
||||
private IEnumerable<FileItem> EnumerateAesKeys()
|
||||
{
|
||||
yield return _mainKey;
|
||||
_uniqueGuids = new HashSet<FGuid> {Constants.ZERO_GUID};
|
||||
|
||||
var hasDynamicKeys = _keysFromSettings.HasDynamicKeys;
|
||||
foreach (var file in _cue4Parse.GameDirectory.DirectoryFiles)
|
||||
{
|
||||
if (file.Guid == Constants.ZERO_GUID || !_uniqueGuids.Add(file.Guid))
|
||||
continue;
|
||||
|
||||
var k = string.Empty;
|
||||
if (hasDynamicKeys && _keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == file.Guid.ToString()) is { } dynamicKey)
|
||||
{
|
||||
k = dynamicKey.Key;
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (_keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == collection[e.CollectionIndex].Guid.ToString()) is { } d)
|
||||
{
|
||||
if (!HasChange)
|
||||
HasChange = FixKey(d.Key) != key;
|
||||
|
||||
file.Key = FixKey(k);
|
||||
yield return file;
|
||||
}
|
||||
d.Key = key;
|
||||
}
|
||||
else
|
||||
{
|
||||
HasChange = true;
|
||||
_keysFromSettings.DynamicKeys.Add(new DynamicKey
|
||||
{
|
||||
Key = key,
|
||||
FileName = collection[e.CollectionIndex].Name,
|
||||
Guid = collection[e.CollectionIndex].Guid.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateProvider(bool isLaunch)
|
||||
{
|
||||
if (!isLaunch && !HasChange) return;
|
||||
|
||||
_cue4Parse.ClearProvider();
|
||||
await _cue4Parse.LoadVfs(AesKeys);
|
||||
|
||||
if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings;
|
||||
else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings;
|
||||
|
||||
Log.Information("{@Json}", UserSettings.Default);
|
||||
}
|
||||
|
||||
private string FixKey(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
return string.Empty;
|
||||
|
||||
if (key.StartsWith("0x"))
|
||||
key = key[2..];
|
||||
|
||||
return "0x" + key.ToUpper().Trim();
|
||||
}
|
||||
|
||||
private IEnumerable<FileItem> EnumerateAesKeys()
|
||||
{
|
||||
yield return _mainKey;
|
||||
_uniqueGuids = new HashSet<FGuid> { Constants.ZERO_GUID };
|
||||
|
||||
var hasDynamicKeys = _keysFromSettings.HasDynamicKeys;
|
||||
foreach (var file in _cue4Parse.GameDirectory.DirectoryFiles)
|
||||
{
|
||||
if (file.Guid == Constants.ZERO_GUID || !_uniqueGuids.Add(file.Guid))
|
||||
continue;
|
||||
|
||||
var k = string.Empty;
|
||||
if (hasDynamicKeys && _keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == file.Guid.ToString()) is { } dynamicKey)
|
||||
{
|
||||
k = dynamicKey.Key;
|
||||
}
|
||||
|
||||
file.Key = FixKey(k);
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,29 +2,28 @@
|
|||
using FModel.ViewModels.ApiEndpoints;
|
||||
using RestSharp;
|
||||
|
||||
namespace FModel.ViewModels
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class ApiEndpointViewModel
|
||||
{
|
||||
public class ApiEndpointViewModel
|
||||
private readonly IRestClient _client = new RestClient
|
||||
{
|
||||
private readonly IRestClient _client = new RestClient
|
||||
{
|
||||
UserAgent = $"FModel/{Constants.APP_VERSION}",
|
||||
Timeout = 3 * 1000
|
||||
}.UseSerializer<JsonNetSerializer>();
|
||||
UserAgent = $"FModel/{Constants.APP_VERSION}",
|
||||
Timeout = 3 * 1000
|
||||
}.UseSerializer<JsonNetSerializer>();
|
||||
|
||||
public FortniteApiEndpoint FortniteApi { get; }
|
||||
public ValorantApiEndpoint ValorantApi { get; }
|
||||
public BenbotApiEndpoint BenbotApi { get; }
|
||||
public EpicApiEndpoint EpicApi { get; }
|
||||
public FModelApi FModelApi { get; }
|
||||
public FortniteApiEndpoint FortniteApi { get; }
|
||||
public ValorantApiEndpoint ValorantApi { get; }
|
||||
public BenbotApiEndpoint BenbotApi { get; }
|
||||
public EpicApiEndpoint EpicApi { get; }
|
||||
public FModelApi FModelApi { get; }
|
||||
|
||||
public ApiEndpointViewModel()
|
||||
{
|
||||
FortniteApi = new FortniteApiEndpoint(_client);
|
||||
ValorantApi = new ValorantApiEndpoint(_client);
|
||||
BenbotApi = new BenbotApiEndpoint(_client);
|
||||
EpicApi = new EpicApiEndpoint(_client);
|
||||
FModelApi = new FModelApi(_client);
|
||||
}
|
||||
public ApiEndpointViewModel()
|
||||
{
|
||||
FortniteApi = new FortniteApiEndpoint(_client);
|
||||
ValorantApi = new ValorantApiEndpoint(_client);
|
||||
BenbotApi = new BenbotApiEndpoint(_client);
|
||||
EpicApi = new EpicApiEndpoint(_client);
|
||||
FModelApi = new FModelApi(_client);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
using RestSharp;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints
|
||||
{
|
||||
public abstract class AbstractApiProvider
|
||||
{
|
||||
protected readonly IRestClient _client;
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
||||
public AbstractApiProvider(IRestClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
public abstract class AbstractApiProvider
|
||||
{
|
||||
protected readonly IRestClient _client;
|
||||
|
||||
protected AbstractApiProvider(IRestClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,73 +6,72 @@ using FModel.ViewModels.ApiEndpoints.Models;
|
|||
using RestSharp;
|
||||
using Serilog;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
||||
public class BenbotApiEndpoint : AbstractApiProvider
|
||||
{
|
||||
public class BenbotApiEndpoint : AbstractApiProvider
|
||||
public BenbotApiEndpoint(IRestClient client) : base(client)
|
||||
{
|
||||
public BenbotApiEndpoint(IRestClient client) : base(client)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AesResponse> GetAesKeysAsync(CancellationToken token)
|
||||
public async Task<AesResponse> GetAesKeysAsync(CancellationToken token)
|
||||
{
|
||||
var request = new RestRequest("https://benbot.app/api/v2/aes", Method.GET)
|
||||
{
|
||||
var request = new RestRequest("https://benbot.app/api/v2/aes", Method.GET)
|
||||
{
|
||||
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
|
||||
};
|
||||
var response = await _client.ExecuteAsync<AesResponse>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
|
||||
};
|
||||
var response = await _client.ExecuteAsync<AesResponse>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public AesResponse GetAesKeys(CancellationToken token)
|
||||
{
|
||||
return GetAesKeysAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
public AesResponse GetAesKeys(CancellationToken token)
|
||||
{
|
||||
return GetAesKeysAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<MappingsResponse[]> GetMappingsAsync(CancellationToken token)
|
||||
public async Task<MappingsResponse[]> GetMappingsAsync(CancellationToken token)
|
||||
{
|
||||
var request = new RestRequest("https://benbot.app/api/v1/mappings", Method.GET)
|
||||
{
|
||||
var request = new RestRequest("https://benbot.app/api/v1/mappings", Method.GET)
|
||||
{
|
||||
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
|
||||
};
|
||||
var response = await _client.ExecuteAsync<MappingsResponse[]>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
|
||||
};
|
||||
var response = await _client.ExecuteAsync<MappingsResponse[]>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public MappingsResponse[] GetMappings(CancellationToken token)
|
||||
{
|
||||
return GetMappingsAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
public MappingsResponse[] GetMappings(CancellationToken token)
|
||||
{
|
||||
return GetMappingsAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, Dictionary<string, string>>> GetHotfixesAsync(CancellationToken token, string language = "en-US")
|
||||
public async Task<Dictionary<string, Dictionary<string, string>>> GetHotfixesAsync(CancellationToken token, string language = "en-US")
|
||||
{
|
||||
var request = new RestRequest("https://benbot.app/api/v1/hotfixes", Method.GET)
|
||||
{
|
||||
var request = new RestRequest("https://benbot.app/api/v1/hotfixes", Method.GET)
|
||||
{
|
||||
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
|
||||
};
|
||||
request.AddParameter("lang", language);
|
||||
var response = await _client.ExecuteAsync<Dictionary<string, Dictionary<string, string>>>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int)response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
|
||||
};
|
||||
request.AddParameter("lang", language);
|
||||
var response = await _client.ExecuteAsync<Dictionary<string, Dictionary<string, string>>>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public Dictionary<string, Dictionary<string, string>> GetHotfixes(CancellationToken token, string language = "en-US")
|
||||
{
|
||||
return GetHotfixesAsync(token, language).GetAwaiter().GetResult();
|
||||
}
|
||||
public Dictionary<string, Dictionary<string, string>> GetHotfixes(CancellationToken token, string language = "en-US")
|
||||
{
|
||||
return GetHotfixesAsync(token, language).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task DownloadFileAsync(string fileLink, string installationPath)
|
||||
{
|
||||
var request = new RestRequest(fileLink, Method.GET);
|
||||
var data = _client.DownloadData(request);
|
||||
await File.WriteAllBytesAsync(installationPath, data);
|
||||
}
|
||||
public async Task DownloadFileAsync(string fileLink, string installationPath)
|
||||
{
|
||||
var request = new RestRequest(fileLink, Method.GET);
|
||||
var data = _client.DownloadData(request);
|
||||
await File.WriteAllBytesAsync(installationPath, data);
|
||||
}
|
||||
|
||||
public void DownloadFile(string fileLink, string installationPath)
|
||||
{
|
||||
DownloadFileAsync(fileLink, installationPath).GetAwaiter().GetResult();
|
||||
}
|
||||
public void DownloadFile(string fileLink, string installationPath)
|
||||
{
|
||||
DownloadFileAsync(fileLink, installationPath).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,55 +7,54 @@ using FModel.ViewModels.ApiEndpoints.Models;
|
|||
using RestSharp;
|
||||
using Serilog;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
||||
public class EpicApiEndpoint : AbstractApiProvider
|
||||
{
|
||||
public class EpicApiEndpoint : AbstractApiProvider
|
||||
private const string _OAUTH_URL = "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token";
|
||||
private const string _BASIC_TOKEN = "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE=";
|
||||
private const string _LAUNCHER_ASSETS = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live";
|
||||
|
||||
public EpicApiEndpoint(IRestClient client) : base(client)
|
||||
{
|
||||
private const string _OAUTH_URL = "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token";
|
||||
private const string _BASIC_TOKEN = "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE=";
|
||||
private const string _LAUNCHER_ASSETS = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live";
|
||||
}
|
||||
|
||||
public EpicApiEndpoint(IRestClient client) : base(client)
|
||||
public async Task<ManifestInfo> GetManifestAsync(CancellationToken token)
|
||||
{
|
||||
if (IsExpired())
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<ManifestInfo> GetManifestAsync(CancellationToken token)
|
||||
{
|
||||
if (IsExpired())
|
||||
var auth = await GetAuthAsync(token);
|
||||
if (auth != null)
|
||||
{
|
||||
var auth = await GetAuthAsync(token);
|
||||
if (auth != null)
|
||||
{
|
||||
UserSettings.Default.LastAuthResponse = auth;
|
||||
}
|
||||
UserSettings.Default.LastAuthResponse = auth;
|
||||
}
|
||||
|
||||
var request = new RestRequest(_LAUNCHER_ASSETS, Method.GET);
|
||||
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
|
||||
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return new ManifestInfo(response.Content);
|
||||
}
|
||||
|
||||
public ManifestInfo GetManifest(CancellationToken token)
|
||||
{
|
||||
return GetManifestAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
var request = new RestRequest(_LAUNCHER_ASSETS, Method.GET);
|
||||
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
|
||||
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return new ManifestInfo(response.Content);
|
||||
}
|
||||
|
||||
private async Task<AuthResponse> GetAuthAsync(CancellationToken token)
|
||||
{
|
||||
var request = new RestRequest(_OAUTH_URL, Method.POST);
|
||||
request.AddHeader("Authorization", _BASIC_TOKEN);
|
||||
request.AddParameter("grant_type", "client_credentials");
|
||||
var response = await _client.ExecuteAsync<AuthResponse>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
public ManifestInfo GetManifest(CancellationToken token)
|
||||
{
|
||||
return GetManifestAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private bool IsExpired()
|
||||
{
|
||||
if (string.IsNullOrEmpty(UserSettings.Default.LastAuthResponse.AccessToken)) return true;
|
||||
return DateTime.Now.Subtract(TimeSpan.FromHours(1)) >= UserSettings.Default.LastAuthResponse.ExpiresAt;
|
||||
}
|
||||
private async Task<AuthResponse> GetAuthAsync(CancellationToken token)
|
||||
{
|
||||
var request = new RestRequest(_OAUTH_URL, Method.POST);
|
||||
request.AddHeader("Authorization", _BASIC_TOKEN);
|
||||
request.AddParameter("grant_type", "client_credentials");
|
||||
var response = await _client.ExecuteAsync<AuthResponse>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
private bool IsExpired()
|
||||
{
|
||||
if (string.IsNullOrEmpty(UserSettings.Default.LastAuthResponse.AccessToken)) return true;
|
||||
return DateTime.Now.Subtract(TimeSpan.FromHours(1)) >= UserSettings.Default.LastAuthResponse.ExpiresAt;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,169 +17,168 @@ using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
|
|||
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
|
||||
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
||||
public class FModelApi : AbstractApiProvider
|
||||
{
|
||||
public class FModelApi : AbstractApiProvider
|
||||
private News _news;
|
||||
private Info _infos;
|
||||
private Backup[] _backups;
|
||||
private Game _game;
|
||||
private readonly IDictionary<string, CommunityDesign> _communityDesigns = new Dictionary<string, CommunityDesign>();
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
||||
public FModelApi(IRestClient client) : base(client)
|
||||
{
|
||||
private News _news;
|
||||
private Info _infos;
|
||||
private Backup[] _backups;
|
||||
private Game _game;
|
||||
private readonly IDictionary<string, CommunityDesign> _communityDesigns = new Dictionary<string, CommunityDesign>();
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
}
|
||||
|
||||
public FModelApi(IRestClient client) : base(client)
|
||||
{
|
||||
}
|
||||
public async Task<News> GetNewsAsync(CancellationToken token)
|
||||
{
|
||||
var request = new RestRequest($"https://api.fmodel.app/v1/news/{Constants.APP_VERSION}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<News>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public async Task<News> GetNewsAsync(CancellationToken token)
|
||||
{
|
||||
var request = new RestRequest($"https://api.fmodel.app/v1/news/{Constants.APP_VERSION}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<News>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
public News GetNews(CancellationToken token)
|
||||
{
|
||||
return _news ??= GetNewsAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public News GetNews(CancellationToken token)
|
||||
{
|
||||
return _news ??= GetNewsAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
public async Task<Info> GetInfosAsync(CancellationToken token, EUpdateMode updateMode)
|
||||
{
|
||||
var request = new RestRequest($"https://api.fmodel.app/v1/infos/{updateMode}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<Info>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public async Task<Info> GetInfosAsync(CancellationToken token, EUpdateMode updateMode)
|
||||
{
|
||||
var request = new RestRequest($"https://api.fmodel.app/v1/infos/{updateMode}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<Info>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
public Info GetInfos(CancellationToken token, EUpdateMode updateMode)
|
||||
{
|
||||
return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public Info GetInfos(CancellationToken token, EUpdateMode updateMode)
|
||||
{
|
||||
return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult();
|
||||
}
|
||||
public async Task<Backup[]> GetBackupsAsync(CancellationToken token, string gameName)
|
||||
{
|
||||
var request = new RestRequest($"https://api.fmodel.app/v1/backups/{gameName}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<Backup[]>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public async Task<Backup[]> GetBackupsAsync(CancellationToken token, string gameName)
|
||||
{
|
||||
var request = new RestRequest($"https://api.fmodel.app/v1/backups/{gameName}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<Backup[]>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
public Backup[] GetBackups(CancellationToken token, string gameName)
|
||||
{
|
||||
return _backups ??= GetBackupsAsync(token, gameName).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public Backup[] GetBackups(CancellationToken token, string gameName)
|
||||
{
|
||||
return _backups ??= GetBackupsAsync(token, gameName).GetAwaiter().GetResult();
|
||||
}
|
||||
public async Task<Game> GetGamesAsync(CancellationToken token, string gameName)
|
||||
{
|
||||
var request = new RestRequest($"https://api.fmodel.app/v1/games/{gameName}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<Game>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public async Task<Game> GetGamesAsync(CancellationToken token, string gameName)
|
||||
{
|
||||
var request = new RestRequest($"https://api.fmodel.app/v1/games/{gameName}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<Game>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data;
|
||||
}
|
||||
public Game GetGames(CancellationToken token, string gameName)
|
||||
{
|
||||
return _game ??= GetGamesAsync(token, gameName).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public Game GetGames(CancellationToken token, string gameName)
|
||||
{
|
||||
return _game ??= GetGamesAsync(token, gameName).GetAwaiter().GetResult();
|
||||
}
|
||||
public async Task<CommunityDesign> GetDesignAsync(string designName)
|
||||
{
|
||||
var request = new RestRequest($"https://api.fmodel.app/v1/designs/{designName}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<Community>(request).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data != null ? new CommunityDesign(response.Data) : null;
|
||||
}
|
||||
|
||||
public async Task<CommunityDesign> GetDesignAsync(string designName)
|
||||
{
|
||||
var request = new RestRequest($"https://api.fmodel.app/v1/designs/{designName}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<Community>(request).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
|
||||
return response.Data != null ? new CommunityDesign(response.Data) : null;
|
||||
}
|
||||
|
||||
public CommunityDesign GetDesign(string designName)
|
||||
{
|
||||
if (_communityDesigns.TryGetValue(designName, out var communityDesign) && communityDesign != null)
|
||||
return communityDesign;
|
||||
|
||||
communityDesign = GetDesignAsync(designName).GetAwaiter().GetResult();
|
||||
_communityDesigns[designName] = communityDesign;
|
||||
public CommunityDesign GetDesign(string designName)
|
||||
{
|
||||
if (_communityDesigns.TryGetValue(designName, out var communityDesign) && communityDesign != null)
|
||||
return communityDesign;
|
||||
}
|
||||
|
||||
public void CheckForUpdates(EUpdateMode updateMode)
|
||||
{
|
||||
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
|
||||
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
|
||||
AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}");
|
||||
}
|
||||
communityDesign = GetDesignAsync(designName).GetAwaiter().GetResult();
|
||||
_communityDesigns[designName] = communityDesign;
|
||||
return communityDesign;
|
||||
}
|
||||
|
||||
private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
|
||||
public void CheckForUpdates(EUpdateMode updateMode)
|
||||
{
|
||||
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
|
||||
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
|
||||
AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}");
|
||||
}
|
||||
|
||||
private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
|
||||
{
|
||||
_infos = JsonConvert.DeserializeObject<Info>(args.RemoteData);
|
||||
if (_infos != null)
|
||||
{
|
||||
_infos = JsonConvert.DeserializeObject<Info>(args.RemoteData);
|
||||
if (_infos != null)
|
||||
args.UpdateInfo = new UpdateInfoEventArgs
|
||||
{
|
||||
args.UpdateInfo = new UpdateInfoEventArgs
|
||||
{
|
||||
CurrentVersion = _infos.Version,
|
||||
ChangelogURL = _infos.ChangelogUrl,
|
||||
DownloadURL = _infos.DownloadUrl
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckForUpdateEvent(UpdateInfoEventArgs args)
|
||||
{
|
||||
if (args is {CurrentVersion: { }})
|
||||
{
|
||||
var currentVersion = new System.Version(args.CurrentVersion);
|
||||
if (currentVersion == args.InstalledVersion)
|
||||
{
|
||||
if (UserSettings.Default.ShowChangelog)
|
||||
ShowChangelog(args);
|
||||
return;
|
||||
}
|
||||
|
||||
var downgrade = currentVersion < args.InstalledVersion;
|
||||
var messageBox = new MessageBoxModel
|
||||
{
|
||||
Text = $"The latest version of FModel {UserSettings.Default.UpdateMode} is {args.CurrentVersion}. You are using version {args.InstalledVersion}. Do you want to {(downgrade ? "downgrade" : "update")} the application now?",
|
||||
Caption = $"{(downgrade ? "Downgrade" : "Update")} Available",
|
||||
Icon = MessageBoxImage.Question,
|
||||
Buttons = MessageBoxButtons.YesNo(),
|
||||
IsSoundEnabled = false
|
||||
};
|
||||
|
||||
MessageBox.Show(messageBox);
|
||||
if (messageBox.Result != MessageBoxResult.Yes) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (AutoUpdater.DownloadUpdate(args))
|
||||
{
|
||||
UserSettings.Default.ShowChangelog = true;
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
UserSettings.Default.ShowChangelog = false;
|
||||
MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show(
|
||||
"There is a problem reaching the update server, please check your internet connection or try again later.",
|
||||
"Update Check Failed", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowChangelog(UpdateInfoEventArgs args)
|
||||
{
|
||||
var request = new RestRequest(args.ChangelogURL, Method.GET);
|
||||
var response = _client.Execute(request);
|
||||
if (string.IsNullOrEmpty(response.Content)) return;
|
||||
|
||||
_applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}");
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog");
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.SetDocumentText(response.Content, false);
|
||||
UserSettings.Default.ShowChangelog = false;
|
||||
CurrentVersion = _infos.Version,
|
||||
ChangelogURL = _infos.ChangelogUrl,
|
||||
DownloadURL = _infos.DownloadUrl
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckForUpdateEvent(UpdateInfoEventArgs args)
|
||||
{
|
||||
if (args is { CurrentVersion: { } })
|
||||
{
|
||||
var currentVersion = new System.Version(args.CurrentVersion);
|
||||
if (currentVersion == args.InstalledVersion)
|
||||
{
|
||||
if (UserSettings.Default.ShowChangelog)
|
||||
ShowChangelog(args);
|
||||
return;
|
||||
}
|
||||
|
||||
var downgrade = currentVersion < args.InstalledVersion;
|
||||
var messageBox = new MessageBoxModel
|
||||
{
|
||||
Text = $"The latest version of FModel {UserSettings.Default.UpdateMode} is {args.CurrentVersion}. You are using version {args.InstalledVersion}. Do you want to {(downgrade ? "downgrade" : "update")} the application now?",
|
||||
Caption = $"{(downgrade ? "Downgrade" : "Update")} Available",
|
||||
Icon = MessageBoxImage.Question,
|
||||
Buttons = MessageBoxButtons.YesNo(),
|
||||
IsSoundEnabled = false
|
||||
};
|
||||
|
||||
MessageBox.Show(messageBox);
|
||||
if (messageBox.Result != MessageBoxResult.Yes) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (AutoUpdater.DownloadUpdate(args))
|
||||
{
|
||||
UserSettings.Default.ShowChangelog = true;
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
UserSettings.Default.ShowChangelog = false;
|
||||
MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show(
|
||||
"There is a problem reaching the update server, please check your internet connection or try again later.",
|
||||
"Update Check Failed", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowChangelog(UpdateInfoEventArgs args)
|
||||
{
|
||||
var request = new RestRequest(args.ChangelogURL, Method.GET);
|
||||
var response = _client.Execute(request);
|
||||
if (string.IsNullOrEmpty(response.Content)) return;
|
||||
|
||||
_applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}");
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog");
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.SetDocumentText(response.Content, false);
|
||||
UserSettings.Default.ShowChangelog = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,31 +3,30 @@ using FModel.ViewModels.ApiEndpoints.Models;
|
|||
using RestSharp;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
||||
public class FortniteApiEndpoint : AbstractApiProvider
|
||||
{
|
||||
public class FortniteApiEndpoint : AbstractApiProvider
|
||||
public FortniteApiEndpoint(IRestClient client) : base(client)
|
||||
{
|
||||
public FortniteApiEndpoint(IRestClient client) : base(client)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PlaylistResponse> GetPlaylistAsync(string playlistId)
|
||||
{
|
||||
var request = new RestRequest($"https://fortnite-api.com/v1/playlists/{playlistId}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<PlaylistResponse>(request).ConfigureAwait(false);
|
||||
return response.Data;
|
||||
}
|
||||
public async Task<PlaylistResponse> GetPlaylistAsync(string playlistId)
|
||||
{
|
||||
var request = new RestRequest($"https://fortnite-api.com/v1/playlists/{playlistId}", Method.GET);
|
||||
var response = await _client.ExecuteAsync<PlaylistResponse>(request).ConfigureAwait(false);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public PlaylistResponse GetPlaylist(string playlistId)
|
||||
{
|
||||
return GetPlaylistAsync(playlistId).GetAwaiter().GetResult();
|
||||
}
|
||||
public PlaylistResponse GetPlaylist(string playlistId)
|
||||
{
|
||||
return GetPlaylistAsync(playlistId).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public bool TryGetBytes(Uri link, out byte[] data)
|
||||
{
|
||||
var request = new RestRequest(link, Method.GET);
|
||||
data = _client.DownloadData(request);
|
||||
return data != null;
|
||||
}
|
||||
public bool TryGetBytes(Uri link, out byte[] data)
|
||||
{
|
||||
var request = new RestRequest(link, Method.GET);
|
||||
data = _client.DownloadData(request);
|
||||
return data != null;
|
||||
}
|
||||
}
|
||||
|
|
@ -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(IRestClient client) : base(client)
|
||||
{
|
||||
private const string _URL = "https://fmodel.fortnite-api.com/valorant/v2/manifest";
|
||||
|
||||
public ValorantApiEndpoint(IRestClient client) : base(client) { }
|
||||
|
||||
public async Task<VManifest> GetManifestAsync(CancellationToken token)
|
||||
{
|
||||
var request = new RestRequest(_URL, Method.GET);
|
||||
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
|
||||
return new VManifest(response.RawBytes);
|
||||
}
|
||||
|
||||
public VManifest GetManifest(CancellationToken token) => GetManifestAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public class VManifest
|
||||
public async Task<VManifest> GetManifestAsync(CancellationToken token)
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
public readonly VHeader Header;
|
||||
public readonly VChunk[] Chunks;
|
||||
public readonly VPak[] Paks;
|
||||
var request = new RestRequest(_URL, Method.GET);
|
||||
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
|
||||
return new VManifest(response.RawBytes);
|
||||
}
|
||||
|
||||
public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data)) { }
|
||||
public VManifest GetManifest(CancellationToken token) => GetManifestAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private VManifest(FArchive Ar)
|
||||
public class VManifest
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
public readonly VHeader Header;
|
||||
public readonly VChunk[] Chunks;
|
||||
public readonly VPak[] Paks;
|
||||
|
||||
public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data))
|
||||
{
|
||||
}
|
||||
|
||||
private VManifest(FArchive Ar)
|
||||
{
|
||||
using (Ar)
|
||||
{
|
||||
using (Ar)
|
||||
{
|
||||
Header = new VHeader(Ar);
|
||||
var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize);
|
||||
var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
|
||||
if (uncompressedBuffer.Length != Header.UncompressedSize)
|
||||
throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}");
|
||||
Header = new VHeader(Ar);
|
||||
var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize);
|
||||
var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
|
||||
if (uncompressedBuffer.Length != Header.UncompressedSize)
|
||||
throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}");
|
||||
|
||||
using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
|
||||
Chunks = manifest.ReadArray<VChunk>((int) Header.ChunkCount);
|
||||
Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest));
|
||||
using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
|
||||
Chunks = manifest.ReadArray<VChunk>((int) Header.ChunkCount);
|
||||
Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest));
|
||||
|
||||
if (manifest.Position != manifest.Length)
|
||||
throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}");
|
||||
}
|
||||
|
||||
_client = new HttpClient(new HttpClientHandler
|
||||
{
|
||||
UseProxy = false,
|
||||
UseCookies = false,
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
CheckCertificateRevocationList = false,
|
||||
PreAuthenticate = false,
|
||||
MaxConnectionsPerServer = 1337,
|
||||
UseDefaultCredentials = false,
|
||||
AllowAutoRedirect = false
|
||||
});
|
||||
if (manifest.Position != manifest.Length)
|
||||
throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}");
|
||||
}
|
||||
|
||||
public async ValueTask PrefetchChunk(VChunk chunk, CancellationToken cancellationToken)
|
||||
_client = new HttpClient(new HttpClientHandler
|
||||
{
|
||||
UseProxy = false,
|
||||
UseCookies = false,
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
CheckCertificateRevocationList = false,
|
||||
PreAuthenticate = false,
|
||||
MaxConnectionsPerServer = 1337,
|
||||
UseDefaultCredentials = false,
|
||||
AllowAutoRedirect = false
|
||||
});
|
||||
}
|
||||
|
||||
public async ValueTask PrefetchChunk(VChunk chunk, CancellationToken cancellationToken)
|
||||
{
|
||||
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
|
||||
if (File.Exists(chunkPath)) return;
|
||||
using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
await using var fileStream = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
await response.Content.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> GetChunkBytes(VChunk chunk, CancellationToken cancellationToken)
|
||||
{
|
||||
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
|
||||
byte[] chunkBytes;
|
||||
|
||||
if (File.Exists(chunkPath))
|
||||
{
|
||||
chunkBytes = new byte[chunk.Size];
|
||||
await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
await fs.ReadAsync(chunkBytes, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
|
||||
if (File.Exists(chunkPath)) return;
|
||||
using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
await using var fileStream = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
await response.Content.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> GetChunkBytes(VChunk chunk, CancellationToken cancellationToken)
|
||||
{
|
||||
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
|
||||
byte[] chunkBytes;
|
||||
|
||||
if (File.Exists(chunkPath))
|
||||
{
|
||||
chunkBytes = new byte[chunk.Size];
|
||||
await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
await fs.ReadAsync(chunkBytes, cancellationToken).ConfigureAwait(false);
|
||||
chunkBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
await response.Content.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
chunkBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
await response.Content.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
chunkBytes = null; // Maybe add logging?
|
||||
}
|
||||
chunkBytes = null; // Maybe add logging?
|
||||
}
|
||||
|
||||
return chunkBytes;
|
||||
}
|
||||
|
||||
public Stream GetPakStream(int index) => new VPakStream(this, index);
|
||||
return chunkBytes;
|
||||
}
|
||||
|
||||
public readonly struct VHeader
|
||||
public Stream GetPakStream(int index) => new VPakStream(this, index);
|
||||
}
|
||||
|
||||
public readonly struct VHeader
|
||||
{
|
||||
private const uint _MAGIC = 0xC3D088F7u;
|
||||
|
||||
public readonly uint Magic;
|
||||
public readonly uint HeaderSize;
|
||||
public readonly ulong ManifestId;
|
||||
public readonly uint UncompressedSize;
|
||||
public readonly uint CompressedSize;
|
||||
public readonly uint ChunkCount;
|
||||
public readonly uint PakCount;
|
||||
public readonly string GameVersion;
|
||||
|
||||
public VHeader(FArchive Ar)
|
||||
{
|
||||
private const uint _MAGIC = 0xC3D088F7u;
|
||||
Magic = Ar.Read<uint>();
|
||||
|
||||
public readonly uint Magic;
|
||||
public readonly uint HeaderSize;
|
||||
public readonly ulong ManifestId;
|
||||
public readonly uint UncompressedSize;
|
||||
public readonly uint CompressedSize;
|
||||
public readonly uint ChunkCount;
|
||||
public readonly uint PakCount;
|
||||
public readonly string GameVersion;
|
||||
if (Magic != _MAGIC)
|
||||
throw new ParserException(Ar, "Invalid manifest magic");
|
||||
|
||||
public VHeader(FArchive Ar)
|
||||
{
|
||||
Magic = Ar.Read<uint>();
|
||||
|
||||
if (Magic != _MAGIC)
|
||||
throw new ParserException(Ar, "Invalid manifest magic");
|
||||
|
||||
HeaderSize = Ar.Read<uint>();
|
||||
ManifestId = Ar.Read<ulong>();
|
||||
UncompressedSize = Ar.Read<uint>();
|
||||
CompressedSize = Ar.Read<uint>();
|
||||
ChunkCount = Ar.Read<uint>();
|
||||
PakCount = Ar.Read<uint>();
|
||||
var gameVersionLength = Ar.ReadByte();
|
||||
GameVersion = gameVersionLength == 0 ? null : Encoding.ASCII.GetString(Ar.ReadBytes(gameVersionLength));
|
||||
Ar.Position = HeaderSize;
|
||||
}
|
||||
HeaderSize = Ar.Read<uint>();
|
||||
ManifestId = Ar.Read<ulong>();
|
||||
UncompressedSize = Ar.Read<uint>();
|
||||
CompressedSize = Ar.Read<uint>();
|
||||
ChunkCount = Ar.Read<uint>();
|
||||
PakCount = Ar.Read<uint>();
|
||||
var gameVersionLength = Ar.ReadByte();
|
||||
GameVersion = gameVersionLength == 0 ? null : Encoding.ASCII.GetString(Ar.ReadBytes(gameVersionLength));
|
||||
Ar.Position = HeaderSize;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct VPak
|
||||
public readonly struct VPak
|
||||
{
|
||||
public readonly ulong Id;
|
||||
public readonly uint Size;
|
||||
public readonly uint[] ChunkIndices;
|
||||
public readonly string Name;
|
||||
|
||||
public VPak(FArchive Ar)
|
||||
{
|
||||
public readonly ulong Id;
|
||||
public readonly uint Size;
|
||||
public readonly uint[] ChunkIndices;
|
||||
public readonly string Name;
|
||||
|
||||
public VPak(FArchive Ar)
|
||||
{
|
||||
Id = Ar.Read<ulong>();
|
||||
Size = Ar.Read<uint>();
|
||||
ChunkIndices = Ar.ReadArray<uint>(Ar.Read<int>());
|
||||
Name = Encoding.ASCII.GetString(Ar.ReadBytes(Ar.ReadByte()));
|
||||
}
|
||||
|
||||
public string GetFullName() => $"ValorantLive/ShooterGame/Content/Paks/{Name}";
|
||||
Id = Ar.Read<ulong>();
|
||||
Size = Ar.Read<uint>();
|
||||
ChunkIndices = Ar.ReadArray<uint>(Ar.Read<int>());
|
||||
Name = Encoding.ASCII.GetString(Ar.ReadBytes(Ar.ReadByte()));
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct VChunk
|
||||
public string GetFullName() => $"ValorantLive/ShooterGame/Content/Paks/{Name}";
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct VChunk
|
||||
{
|
||||
public readonly ulong Id;
|
||||
public readonly uint Size;
|
||||
|
||||
public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}";
|
||||
}
|
||||
|
||||
public class VPakStream : Stream, ICloneable
|
||||
{
|
||||
private readonly VManifest _manifest;
|
||||
private readonly int _pakIndex;
|
||||
private readonly VChunk[] _chunks;
|
||||
|
||||
public VPakStream(VManifest manifest, int pakIndex, long position = 0L)
|
||||
{
|
||||
public readonly ulong Id;
|
||||
public readonly uint Size;
|
||||
_manifest = manifest;
|
||||
_pakIndex = pakIndex;
|
||||
_position = position;
|
||||
|
||||
public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}";
|
||||
var pak = manifest.Paks[pakIndex];
|
||||
_chunks = new VChunk[pak.ChunkIndices.Length];
|
||||
for (var i = 0; i < _chunks.Length; i++)
|
||||
{
|
||||
_chunks[i] = manifest.Chunks[pak.ChunkIndices[i]];
|
||||
}
|
||||
|
||||
Length = pak.Size;
|
||||
}
|
||||
|
||||
public class VPakStream : Stream, ICloneable
|
||||
public object Clone() => new VPakStream(_manifest, _pakIndex, _position);
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
private readonly VManifest _manifest;
|
||||
private readonly int _pakIndex;
|
||||
private readonly VChunk[] _chunks;
|
||||
var (i, startPos) = GetChunkIndex(_position);
|
||||
if (i == -1) return 0;
|
||||
|
||||
public VPakStream(VManifest manifest, int pakIndex, long position = 0L)
|
||||
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
|
||||
var bytesRead = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
_manifest = manifest;
|
||||
_pakIndex = pakIndex;
|
||||
_position = position;
|
||||
var chunk = _chunks[i];
|
||||
var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false);
|
||||
var chunkBytes = chunk.Size - startPos;
|
||||
var bytesLeft = count - bytesRead;
|
||||
|
||||
var pak = manifest.Paks[pakIndex];
|
||||
_chunks = new VChunk[pak.ChunkIndices.Length];
|
||||
for (var i = 0; i < _chunks.Length; i++)
|
||||
if (bytesLeft <= chunkBytes)
|
||||
{
|
||||
_chunks[i] = manifest.Chunks[pak.ChunkIndices[i]];
|
||||
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint) bytesLeft);
|
||||
bytesRead += bytesLeft;
|
||||
break;
|
||||
}
|
||||
|
||||
Length = pak.Size;
|
||||
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes);
|
||||
bytesRead += (int) chunkBytes;
|
||||
startPos = 0u;
|
||||
|
||||
if (++i == _chunks.Length) break;
|
||||
}
|
||||
|
||||
public object Clone() => new VPakStream(_manifest, _pakIndex, _position);
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var (i, startPos) = GetChunkIndex(_position);
|
||||
if (i == -1) return 0;
|
||||
|
||||
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
|
||||
var bytesRead = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var chunk = _chunks[i];
|
||||
var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false);
|
||||
var chunkBytes = chunk.Size - startPos;
|
||||
var bytesLeft = count - bytesRead;
|
||||
|
||||
if (bytesLeft <= chunkBytes)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint)bytesLeft);
|
||||
bytesRead += bytesLeft;
|
||||
break;
|
||||
}
|
||||
|
||||
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes);
|
||||
bytesRead += (int)chunkBytes;
|
||||
startPos = 0u;
|
||||
|
||||
if (++i == _chunks.Length) break;
|
||||
}
|
||||
|
||||
_position += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
private async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
|
||||
{
|
||||
var tasks = new List<Task>();
|
||||
var s = new SemaphoreSlim(concurrentDownloads);
|
||||
while (count > 0)
|
||||
{
|
||||
await s.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var chunk = _chunks[i++];
|
||||
tasks.Add(PrefetchChunkAsync(chunk));
|
||||
|
||||
if (i == _chunks.Length) break;
|
||||
count -= chunk.Size - startPos;
|
||||
startPos = 0u;
|
||||
}
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
s.Dispose();
|
||||
async Task PrefetchChunkAsync(VChunk chunk)
|
||||
{
|
||||
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
|
||||
s.Release(); // This is intended
|
||||
}
|
||||
}
|
||||
|
||||
private (int Index, uint ChunkPos) GetChunkIndex(long position)
|
||||
{
|
||||
for (var i = 0; i < _chunks.Length; i++)
|
||||
{
|
||||
var size = _chunks[i].Size;
|
||||
if (position < size) return (i, (uint) position);
|
||||
position -= size;
|
||||
}
|
||||
|
||||
return (-1, 0u);
|
||||
}
|
||||
|
||||
private long _position;
|
||||
public override long Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
if (value >= Length || value < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
|
||||
_position = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
Position = origin switch
|
||||
{
|
||||
SeekOrigin.Begin => offset,
|
||||
SeekOrigin.Current => offset + _position,
|
||||
SeekOrigin.End => Length + offset,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(offset))
|
||||
};
|
||||
return _position;
|
||||
}
|
||||
|
||||
public override long Length { get; }
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => true;
|
||||
public override bool CanWrite => false;
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
_position += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
private async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
|
||||
{
|
||||
var tasks = new List<Task>();
|
||||
var s = new SemaphoreSlim(concurrentDownloads);
|
||||
while (count > 0)
|
||||
{
|
||||
await s.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var chunk = _chunks[i++];
|
||||
tasks.Add(PrefetchChunkAsync(chunk));
|
||||
|
||||
if (i == _chunks.Length) break;
|
||||
count -= chunk.Size - startPos;
|
||||
startPos = 0u;
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
s.Dispose();
|
||||
|
||||
async Task PrefetchChunkAsync(VChunk chunk)
|
||||
{
|
||||
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
|
||||
s.Release(); // This is intended
|
||||
}
|
||||
}
|
||||
|
||||
private (int Index, uint ChunkPos) GetChunkIndex(long position)
|
||||
{
|
||||
for (var i = 0; i < _chunks.Length; i++)
|
||||
{
|
||||
var size = _chunks[i].Size;
|
||||
if (position < size) return (i, (uint) position);
|
||||
position -= size;
|
||||
}
|
||||
|
||||
return (-1, 0u);
|
||||
}
|
||||
|
||||
private long _position;
|
||||
public override long Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
if (value >= Length || value < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
|
||||
_position = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
Position = origin switch
|
||||
{
|
||||
SeekOrigin.Begin => offset,
|
||||
SeekOrigin.Current => offset + _position,
|
||||
SeekOrigin.End => Length + offset,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(offset))
|
||||
};
|
||||
return _position;
|
||||
}
|
||||
|
||||
public override long Length { get; }
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => true;
|
||||
public override bool CanWrite => false;
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
}
|
||||
|
|
@ -5,202 +5,200 @@ using FModel.Settings;
|
|||
using FModel.ViewModels.Commands;
|
||||
using FModel.Views;
|
||||
using FModel.Views.Resources.Controls;
|
||||
|
||||
using Ionic.Zip;
|
||||
|
||||
using Oodle.NET;
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
using MessageBox = AdonisUI.Controls.MessageBox;
|
||||
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
|
||||
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
|
||||
using OodleCUE4 = CUE4Parse.Compression.Oodle;
|
||||
|
||||
namespace FModel.ViewModels
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class ApplicationViewModel : ViewModel
|
||||
{
|
||||
public class ApplicationViewModel : ViewModel
|
||||
private EBuildKind _build;
|
||||
|
||||
public EBuildKind Build
|
||||
{
|
||||
private EBuildKind _build;
|
||||
public EBuildKind Build
|
||||
get => _build;
|
||||
private set
|
||||
{
|
||||
get => _build;
|
||||
private set
|
||||
{
|
||||
SetProperty(ref _build, value);
|
||||
RaisePropertyChanged(nameof(TitleExtra));
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isReady;
|
||||
public bool IsReady
|
||||
{
|
||||
get => _isReady;
|
||||
private set => SetProperty(ref _isReady, value);
|
||||
}
|
||||
|
||||
private EStatusKind _status;
|
||||
public EStatusKind Status
|
||||
{
|
||||
get => _status;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _status, value);
|
||||
IsReady = Status != EStatusKind.Loading && Status != EStatusKind.Stopping;
|
||||
}
|
||||
}
|
||||
|
||||
public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this);
|
||||
private RightClickMenuCommand _rightClickMenuCommand;
|
||||
public MenuCommand MenuCommand => _menuCommand ??= new MenuCommand(this);
|
||||
private MenuCommand _menuCommand;
|
||||
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
|
||||
private CopyCommand _copyCommand;
|
||||
|
||||
public string TitleExtra =>
|
||||
$"{UserSettings.Default.UpdateMode} - {CUE4Parse.Game.GetDescription()} (" + // FModel {UpdateMode} - {FGame} ({UE}) ({Build})
|
||||
$"{(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" +
|
||||
$"{(Build != EBuildKind.Release ? $" ({Build})" : "")}";
|
||||
|
||||
public LoadingModesViewModel LoadingModes { get; }
|
||||
public CustomDirectoriesViewModel CustomDirectories { get; }
|
||||
public CUE4ParseViewModel CUE4Parse { get; }
|
||||
public SettingsViewModel SettingsView { get; }
|
||||
public AesManagerViewModel AesManager { get; }
|
||||
public AudioPlayerViewModel AudioPlayer { get; }
|
||||
public MapViewerViewModel MapViewer { get; }
|
||||
public ModelViewerViewModel ModelViewer { get; }
|
||||
private OodleCompressor _oodle;
|
||||
|
||||
public ApplicationViewModel()
|
||||
{
|
||||
Status = EStatusKind.Loading;
|
||||
#if DEBUG
|
||||
Build = EBuildKind.Debug;
|
||||
#elif RELEASE
|
||||
Build = EBuildKind.Release;
|
||||
#else
|
||||
Build = EBuildKind.Unknown;
|
||||
#endif
|
||||
LoadingModes = new LoadingModesViewModel();
|
||||
|
||||
AvoidEmptyGameDirectoryAndSetEGame(false);
|
||||
CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory);
|
||||
CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory);
|
||||
SettingsView = new SettingsViewModel(CUE4Parse.Game);
|
||||
AesManager = new AesManagerViewModel(CUE4Parse);
|
||||
MapViewer = new MapViewerViewModel(CUE4Parse);
|
||||
AudioPlayer = new AudioPlayerViewModel();
|
||||
ModelViewer = new ModelViewerViewModel(CUE4Parse.Game);
|
||||
Status = EStatusKind.Ready;
|
||||
}
|
||||
|
||||
public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched)
|
||||
{
|
||||
var gameDirectory = UserSettings.Default.GameDirectory;
|
||||
if (!string.IsNullOrEmpty(gameDirectory) && !bAlreadyLaunched) return;
|
||||
|
||||
var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory);
|
||||
var result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
|
||||
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory;
|
||||
if (!bAlreadyLaunched || gameDirectory.Equals(gameLauncherViewModel.SelectedDetectedGame.GameDirectory)) return;
|
||||
|
||||
RestartWithWarning();
|
||||
}
|
||||
|
||||
public void RestartWithWarning()
|
||||
{
|
||||
MessageBox.Show("It looks like you just changed something.\nFModel will restart to apply your changes.", "Uh oh, a restart is needed", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
Restart();
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
var path = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
|
||||
if (path.EndsWith(".dll"))
|
||||
{
|
||||
new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
Arguments = $"\"{Path.GetFullPath(Environment.GetCommandLineArgs()[0])}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = false,
|
||||
RedirectStandardError = false,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
}.Start();
|
||||
}
|
||||
else if (path.EndsWith(".exe"))
|
||||
{
|
||||
new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = false,
|
||||
RedirectStandardError = false,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
}.Start();
|
||||
}
|
||||
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
public async Task InitVgmStream()
|
||||
{
|
||||
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
|
||||
if (File.Exists(vgmZipFilePath)) return;
|
||||
|
||||
await ApplicationService.ApiEndpointView.BenbotApi.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
|
||||
if (new FileInfo(vgmZipFilePath).Length > 0)
|
||||
{
|
||||
var zip = ZipFile.Read(vgmZipFilePath);
|
||||
var zipDir = vgmZipFilePath.SubstringBeforeLast("\\");
|
||||
foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently);
|
||||
}
|
||||
else
|
||||
{
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("Could not download VgmStream", Constants.WHITE, true);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InitOodle()
|
||||
{
|
||||
var dataDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
||||
var oodlePath = Path.Combine(dataDir.FullName, OodleCUE4.OODLE_DLL_NAME);
|
||||
|
||||
if (File.Exists(OodleCUE4.OODLE_DLL_NAME))
|
||||
{
|
||||
File.Move(OodleCUE4.OODLE_DLL_NAME, oodlePath, true);
|
||||
}
|
||||
else if (!File.Exists(oodlePath))
|
||||
{
|
||||
var result = await OodleCUE4.DownloadOodleDll(oodlePath);
|
||||
if (!result) return;
|
||||
}
|
||||
|
||||
if (File.Exists("oo2core_8_win64.dll"))
|
||||
File.Delete("oo2core_8_win64.dll");
|
||||
|
||||
_oodle = new OodleCompressor(oodlePath);
|
||||
|
||||
unsafe
|
||||
{
|
||||
OodleCUE4.DecompressFunc = (bufferPtr, bufferSize, outputPtr, outputSize, a, b, c, d, e, f, g, h, i, threadModule) =>
|
||||
_oodle.Decompress(new IntPtr(bufferPtr), bufferSize, new IntPtr(outputPtr), outputSize,
|
||||
(OodleLZ_FuzzSafe)a, (OodleLZ_CheckCRC)b, (OodleLZ_Verbosity)c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase)threadModule);
|
||||
}
|
||||
SetProperty(ref _build, value);
|
||||
RaisePropertyChanged(nameof(TitleExtra));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isReady;
|
||||
|
||||
public bool IsReady
|
||||
{
|
||||
get => _isReady;
|
||||
private set => SetProperty(ref _isReady, value);
|
||||
}
|
||||
|
||||
private EStatusKind _status;
|
||||
|
||||
public EStatusKind Status
|
||||
{
|
||||
get => _status;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _status, value);
|
||||
IsReady = Status != EStatusKind.Loading && Status != EStatusKind.Stopping;
|
||||
}
|
||||
}
|
||||
|
||||
public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this);
|
||||
private RightClickMenuCommand _rightClickMenuCommand;
|
||||
public MenuCommand MenuCommand => _menuCommand ??= new MenuCommand(this);
|
||||
private MenuCommand _menuCommand;
|
||||
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
|
||||
private CopyCommand _copyCommand;
|
||||
|
||||
public string TitleExtra =>
|
||||
$"{UserSettings.Default.UpdateMode} - {CUE4Parse.Game.GetDescription()} (" + // FModel {UpdateMode} - {FGame} ({UE}) ({Build})
|
||||
$"{(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" +
|
||||
$"{(Build != EBuildKind.Release ? $" ({Build})" : "")}";
|
||||
|
||||
public LoadingModesViewModel LoadingModes { get; }
|
||||
public CustomDirectoriesViewModel CustomDirectories { get; }
|
||||
public CUE4ParseViewModel CUE4Parse { get; }
|
||||
public SettingsViewModel SettingsView { get; }
|
||||
public AesManagerViewModel AesManager { get; }
|
||||
public AudioPlayerViewModel AudioPlayer { get; }
|
||||
public MapViewerViewModel MapViewer { get; }
|
||||
public ModelViewerViewModel ModelViewer { get; }
|
||||
private OodleCompressor _oodle;
|
||||
|
||||
public ApplicationViewModel()
|
||||
{
|
||||
Status = EStatusKind.Loading;
|
||||
#if DEBUG
|
||||
Build = EBuildKind.Debug;
|
||||
#elif RELEASE
|
||||
Build = EBuildKind.Release;
|
||||
#else
|
||||
Build = EBuildKind.Unknown;
|
||||
#endif
|
||||
LoadingModes = new LoadingModesViewModel();
|
||||
|
||||
AvoidEmptyGameDirectoryAndSetEGame(false);
|
||||
CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory);
|
||||
CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory);
|
||||
SettingsView = new SettingsViewModel(CUE4Parse.Game);
|
||||
AesManager = new AesManagerViewModel(CUE4Parse);
|
||||
MapViewer = new MapViewerViewModel(CUE4Parse);
|
||||
AudioPlayer = new AudioPlayerViewModel();
|
||||
ModelViewer = new ModelViewerViewModel(CUE4Parse.Game);
|
||||
Status = EStatusKind.Ready;
|
||||
}
|
||||
|
||||
public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched)
|
||||
{
|
||||
var gameDirectory = UserSettings.Default.GameDirectory;
|
||||
if (!string.IsNullOrEmpty(gameDirectory) && !bAlreadyLaunched) return;
|
||||
|
||||
var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory);
|
||||
var result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
|
||||
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory;
|
||||
if (!bAlreadyLaunched || gameDirectory == gameLauncherViewModel.SelectedDetectedGame.GameDirectory) return;
|
||||
|
||||
RestartWithWarning();
|
||||
}
|
||||
|
||||
public void RestartWithWarning()
|
||||
{
|
||||
MessageBox.Show("It looks like you just changed something.\nFModel will restart to apply your changes.", "Uh oh, a restart is needed", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
Restart();
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
var path = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
|
||||
if (path.EndsWith(".dll"))
|
||||
{
|
||||
new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
Arguments = $"\"{Path.GetFullPath(Environment.GetCommandLineArgs()[0])}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = false,
|
||||
RedirectStandardError = false,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
}.Start();
|
||||
}
|
||||
else if (path.EndsWith(".exe"))
|
||||
{
|
||||
new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = false,
|
||||
RedirectStandardError = false,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
}.Start();
|
||||
}
|
||||
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
public async Task InitVgmStream()
|
||||
{
|
||||
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
|
||||
if (File.Exists(vgmZipFilePath)) return;
|
||||
|
||||
await ApplicationService.ApiEndpointView.BenbotApi.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
|
||||
if (new FileInfo(vgmZipFilePath).Length > 0)
|
||||
{
|
||||
var zip = ZipFile.Read(vgmZipFilePath);
|
||||
var zipDir = vgmZipFilePath.SubstringBeforeLast("\\");
|
||||
foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently);
|
||||
}
|
||||
else
|
||||
{
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("Could not download VgmStream", Constants.WHITE, true);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InitOodle()
|
||||
{
|
||||
var dataDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
||||
var oodlePath = Path.Combine(dataDir.FullName, OodleCUE4.OODLE_DLL_NAME);
|
||||
|
||||
if (File.Exists(OodleCUE4.OODLE_DLL_NAME))
|
||||
{
|
||||
File.Move(OodleCUE4.OODLE_DLL_NAME, oodlePath, true);
|
||||
}
|
||||
else if (!File.Exists(oodlePath))
|
||||
{
|
||||
var result = await OodleCUE4.DownloadOodleDll(oodlePath);
|
||||
if (!result) return;
|
||||
}
|
||||
|
||||
if (File.Exists("oo2core_8_win64.dll"))
|
||||
File.Delete("oo2core_8_win64.dll");
|
||||
|
||||
_oodle = new OodleCompressor(oodlePath);
|
||||
|
||||
unsafe
|
||||
{
|
||||
OodleCUE4.DecompressFunc = (bufferPtr, bufferSize, outputPtr, outputSize, a, b, c, d, e, f, g, h, i, threadModule) =>
|
||||
_oodle.Decompress(new IntPtr(bufferPtr), bufferSize, new IntPtr(outputPtr), outputSize,
|
||||
(OodleLZ_FuzzSafe) a, (OodleLZ_CheckCRC) b, (OodleLZ_Verbosity) c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase) threadModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.SelectedImage.ExportName + " (Image)", () =>
|
||||
var popout = new ImagePopout
|
||||
{
|
||||
var popout = new ImagePopout
|
||||
{
|
||||
Title = contextViewModel.SelectedImage.ExportName + " (Image)",
|
||||
Width = contextViewModel.SelectedImage.Image.Width,
|
||||
Height = contextViewModel.SelectedImage.Image.Height,
|
||||
WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
|
||||
ImageCtrl = {Source = contextViewModel.SelectedImage.Image}
|
||||
};
|
||||
RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor));
|
||||
popout.Show();
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "Copy":
|
||||
ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png");
|
||||
break;
|
||||
case "Save":
|
||||
contextViewModel.SaveImage(false);
|
||||
break;
|
||||
Title = contextViewModel.SelectedImage.ExportName + " (Image)",
|
||||
Width = contextViewModel.SelectedImage.Image.Width,
|
||||
Height = contextViewModel.SelectedImage.Image.Height,
|
||||
WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
|
||||
ImageCtrl = { Source = contextViewModel.SelectedImage.Image }
|
||||
};
|
||||
RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor));
|
||||
popout.Show();
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "Copy":
|
||||
ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png");
|
||||
break;
|
||||
case "Save":
|
||||
contextViewModel.SaveImage(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,210 +18,209 @@ using FModel.Views.Resources.Controls;
|
|||
using K4os.Compression.LZ4.Streams;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace FModel.ViewModels.Commands
|
||||
namespace FModel.ViewModels.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// this will always load all files no matter the loading mode
|
||||
/// however what this does is filtering what to show to the user
|
||||
/// </summary>
|
||||
public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||
{
|
||||
/// <summary>
|
||||
/// this will always load all files no matter the loading mode
|
||||
/// however what this does is filtering what to show to the user
|
||||
/// </summary>
|
||||
public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||
private const uint _IS_LZ4 = 0x184D2204u;
|
||||
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
|
||||
|
||||
public LoadCommand(LoadingModesViewModel contextViewModel) : base(contextViewModel)
|
||||
{
|
||||
private const uint _IS_LZ4 = 0x184D2204u;
|
||||
}
|
||||
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
|
||||
|
||||
public LoadCommand(LoadingModesViewModel contextViewModel) : base(contextViewModel)
|
||||
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
|
||||
{
|
||||
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
|
||||
if (_applicationView.CUE4Parse.Provider.Files.Count <= 0)
|
||||
{
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true);
|
||||
return;
|
||||
}
|
||||
|
||||
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
|
||||
if (_applicationView.CUE4Parse.Game == FGame.FortniteGame &&
|
||||
_applicationView.CUE4Parse.Provider.MappingsContainer == null)
|
||||
{
|
||||
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
|
||||
if (_applicationView.CUE4Parse.Provider.Files.Count <= 0)
|
||||
{
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_applicationView.CUE4Parse.Game == FGame.FortniteGame &&
|
||||
_applicationView.CUE4Parse.Provider.MappingsContainer == null)
|
||||
{
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("Mappings could not get pulled, extracting packages might not work properly. If so, press F12 or please restart.", Constants.WHITE, true);
|
||||
}
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("Mappings could not get pulled, extracting packages might not work properly. If so, press F12 or please restart.", Constants.WHITE, true);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
var loadingTime = Stopwatch.StartNew();
|
||||
var loadingTime = Stopwatch.StartNew();
|
||||
#endif
|
||||
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
|
||||
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
|
||||
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
|
||||
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
|
||||
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
|
||||
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
|
||||
|
||||
await _applicationView.CUE4Parse.LoadLocalizedResources(); // load locres if not already loaded
|
||||
await _applicationView.CUE4Parse.LoadVirtualPaths(); // load virtual paths if not already loaded
|
||||
Helper.CloseWindow<AdonisWindow>("Search View"); // close search window if opened
|
||||
await _applicationView.CUE4Parse.LoadLocalizedResources(); // load locres if not already loaded
|
||||
await _applicationView.CUE4Parse.LoadVirtualPaths(); // load virtual paths if not already loaded
|
||||
Helper.CloseWindow<AdonisWindow>("Search View"); // close search window if opened
|
||||
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
// filter what to show
|
||||
switch (UserSettings.Default.LoadingMode)
|
||||
{
|
||||
case ELoadingMode.Single:
|
||||
case ELoadingMode.Multiple:
|
||||
{
|
||||
var l = (IList) parameter;
|
||||
if (l.Count < 1) return;
|
||||
|
||||
var directoryFilesToShow = l.Cast<FileItem>();
|
||||
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);
|
||||
break;
|
||||
}
|
||||
case ELoadingMode.All:
|
||||
{
|
||||
FilterDirectoryFilesToDisplay(cancellationToken, null);
|
||||
break;
|
||||
}
|
||||
case ELoadingMode.AllButNew:
|
||||
case ELoadingMode.AllButModified:
|
||||
{
|
||||
FilterNewOrModifiedFilesToDisplay(cancellationToken);
|
||||
break;
|
||||
}
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_discordHandler.UpdatePresence(_applicationView.CUE4Parse);
|
||||
});
|
||||
#if DEBUG
|
||||
loadingTime.Stop();
|
||||
FLogger.AppendDebug();
|
||||
FLogger.AppendText($"{_applicationView.CUE4Parse.SearchVm.SearchResults.Count} packages, {_applicationView.CUE4Parse.LocalizedResourcesCount} localized resources, and {_applicationView.CUE4Parse.VirtualPathCount} virtual paths loaded in {loadingTime.Elapsed.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)} seconds", Constants.WHITE, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void FilterDirectoryFilesToDisplay(CancellationToken cancellationToken, IEnumerable<FileItem> directoryFiles)
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
HashSet<string> filter;
|
||||
if (directoryFiles == null)
|
||||
filter = null;
|
||||
else
|
||||
{
|
||||
filter = new HashSet<string>();
|
||||
foreach (var directoryFile in directoryFiles)
|
||||
{
|
||||
if (!directoryFile.IsEnabled)
|
||||
continue;
|
||||
|
||||
filter.Add(directoryFile.Name);
|
||||
}
|
||||
}
|
||||
|
||||
var hasFilter = filter != null && filter.Count != 0;
|
||||
var entries = new List<VfsEntry>();
|
||||
|
||||
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
|
||||
|
||||
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
|
||||
continue;
|
||||
|
||||
if (hasFilter)
|
||||
{
|
||||
if (filter.Contains(entry.Vfs.Name))
|
||||
entries.Add(entry);
|
||||
}
|
||||
else
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||
}
|
||||
|
||||
private void FilterNewOrModifiedFilesToDisplay(CancellationToken cancellationToken)
|
||||
{
|
||||
var openFileDialog = new OpenFileDialog
|
||||
{
|
||||
Title = "Select a backup file older than your current game version",
|
||||
InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"),
|
||||
Filter = "FBKP Files (*.fbkp)|*.fbkp|All Files (*.*)|*.*",
|
||||
Multiselect = false
|
||||
};
|
||||
|
||||
if (!(bool) openFileDialog.ShowDialog()) return;
|
||||
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true);
|
||||
|
||||
using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
||||
if (fileStream.ReadUInt32() == _IS_LZ4)
|
||||
{
|
||||
fileStream.Position -= 4;
|
||||
using var compressionStream = LZ4Stream.Decode(fileStream);
|
||||
compressionStream.CopyTo(memoryStream);
|
||||
}
|
||||
else fileStream.CopyTo(memoryStream);
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
|
||||
var entries = new List<VfsEntry>();
|
||||
|
||||
// filter what to show
|
||||
switch (UserSettings.Default.LoadingMode)
|
||||
{
|
||||
case ELoadingMode.AllButNew:
|
||||
case ELoadingMode.Single:
|
||||
case ELoadingMode.Multiple:
|
||||
{
|
||||
var paths = new Dictionary<string, int>();
|
||||
while (archive.Position < archive.Length)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
archive.Position += 29;
|
||||
paths[archive.ReadString().ToLower()[1..]] = 0;
|
||||
archive.Position += 4;
|
||||
}
|
||||
|
||||
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (value is not VfsEntry entry || paths.ContainsKey(key) || entry.Path.EndsWith(".uexp") ||
|
||||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
|
||||
|
||||
entries.Add(entry);
|
||||
}
|
||||
var l = (IList) parameter;
|
||||
if (l.Count < 1) return;
|
||||
|
||||
var directoryFilesToShow = l.Cast<FileItem>();
|
||||
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);
|
||||
break;
|
||||
}
|
||||
case ELoadingMode.All:
|
||||
{
|
||||
FilterDirectoryFilesToDisplay(cancellationToken, null);
|
||||
break;
|
||||
}
|
||||
case ELoadingMode.AllButNew:
|
||||
case ELoadingMode.AllButModified:
|
||||
{
|
||||
while (archive.Position < archive.Length)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
archive.Position += 16;
|
||||
var uncompressedSize = archive.Read<long>();
|
||||
var isEncrypted = archive.ReadFlag();
|
||||
archive.Position += 4;
|
||||
var fullPath = archive.ReadString().ToLower()[1..];
|
||||
archive.Position += 4;
|
||||
|
||||
if (fullPath.EndsWith(".uexp") || fullPath.EndsWith(".ubulk") || fullPath.EndsWith(".uptnl") ||
|
||||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(fullPath, out var asset) || asset is not VfsEntry entry ||
|
||||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
|
||||
continue;
|
||||
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
FilterNewOrModifiedFilesToDisplay(cancellationToken);
|
||||
break;
|
||||
}
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||
}
|
||||
_discordHandler.UpdatePresence(_applicationView.CUE4Parse);
|
||||
});
|
||||
#if DEBUG
|
||||
loadingTime.Stop();
|
||||
FLogger.AppendDebug();
|
||||
FLogger.AppendText($"{_applicationView.CUE4Parse.SearchVm.SearchResults.Count} packages, {_applicationView.CUE4Parse.LocalizedResourcesCount} localized resources, and {_applicationView.CUE4Parse.VirtualPathCount} virtual paths loaded in {loadingTime.Elapsed.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)} seconds", Constants.WHITE, true);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void FilterDirectoryFilesToDisplay(CancellationToken cancellationToken, IEnumerable<FileItem> directoryFiles)
|
||||
{
|
||||
HashSet<string> filter;
|
||||
if (directoryFiles == null)
|
||||
filter = null;
|
||||
else
|
||||
{
|
||||
filter = new HashSet<string>();
|
||||
foreach (var directoryFile in directoryFiles)
|
||||
{
|
||||
if (!directoryFile.IsEnabled)
|
||||
continue;
|
||||
|
||||
filter.Add(directoryFile.Name);
|
||||
}
|
||||
}
|
||||
|
||||
var hasFilter = filter != null && filter.Count != 0;
|
||||
var entries = new List<VfsEntry>();
|
||||
|
||||
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
|
||||
|
||||
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
|
||||
continue;
|
||||
|
||||
if (hasFilter)
|
||||
{
|
||||
if (filter.Contains(entry.Vfs.Name))
|
||||
entries.Add(entry);
|
||||
}
|
||||
else
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||
}
|
||||
|
||||
private void FilterNewOrModifiedFilesToDisplay(CancellationToken cancellationToken)
|
||||
{
|
||||
var openFileDialog = new OpenFileDialog
|
||||
{
|
||||
Title = "Select a backup file older than your current game version",
|
||||
InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"),
|
||||
Filter = "FBKP Files (*.fbkp)|*.fbkp|All Files (*.*)|*.*",
|
||||
Multiselect = false
|
||||
};
|
||||
|
||||
if (!openFileDialog.ShowDialog().GetValueOrDefault()) return;
|
||||
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true);
|
||||
|
||||
using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
||||
if (fileStream.ReadUInt32() == _IS_LZ4)
|
||||
{
|
||||
fileStream.Position -= 4;
|
||||
using var compressionStream = LZ4Stream.Decode(fileStream);
|
||||
compressionStream.CopyTo(memoryStream);
|
||||
}
|
||||
else fileStream.CopyTo(memoryStream);
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
|
||||
var entries = new List<VfsEntry>();
|
||||
|
||||
switch (UserSettings.Default.LoadingMode)
|
||||
{
|
||||
case ELoadingMode.AllButNew:
|
||||
{
|
||||
var paths = new Dictionary<string, int>();
|
||||
while (archive.Position < archive.Length)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
archive.Position += 29;
|
||||
paths[archive.ReadString().ToLower()[1..]] = 0;
|
||||
archive.Position += 4;
|
||||
}
|
||||
|
||||
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (value is not VfsEntry entry || paths.ContainsKey(key) || entry.Path.EndsWith(".uexp") ||
|
||||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
|
||||
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ELoadingMode.AllButModified:
|
||||
{
|
||||
while (archive.Position < archive.Length)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
archive.Position += 16;
|
||||
var uncompressedSize = archive.Read<long>();
|
||||
var isEncrypted = archive.ReadFlag();
|
||||
archive.Position += 4;
|
||||
var fullPath = archive.ReadString().ToLower()[1..];
|
||||
archive.Position += 4;
|
||||
|
||||
if (fullPath.EndsWith(".uexp") || fullPath.EndsWith(".ubulk") || fullPath.EndsWith(".uptnl") ||
|
||||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(fullPath, out var asset) || asset is not VfsEntry entry ||
|
||||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
|
||||
continue;
|
||||
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,60 +3,63 @@ using System.Linq;
|
|||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
|
||||
namespace FModel.ViewModels.Commands
|
||||
namespace FModel.ViewModels.Commands;
|
||||
|
||||
public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
||||
{
|
||||
public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
|
||||
public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
|
||||
{
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
|
||||
public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
|
||||
{
|
||||
}
|
||||
|
||||
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
|
||||
{
|
||||
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
||||
return;
|
||||
|
||||
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
|
||||
if (!assetItems.Any()) return;
|
||||
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
switch (trigger)
|
||||
{
|
||||
case "Assets_Extract_New_Tab":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(asset.FullPath, true);
|
||||
}
|
||||
break;
|
||||
case "Assets_Export_Data":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ExportData(asset.FullPath);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Properties":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(asset.FullPath);
|
||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveProperty(false);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Texture":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(asset.FullPath);
|
||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveImage(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
|
||||
{
|
||||
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
||||
return;
|
||||
|
||||
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
|
||||
if (!assetItems.Any()) return;
|
||||
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
switch (trigger)
|
||||
{
|
||||
case "Assets_Extract_New_Tab":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(asset.FullPath, true);
|
||||
}
|
||||
|
||||
break;
|
||||
case "Assets_Export_Data":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ExportData(asset.FullPath);
|
||||
}
|
||||
|
||||
break;
|
||||
case "Assets_Save_Properties":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(asset.FullPath);
|
||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveProperty(false);
|
||||
}
|
||||
|
||||
break;
|
||||
case "Assets_Save_Texture":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(asset.FullPath);
|
||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveImage(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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,163 +9,162 @@ using FModel.Framework;
|
|||
using FModel.Settings;
|
||||
using FModel.ViewModels.Commands;
|
||||
|
||||
namespace FModel.ViewModels
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class CustomDirectory : ViewModel
|
||||
{
|
||||
public class CustomDirectory : ViewModel
|
||||
private string _header;
|
||||
public string Header
|
||||
{
|
||||
private string _header;
|
||||
public string Header
|
||||
{
|
||||
get => _header;
|
||||
set => SetProperty(ref _header, value);
|
||||
}
|
||||
|
||||
private string _directoryPath;
|
||||
public string DirectoryPath
|
||||
{
|
||||
get => _directoryPath;
|
||||
set => SetProperty(ref _directoryPath, value);
|
||||
}
|
||||
|
||||
public CustomDirectory()
|
||||
{
|
||||
Header = string.Empty;
|
||||
DirectoryPath = string.Empty;
|
||||
}
|
||||
|
||||
public CustomDirectory(string header, string path)
|
||||
{
|
||||
Header = header;
|
||||
DirectoryPath = path;
|
||||
}
|
||||
|
||||
public override string ToString() => Header;
|
||||
get => _header;
|
||||
set => SetProperty(ref _header, value);
|
||||
}
|
||||
|
||||
public class CustomDirectoriesViewModel : ViewModel
|
||||
private string _directoryPath;
|
||||
public string DirectoryPath
|
||||
{
|
||||
private GoToCommand _goToCommand;
|
||||
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(this);
|
||||
private AddEditDirectoryCommand _addEditDirectoryCommand;
|
||||
public AddEditDirectoryCommand AddEditDirectoryCommand => _addEditDirectoryCommand ??= new AddEditDirectoryCommand(this);
|
||||
private DeleteDirectoryCommand _deleteDirectoryCommand;
|
||||
public DeleteDirectoryCommand DeleteDirectoryCommand => _deleteDirectoryCommand ??= new DeleteDirectoryCommand(this);
|
||||
|
||||
private readonly ObservableCollection<Control> _directories;
|
||||
public ReadOnlyObservableCollection<Control> Directories { get; }
|
||||
|
||||
private readonly FGame _game;
|
||||
private readonly string _gameDirectoryAtLaunch;
|
||||
|
||||
public CustomDirectoriesViewModel(FGame game, string directory)
|
||||
{
|
||||
_game = game;
|
||||
_gameDirectoryAtLaunch = directory;
|
||||
_directories = new ObservableCollection<Control>(EnumerateDirectories());
|
||||
Directories = new ReadOnlyObservableCollection<Control>(_directories);
|
||||
}
|
||||
|
||||
public int GetIndex(CustomDirectory dir)
|
||||
{
|
||||
return _directories.IndexOf(_directories.FirstOrDefault(x =>
|
||||
x is MenuItem m && m.Header.ToString() == dir.Header && m.Tag.ToString() == dir.DirectoryPath));
|
||||
}
|
||||
|
||||
public void Add(CustomDirectory dir)
|
||||
{
|
||||
_directories.Add(new MenuItem {Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir)});
|
||||
}
|
||||
|
||||
public void Edit(int index, CustomDirectory newDir)
|
||||
{
|
||||
if (_directories.ElementAt(index) is not MenuItem dir) return;
|
||||
|
||||
dir.Header = newDir.Header;
|
||||
dir.Tag = newDir.DirectoryPath;
|
||||
}
|
||||
|
||||
public void Delete(int index)
|
||||
{
|
||||
_directories.RemoveAt(index);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
var cd = new List<CustomDirectory>();
|
||||
for (var i = 2; i < _directories.Count; i++)
|
||||
{
|
||||
if (_directories[i] is not MenuItem m) continue;
|
||||
cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString()));
|
||||
}
|
||||
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch))
|
||||
UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd;
|
||||
else UserSettings.Default.CustomDirectories[_game] = cd;
|
||||
}
|
||||
|
||||
private IEnumerable<Control> EnumerateDirectories()
|
||||
{
|
||||
yield return new MenuItem
|
||||
{
|
||||
Header = "Add Directory",
|
||||
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/add_directory.png", UriKind.Relative))},
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
Command = AddEditDirectoryCommand
|
||||
};
|
||||
yield return new Separator();
|
||||
|
||||
IList<CustomDirectory> cd;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameDirectoryAtLaunch, out var settings))
|
||||
cd = settings.CustomDirectories;
|
||||
else cd = UserSettings.Default.CustomDirectories[_game];
|
||||
|
||||
foreach (var setting in cd)
|
||||
{
|
||||
if (setting.DirectoryPath.EndsWith('/'))
|
||||
setting.DirectoryPath = setting.DirectoryPath[..^1];
|
||||
|
||||
yield return new MenuItem
|
||||
{
|
||||
Header = setting.Header,
|
||||
Tag = setting.DirectoryPath,
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
ItemsSource = EnumerateCommands(setting)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<MenuItem> EnumerateCommands(CustomDirectory dir)
|
||||
{
|
||||
yield return new MenuItem
|
||||
{
|
||||
Header = "Go To",
|
||||
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/go_to_directory.png", UriKind.Relative))},
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
Command = GoToCommand,
|
||||
CommandParameter = dir.DirectoryPath
|
||||
};
|
||||
yield return new MenuItem
|
||||
{
|
||||
Header = "Edit Directory",
|
||||
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/edit.png", UriKind.Relative))},
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
Command = AddEditDirectoryCommand,
|
||||
CommandParameter = dir
|
||||
};
|
||||
yield return new MenuItem
|
||||
{
|
||||
Header = "Delete Directory",
|
||||
StaysOpenOnClick = true,
|
||||
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/delete.png", UriKind.Relative))},
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
Command = DeleteDirectoryCommand,
|
||||
CommandParameter = dir
|
||||
};
|
||||
}
|
||||
get => _directoryPath;
|
||||
set => SetProperty(ref _directoryPath, value);
|
||||
}
|
||||
|
||||
public CustomDirectory()
|
||||
{
|
||||
Header = string.Empty;
|
||||
DirectoryPath = string.Empty;
|
||||
}
|
||||
|
||||
public CustomDirectory(string header, string path)
|
||||
{
|
||||
Header = header;
|
||||
DirectoryPath = path;
|
||||
}
|
||||
|
||||
public override string ToString() => Header;
|
||||
}
|
||||
|
||||
public class CustomDirectoriesViewModel : ViewModel
|
||||
{
|
||||
private GoToCommand _goToCommand;
|
||||
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(this);
|
||||
private AddEditDirectoryCommand _addEditDirectoryCommand;
|
||||
public AddEditDirectoryCommand AddEditDirectoryCommand => _addEditDirectoryCommand ??= new AddEditDirectoryCommand(this);
|
||||
private DeleteDirectoryCommand _deleteDirectoryCommand;
|
||||
public DeleteDirectoryCommand DeleteDirectoryCommand => _deleteDirectoryCommand ??= new DeleteDirectoryCommand(this);
|
||||
|
||||
private readonly ObservableCollection<Control> _directories;
|
||||
public ReadOnlyObservableCollection<Control> Directories { get; }
|
||||
|
||||
private readonly FGame _game;
|
||||
private readonly string _gameDirectoryAtLaunch;
|
||||
|
||||
public CustomDirectoriesViewModel(FGame game, string directory)
|
||||
{
|
||||
_game = game;
|
||||
_gameDirectoryAtLaunch = directory;
|
||||
_directories = new ObservableCollection<Control>(EnumerateDirectories());
|
||||
Directories = new ReadOnlyObservableCollection<Control>(_directories);
|
||||
}
|
||||
|
||||
public int GetIndex(CustomDirectory dir)
|
||||
{
|
||||
return _directories.IndexOf(_directories.FirstOrDefault(x =>
|
||||
x is MenuItem m && m.Header.ToString() == dir.Header && m.Tag.ToString() == dir.DirectoryPath));
|
||||
}
|
||||
|
||||
public void Add(CustomDirectory dir)
|
||||
{
|
||||
_directories.Add(new MenuItem { Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir) });
|
||||
}
|
||||
|
||||
public void Edit(int index, CustomDirectory newDir)
|
||||
{
|
||||
if (_directories.ElementAt(index) is not MenuItem dir) return;
|
||||
|
||||
dir.Header = newDir.Header;
|
||||
dir.Tag = newDir.DirectoryPath;
|
||||
}
|
||||
|
||||
public void Delete(int index)
|
||||
{
|
||||
_directories.RemoveAt(index);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
var cd = new List<CustomDirectory>();
|
||||
for (var i = 2; i < _directories.Count; i++)
|
||||
{
|
||||
if (_directories[i] is not MenuItem m) continue;
|
||||
cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString()));
|
||||
}
|
||||
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch))
|
||||
UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd;
|
||||
else UserSettings.Default.CustomDirectories[_game] = cd;
|
||||
}
|
||||
|
||||
private IEnumerable<Control> EnumerateDirectories()
|
||||
{
|
||||
yield return new MenuItem
|
||||
{
|
||||
Header = "Add Directory",
|
||||
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/add_directory.png", UriKind.Relative)) },
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
Command = AddEditDirectoryCommand
|
||||
};
|
||||
yield return new Separator();
|
||||
|
||||
IList<CustomDirectory> cd;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameDirectoryAtLaunch, out var settings))
|
||||
cd = settings.CustomDirectories;
|
||||
else cd = UserSettings.Default.CustomDirectories[_game];
|
||||
|
||||
foreach (var setting in cd)
|
||||
{
|
||||
if (setting.DirectoryPath.EndsWith('/'))
|
||||
setting.DirectoryPath = setting.DirectoryPath[..^1];
|
||||
|
||||
yield return new MenuItem
|
||||
{
|
||||
Header = setting.Header,
|
||||
Tag = setting.DirectoryPath,
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
ItemsSource = EnumerateCommands(setting)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<MenuItem> EnumerateCommands(CustomDirectory dir)
|
||||
{
|
||||
yield return new MenuItem
|
||||
{
|
||||
Header = "Go To",
|
||||
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/go_to_directory.png", UriKind.Relative)) },
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
Command = GoToCommand,
|
||||
CommandParameter = dir.DirectoryPath
|
||||
};
|
||||
yield return new MenuItem
|
||||
{
|
||||
Header = "Edit Directory",
|
||||
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/edit.png", UriKind.Relative)) },
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
Command = AddEditDirectoryCommand,
|
||||
CommandParameter = dir
|
||||
};
|
||||
yield return new MenuItem
|
||||
{
|
||||
Header = "Delete Directory",
|
||||
StaysOpenOnClick = true,
|
||||
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/delete.png", UriKind.Relative)) },
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
Command = DeleteDirectoryCommand,
|
||||
CommandParameter = dir
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -14,356 +14,355 @@ using FModel.Settings;
|
|||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace FModel.ViewModels
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class GameSelectorViewModel : ViewModel
|
||||
{
|
||||
public class GameSelectorViewModel : ViewModel
|
||||
public class DetectedGame
|
||||
{
|
||||
public class DetectedGame
|
||||
public string GameName { get; set; }
|
||||
public string GameDirectory { get; set; }
|
||||
public bool IsManual { get; set; }
|
||||
|
||||
// the followings are only used when game is manually added
|
||||
public AesResponse AesKeys { get; set; }
|
||||
public EGame OverridedGame { get; set; }
|
||||
public List<FCustomVersion> OverridedCustomVersions { get; set; }
|
||||
public Dictionary<string, bool> OverridedOptions { get; set; }
|
||||
public IList<CustomDirectory> CustomDirectories { get; set; }
|
||||
}
|
||||
|
||||
private DetectedGame _selectedDetectedGame;
|
||||
public DetectedGame SelectedDetectedGame
|
||||
{
|
||||
get => _selectedDetectedGame;
|
||||
set => SetProperty(ref _selectedDetectedGame, value);
|
||||
}
|
||||
|
||||
private readonly ObservableCollection<DetectedGame> _autoDetectedGames;
|
||||
public ReadOnlyObservableCollection<DetectedGame> AutoDetectedGames { get; }
|
||||
|
||||
public GameSelectorViewModel(string gameDirectory)
|
||||
{
|
||||
_autoDetectedGames = new ObservableCollection<DetectedGame>(EnumerateDetectedGames().Where(x => x != null));
|
||||
foreach (var game in UserSettings.Default.ManualGames.Values)
|
||||
{
|
||||
public string GameName { get; set; }
|
||||
public string GameDirectory { get; set; }
|
||||
public bool IsManual { get; set; }
|
||||
|
||||
// the followings are only used when game is manually added
|
||||
public AesResponse AesKeys { get; set; }
|
||||
public EGame OverridedGame { get; set; }
|
||||
public List<FCustomVersion> OverridedCustomVersions { get; set; }
|
||||
public Dictionary<string, bool> OverridedOptions { get; set; }
|
||||
public IList<CustomDirectory> CustomDirectories { get; set; }
|
||||
}
|
||||
|
||||
private DetectedGame _selectedDetectedGame;
|
||||
public DetectedGame SelectedDetectedGame
|
||||
{
|
||||
get => _selectedDetectedGame;
|
||||
set => SetProperty(ref _selectedDetectedGame, value);
|
||||
}
|
||||
|
||||
private readonly ObservableCollection<DetectedGame> _autoDetectedGames;
|
||||
public ReadOnlyObservableCollection<DetectedGame> AutoDetectedGames { get; }
|
||||
|
||||
public GameSelectorViewModel(string gameDirectory)
|
||||
{
|
||||
_autoDetectedGames = new ObservableCollection<DetectedGame>(EnumerateDetectedGames().Where(x => x != null));
|
||||
foreach (var game in UserSettings.Default.ManualGames.Values)
|
||||
{
|
||||
_autoDetectedGames.Add(game);
|
||||
}
|
||||
|
||||
AutoDetectedGames = new ReadOnlyObservableCollection<DetectedGame>(_autoDetectedGames);
|
||||
|
||||
if (AutoDetectedGames.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame)
|
||||
SelectedDetectedGame = detectedGame;
|
||||
else if (!string.IsNullOrEmpty(gameDirectory))
|
||||
AddUnknownGame(gameDirectory);
|
||||
else
|
||||
SelectedDetectedGame = AutoDetectedGames.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// dedicated to manual games
|
||||
/// </summary>
|
||||
public void AddUnknownGame(string gameName, string gameDirectory)
|
||||
{
|
||||
var game = new DetectedGame
|
||||
{
|
||||
GameName = gameName,
|
||||
GameDirectory = gameDirectory,
|
||||
IsManual = true,
|
||||
AesKeys = null,
|
||||
OverridedGame = EGame.GAME_UE4_LATEST,
|
||||
OverridedCustomVersions = null,
|
||||
OverridedOptions = null,
|
||||
CustomDirectories = new List<CustomDirectory>()
|
||||
};
|
||||
|
||||
UserSettings.Default.ManualGames[gameDirectory] = game;
|
||||
_autoDetectedGames.Add(game);
|
||||
SelectedDetectedGame = AutoDetectedGames.Last();
|
||||
}
|
||||
|
||||
public void AddUnknownGame(string gameDirectory)
|
||||
{
|
||||
_autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory });
|
||||
SelectedDetectedGame = AutoDetectedGames.Last();
|
||||
}
|
||||
AutoDetectedGames = new ReadOnlyObservableCollection<DetectedGame>(_autoDetectedGames);
|
||||
|
||||
public void DeleteSelectedGame()
|
||||
{
|
||||
UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem
|
||||
_autoDetectedGames.Remove(SelectedDetectedGame);
|
||||
SelectedDetectedGame = AutoDetectedGames.Last();
|
||||
}
|
||||
if (AutoDetectedGames.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame)
|
||||
SelectedDetectedGame = detectedGame;
|
||||
else if (!string.IsNullOrEmpty(gameDirectory))
|
||||
AddUnknownGame(gameDirectory);
|
||||
else
|
||||
SelectedDetectedGame = AutoDetectedGames.FirstOrDefault();
|
||||
}
|
||||
|
||||
private IEnumerable<DetectedGame> EnumerateDetectedGames()
|
||||
/// <summary>
|
||||
/// dedicated to manual games
|
||||
/// </summary>
|
||||
public void AddUnknownGame(string gameName, string gameDirectory)
|
||||
{
|
||||
var game = new DetectedGame
|
||||
{
|
||||
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks");
|
||||
yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER };
|
||||
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("WorldExplorersLive", "\\WorldExplorers\\Content");
|
||||
yield return GetUnrealEngineGame("Newt", "\\g3\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content");
|
||||
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks");
|
||||
yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER };
|
||||
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks");
|
||||
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight
|
||||
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG
|
||||
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate
|
||||
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves
|
||||
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks");
|
||||
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks");
|
||||
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks");
|
||||
}
|
||||
GameName = gameName,
|
||||
GameDirectory = gameDirectory,
|
||||
IsManual = true,
|
||||
AesKeys = null,
|
||||
OverridedGame = EGame.GAME_UE4_LATEST,
|
||||
OverridedCustomVersions = null,
|
||||
OverridedOptions = null,
|
||||
CustomDirectories = new List<CustomDirectory>()
|
||||
};
|
||||
|
||||
private LauncherInstalled _launcherInstalled;
|
||||
private DetectedGame GetUnrealEngineGame(string gameName, string pakDirectory)
|
||||
UserSettings.Default.ManualGames[gameDirectory] = game;
|
||||
_autoDetectedGames.Add(game);
|
||||
SelectedDetectedGame = AutoDetectedGames.Last();
|
||||
}
|
||||
|
||||
public void AddUnknownGame(string gameDirectory)
|
||||
{
|
||||
_autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory });
|
||||
SelectedDetectedGame = AutoDetectedGames.Last();
|
||||
}
|
||||
|
||||
public void DeleteSelectedGame()
|
||||
{
|
||||
UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem
|
||||
_autoDetectedGames.Remove(SelectedDetectedGame);
|
||||
SelectedDetectedGame = AutoDetectedGames.Last();
|
||||
}
|
||||
|
||||
private IEnumerable<DetectedGame> EnumerateDetectedGames()
|
||||
{
|
||||
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks");
|
||||
yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER };
|
||||
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("WorldExplorersLive", "\\WorldExplorers\\Content");
|
||||
yield return GetUnrealEngineGame("Newt", "\\g3\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content");
|
||||
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks");
|
||||
yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER };
|
||||
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks");
|
||||
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight
|
||||
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG
|
||||
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate
|
||||
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves
|
||||
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks");
|
||||
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks");
|
||||
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks");
|
||||
}
|
||||
|
||||
private LauncherInstalled _launcherInstalled;
|
||||
private DetectedGame GetUnrealEngineGame(string gameName, string pakDirectory)
|
||||
{
|
||||
_launcherInstalled ??= GetDriveLauncherInstalls<LauncherInstalled>("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat");
|
||||
if (_launcherInstalled?.InstallationList != null)
|
||||
{
|
||||
_launcherInstalled ??= GetDriveLauncherInstalls<LauncherInstalled>("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat");
|
||||
if (_launcherInstalled?.InstallationList != null)
|
||||
foreach (var installationList in _launcherInstalled.InstallationList)
|
||||
{
|
||||
foreach (var installationList in _launcherInstalled.InstallationList)
|
||||
{
|
||||
if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase))
|
||||
return new DetectedGame { GameName = installationList.AppName, GameDirectory = $"{installationList.InstallLocation}{pakDirectory}" };
|
||||
}
|
||||
if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase))
|
||||
return new DetectedGame { GameName = installationList.AppName, GameDirectory = $"{installationList.InstallLocation}{pakDirectory}" };
|
||||
}
|
||||
|
||||
Log.Warning("Could not find {GameName} in LauncherInstalled.dat", gameName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private RiotClientInstalls _riotClientInstalls;
|
||||
private DetectedGame GetRiotGame(string gameName, string pakDirectory)
|
||||
Log.Warning("Could not find {GameName} in LauncherInstalled.dat", gameName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private RiotClientInstalls _riotClientInstalls;
|
||||
private DetectedGame GetRiotGame(string gameName, string pakDirectory)
|
||||
{
|
||||
_riotClientInstalls ??= GetDriveLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json");
|
||||
if (_riotClientInstalls is { AssociatedClient: { } })
|
||||
{
|
||||
_riotClientInstalls ??= GetDriveLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json");
|
||||
if (_riotClientInstalls is { AssociatedClient: { } })
|
||||
foreach (var (key, _) in _riotClientInstalls.AssociatedClient)
|
||||
{
|
||||
foreach (var (key, _) in _riotClientInstalls.AssociatedClient)
|
||||
{
|
||||
if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase))
|
||||
return new DetectedGame { GameName = gameName, GameDirectory = $"{key.Replace('/', '\\')}{pakDirectory}" };
|
||||
}
|
||||
if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase))
|
||||
return new DetectedGame { GameName = gameName, GameDirectory = $"{key.Replace('/', '\\')}{pakDirectory}" };
|
||||
}
|
||||
|
||||
Log.Warning("Could not find {GameName} in RiotClientInstalls.json", gameName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private LauncherSettings _launcherSettings;
|
||||
private DetectedGame GetMojangGame(string gameName, string pakDirectory)
|
||||
Log.Warning("Could not find {GameName} in RiotClientInstalls.json", gameName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private LauncherSettings _launcherSettings;
|
||||
private DetectedGame GetMojangGame(string gameName, string pakDirectory)
|
||||
{
|
||||
_launcherSettings ??= GetDataLauncherInstalls<LauncherSettings>("\\.minecraft\\launcher_settings.json");
|
||||
if (_launcherSettings is { ProductLibraryDir: { } })
|
||||
return new DetectedGame { GameName = gameName, GameDirectory = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}" };
|
||||
|
||||
Log.Warning("Could not find {GameName} in launcher_settings.json", gameName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private DetectedGame GetSteamGame(int id, string pakDirectory)
|
||||
{
|
||||
var steamInfo = SteamDetection.GetSteamGameById(id);
|
||||
if (steamInfo is not null)
|
||||
return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" };
|
||||
|
||||
Log.Warning("Could not find {GameId} in steam manifests", id);
|
||||
return null;
|
||||
}
|
||||
|
||||
private DetectedGame GetRockstarGamesGame(string key, string pakDirectory)
|
||||
{
|
||||
var installLocation = string.Empty;
|
||||
try
|
||||
{
|
||||
_launcherSettings ??= GetDataLauncherInstalls<LauncherSettings>("\\.minecraft\\launcher_settings.json");
|
||||
if (_launcherSettings is { ProductLibraryDir: { } })
|
||||
return new DetectedGame { GameName = gameName, GameDirectory = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}" };
|
||||
|
||||
Log.Warning("Could not find {GameName} in launcher_settings.json", gameName);
|
||||
return null;
|
||||
installLocation = App.GetRegistryValue(@$"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{key}", "InstallLocation", RegistryHive.LocalMachine);
|
||||
}
|
||||
|
||||
private DetectedGame GetSteamGame(int id, string pakDirectory)
|
||||
catch
|
||||
{
|
||||
var steamInfo = SteamDetection.GetSteamGameById(id);
|
||||
if (steamInfo is not null)
|
||||
return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" };
|
||||
|
||||
Log.Warning("Could not find {GameId} in steam manifests", id);
|
||||
return null;
|
||||
// ignored
|
||||
}
|
||||
|
||||
private DetectedGame GetRockstarGamesGame(string key, string pakDirectory)
|
||||
if (!string.IsNullOrEmpty(installLocation))
|
||||
return new DetectedGame { GameName = key, GameDirectory = $"{installLocation}{pakDirectory}" };
|
||||
|
||||
Log.Warning("Could not find {GameName} in the registry", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
private T GetDriveLauncherInstalls<T>(string jsonFile)
|
||||
{
|
||||
foreach (var drive in DriveInfo.GetDrives())
|
||||
{
|
||||
var installLocation = string.Empty;
|
||||
try
|
||||
{
|
||||
installLocation = App.GetRegistryValue(@$"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{key}", "InstallLocation", RegistryHive.LocalMachine);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
var launcher = $"{drive.Name}{jsonFile}";
|
||||
if (!File.Exists(launcher)) continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(installLocation))
|
||||
return new DetectedGame { GameName = key, GameDirectory = $"{installLocation}{pakDirectory}" };
|
||||
|
||||
Log.Warning("Could not find {GameName} in the registry", key);
|
||||
return null;
|
||||
Log.Information("\"{Launcher}\" found in drive \"{DriveName}\"", launcher, drive.Name);
|
||||
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
|
||||
}
|
||||
|
||||
private T GetDriveLauncherInstalls<T>(string jsonFile)
|
||||
Log.Warning("\"{JsonFile}\" not found in any drives", jsonFile);
|
||||
return default;
|
||||
}
|
||||
|
||||
private T GetDataLauncherInstalls<T>(string jsonFile)
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var launcher = $"{appData}{jsonFile}";
|
||||
if (File.Exists(launcher))
|
||||
{
|
||||
foreach (var drive in DriveInfo.GetDrives())
|
||||
{
|
||||
var launcher = $"{drive.Name}{jsonFile}";
|
||||
if (!File.Exists(launcher)) continue;
|
||||
|
||||
Log.Information("\"{Launcher}\" found in drive \"{DriveName}\"", launcher, drive.Name);
|
||||
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
|
||||
}
|
||||
|
||||
Log.Warning("\"{JsonFile}\" not found in any drives", jsonFile);
|
||||
return default;
|
||||
Log.Information("\"{Launcher}\" found in \"{AppData}\"", launcher, appData);
|
||||
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
|
||||
}
|
||||
|
||||
private T GetDataLauncherInstalls<T>(string jsonFile)
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var launcher = $"{appData}{jsonFile}";
|
||||
if (File.Exists(launcher))
|
||||
{
|
||||
Log.Information("\"{Launcher}\" found in \"{AppData}\"", launcher, appData);
|
||||
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
|
||||
}
|
||||
|
||||
Log.Warning("\"{Json}\" not found anywhere", jsonFile);
|
||||
return default;
|
||||
}
|
||||
Log.Warning("\"{Json}\" not found anywhere", jsonFile);
|
||||
return default;
|
||||
}
|
||||
|
||||
#pragma warning disable 649
|
||||
private class LauncherInstalled
|
||||
{
|
||||
public Installation[] InstallationList;
|
||||
}
|
||||
private class LauncherInstalled
|
||||
{
|
||||
public Installation[] InstallationList;
|
||||
}
|
||||
|
||||
private class Installation
|
||||
{
|
||||
public string InstallLocation;
|
||||
public string AppName;
|
||||
public string AppVersion;
|
||||
}
|
||||
private class Installation
|
||||
{
|
||||
public string InstallLocation;
|
||||
public string AppName;
|
||||
public string AppVersion;
|
||||
}
|
||||
|
||||
private class RiotClientInstalls
|
||||
{
|
||||
[JsonProperty("associated_client", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Dictionary<string, string> AssociatedClient;
|
||||
private class RiotClientInstalls
|
||||
{
|
||||
[JsonProperty("associated_client", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Dictionary<string, string> AssociatedClient;
|
||||
|
||||
[JsonProperty("patchlines", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Dictionary<string, string> Patchlines;
|
||||
[JsonProperty("patchlines", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public Dictionary<string, string> Patchlines;
|
||||
|
||||
[JsonProperty("rc_default", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string RcDefault;
|
||||
[JsonProperty("rc_default", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string RcDefault;
|
||||
|
||||
[JsonProperty("rc_live", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string RcLive;
|
||||
}
|
||||
[JsonProperty("rc_live", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string RcLive;
|
||||
}
|
||||
|
||||
private class LauncherSettings
|
||||
{
|
||||
[JsonProperty("channel", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Channel;
|
||||
private class LauncherSettings
|
||||
{
|
||||
[JsonProperty("channel", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Channel;
|
||||
|
||||
[JsonProperty("customChannels", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public object[] CustomChannels;
|
||||
[JsonProperty("customChannels", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public object[] CustomChannels;
|
||||
|
||||
[JsonProperty("deviceId", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string DeviceId;
|
||||
[JsonProperty("deviceId", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string DeviceId;
|
||||
|
||||
[JsonProperty("formatVersion", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public int FormatVersion;
|
||||
[JsonProperty("formatVersion", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public int FormatVersion;
|
||||
|
||||
[JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Locale;
|
||||
[JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Locale;
|
||||
|
||||
[JsonProperty("productLibraryDir", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string ProductLibraryDir;
|
||||
}
|
||||
[JsonProperty("productLibraryDir", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string ProductLibraryDir;
|
||||
}
|
||||
#pragma warning restore 649
|
||||
|
||||
// https://stackoverflow.com/questions/54767662/finding-game-launcher-executables-in-directory-c-sharp/67679123#67679123
|
||||
public static class SteamDetection
|
||||
// https://stackoverflow.com/questions/54767662/finding-game-launcher-executables-in-directory-c-sharp/67679123#67679123
|
||||
public static class SteamDetection
|
||||
{
|
||||
private static readonly List<AppInfo> _steamApps;
|
||||
|
||||
static SteamDetection()
|
||||
{
|
||||
private static readonly List<AppInfo> _steamApps;
|
||||
_steamApps = GetSteamApps(GetSteamLibs());
|
||||
}
|
||||
|
||||
static SteamDetection()
|
||||
public static AppInfo GetSteamGameById(int id) => _steamApps.FirstOrDefault(app => app.Id == id.ToString());
|
||||
|
||||
private static List<AppInfo> GetSteamApps(IEnumerable<string> steamLibs)
|
||||
{
|
||||
var apps = new List<AppInfo>();
|
||||
foreach (var files in steamLibs.Select(lib => Path.Combine(lib, "SteamApps")).Select(appMetaDataPath => Directory.GetFiles(appMetaDataPath, "*.acf")))
|
||||
{
|
||||
_steamApps = GetSteamApps(GetSteamLibs());
|
||||
apps.AddRange(files.Select(GetAppInfo).Where(appInfo => appInfo != null));
|
||||
}
|
||||
|
||||
public static AppInfo GetSteamGameById(int id) => _steamApps.FirstOrDefault(app => app.Id == id.ToString());
|
||||
return apps;
|
||||
}
|
||||
|
||||
private static List<AppInfo> GetSteamApps(IEnumerable<string> steamLibs)
|
||||
private static AppInfo GetAppInfo(string appMetaFile)
|
||||
{
|
||||
var fileDataLines = File.ReadAllLines(appMetaFile);
|
||||
var dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var line in fileDataLines)
|
||||
{
|
||||
var apps = new List<AppInfo>();
|
||||
foreach (var files in steamLibs.Select(lib => Path.Combine(lib, "SteamApps")).Select(appMetaDataPath => Directory.GetFiles(appMetaDataPath, "*.acf")))
|
||||
{
|
||||
apps.AddRange(files.Select(GetAppInfo).Where(appInfo => appInfo != null));
|
||||
}
|
||||
|
||||
return apps;
|
||||
var match = Regex.Match(line, @"\s*""(?<key>\w+)""\s+""(?<val>.*)""");
|
||||
if (!match.Success) continue;
|
||||
var key = match.Groups["key"].Value;
|
||||
var val = match.Groups["val"].Value;
|
||||
dic[key] = val;
|
||||
}
|
||||
|
||||
private static AppInfo GetAppInfo(string appMetaFile)
|
||||
if (dic.Keys.Count <= 0) return null;
|
||||
AppInfo appInfo = new();
|
||||
var appId = dic["appid"];
|
||||
var name = dic["name"];
|
||||
var installDir = dic["installDir"];
|
||||
|
||||
var path = Path.GetDirectoryName(appMetaFile);
|
||||
var libGameRoot = Path.Combine(path, "common", installDir);
|
||||
|
||||
if (!Directory.Exists(libGameRoot)) return null;
|
||||
|
||||
appInfo.Id = appId;
|
||||
appInfo.Name = name;
|
||||
appInfo.GameRoot = libGameRoot;
|
||||
|
||||
return appInfo;
|
||||
}
|
||||
|
||||
private static List<string> GetSteamLibs()
|
||||
{
|
||||
var steamPath = GetSteamPath();
|
||||
if (steamPath == null) return new List<string>();
|
||||
var libraries = new List<string> { steamPath };
|
||||
|
||||
var listFile = Path.Combine(steamPath, @"steamapps\libraryfolders.vdf");
|
||||
if (!File.Exists(listFile)) return new List<string>();
|
||||
var lines = File.ReadAllLines(listFile);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var fileDataLines = File.ReadAllLines(appMetaFile);
|
||||
var dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var line in fileDataLines)
|
||||
var match = Regex.Match(line, @"""(?<path>\w:\\\\.*)""");
|
||||
if (!match.Success) continue;
|
||||
var path = match.Groups["path"].Value.Replace(@"\\", @"\");
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
var match = Regex.Match(line, @"\s*""(?<key>\w+)""\s+""(?<val>.*)""");
|
||||
if (!match.Success) continue;
|
||||
var key = match.Groups["key"].Value;
|
||||
var val = match.Groups["val"].Value;
|
||||
dic[key] = val;
|
||||
libraries.Add(path);
|
||||
}
|
||||
|
||||
if (dic.Keys.Count <= 0) return null;
|
||||
AppInfo appInfo = new();
|
||||
var appId = dic["appid"];
|
||||
var name = dic["name"];
|
||||
var installDir = dic["installDir"];
|
||||
|
||||
var path = Path.GetDirectoryName(appMetaFile);
|
||||
var libGameRoot = Path.Combine(path, "common", installDir);
|
||||
|
||||
if (!Directory.Exists(libGameRoot)) return null;
|
||||
|
||||
appInfo.Id = appId;
|
||||
appInfo.Name = name;
|
||||
appInfo.GameRoot = libGameRoot;
|
||||
|
||||
return appInfo;
|
||||
}
|
||||
|
||||
private static List<string> GetSteamLibs()
|
||||
return libraries;
|
||||
}
|
||||
|
||||
private static string GetSteamPath() => (string) Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath", ""); // Win64, we don't support Win32
|
||||
|
||||
public class AppInfo
|
||||
{
|
||||
public string Id { get; internal set; }
|
||||
public string Name { get; internal set; }
|
||||
public string GameRoot { get; internal set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var steamPath = GetSteamPath();
|
||||
if (steamPath == null) return new List<string>();
|
||||
var libraries = new List<string> { steamPath };
|
||||
|
||||
var listFile = Path.Combine(steamPath, @"steamapps\libraryfolders.vdf");
|
||||
if (!File.Exists(listFile)) return new List<string>();
|
||||
var lines = File.ReadAllLines(listFile);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = Regex.Match(line, @"""(?<path>\w:\\\\.*)""");
|
||||
if (!match.Success) continue;
|
||||
var path = match.Groups["path"].Value.Replace(@"\\", @"\");
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
libraries.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
return libraries;
|
||||
}
|
||||
|
||||
private static string GetSteamPath() => (string) Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath", ""); // Win64, we don't support Win32
|
||||
|
||||
public class AppInfo
|
||||
{
|
||||
public string Id { get; internal set; }
|
||||
public string Name { get; internal set; }
|
||||
public string GameRoot { get; internal set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name} ({Id})";
|
||||
}
|
||||
return $"{Name} ({Id})";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,323 +14,324 @@ using FModel.Services;
|
|||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
|
||||
namespace FModel.ViewModels
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class SettingsViewModel : ViewModel
|
||||
{
|
||||
public class SettingsViewModel : ViewModel
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler;
|
||||
|
||||
private EUpdateMode _selectedUpdateMode;
|
||||
public EUpdateMode SelectedUpdateMode
|
||||
{
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler;
|
||||
|
||||
private EUpdateMode _selectedUpdateMode;
|
||||
public EUpdateMode SelectedUpdateMode
|
||||
{
|
||||
get => _selectedUpdateMode;
|
||||
set => SetProperty(ref _selectedUpdateMode, value);
|
||||
}
|
||||
|
||||
private string _selectedPreset;
|
||||
public string SelectedPreset
|
||||
{
|
||||
get => _selectedPreset;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedPreset, value);
|
||||
RaisePropertyChanged("EnableElements");
|
||||
}
|
||||
}
|
||||
|
||||
private ETexturePlatform _selectedUePlatform;
|
||||
public ETexturePlatform SelectedUePlatform
|
||||
{
|
||||
get => _selectedUePlatform;
|
||||
set => SetProperty(ref _selectedUePlatform, value);
|
||||
}
|
||||
|
||||
private EGame _selectedUeGame;
|
||||
public EGame SelectedUeGame
|
||||
{
|
||||
get => _selectedUeGame;
|
||||
set => SetProperty(ref _selectedUeGame, value);
|
||||
}
|
||||
|
||||
private List<FCustomVersion> _selectedCustomVersions;
|
||||
public List<FCustomVersion> SelectedCustomVersions
|
||||
{
|
||||
get => _selectedCustomVersions;
|
||||
set => SetProperty(ref _selectedCustomVersions, value);
|
||||
}
|
||||
|
||||
private Dictionary<string, bool> _selectedOptions;
|
||||
public Dictionary<string, bool> SelectedOptions
|
||||
{
|
||||
get => _selectedOptions;
|
||||
set => SetProperty(ref _selectedOptions, value);
|
||||
}
|
||||
|
||||
private ELanguage _selectedAssetLanguage;
|
||||
public ELanguage SelectedAssetLanguage
|
||||
{
|
||||
get => _selectedAssetLanguage;
|
||||
set => SetProperty(ref _selectedAssetLanguage, value);
|
||||
}
|
||||
|
||||
private EAesReload _selectedAesReload;
|
||||
public EAesReload SelectedAesReload
|
||||
{
|
||||
get => _selectedAesReload;
|
||||
set => SetProperty(ref _selectedAesReload, value);
|
||||
}
|
||||
|
||||
private EDiscordRpc _selectedDiscordRpc;
|
||||
public EDiscordRpc SelectedDiscordRpc
|
||||
{
|
||||
get => _selectedDiscordRpc;
|
||||
set => SetProperty(ref _selectedDiscordRpc, value);
|
||||
}
|
||||
|
||||
private ECompressedAudio _selectedCompressedAudio;
|
||||
public ECompressedAudio SelectedCompressedAudio
|
||||
{
|
||||
get => _selectedCompressedAudio;
|
||||
set => SetProperty(ref _selectedCompressedAudio, value);
|
||||
}
|
||||
|
||||
private EIconStyle _selectedCosmeticStyle;
|
||||
public EIconStyle SelectedCosmeticStyle
|
||||
{
|
||||
get => _selectedCosmeticStyle;
|
||||
set => SetProperty(ref _selectedCosmeticStyle, value);
|
||||
}
|
||||
|
||||
private EMeshFormat _selectedMeshExportFormat;
|
||||
public EMeshFormat SelectedMeshExportFormat
|
||||
{
|
||||
get => _selectedMeshExportFormat;
|
||||
set => SetProperty(ref _selectedMeshExportFormat, value);
|
||||
}
|
||||
|
||||
private ELodFormat _selectedLodExportFormat;
|
||||
public ELodFormat SelectedLodExportFormat
|
||||
{
|
||||
get => _selectedLodExportFormat;
|
||||
set => SetProperty(ref _selectedLodExportFormat, value);
|
||||
}
|
||||
|
||||
private ETextureFormat _selectedTextureExportFormat;
|
||||
public ETextureFormat SelectedTextureExportFormat
|
||||
{
|
||||
get => _selectedTextureExportFormat;
|
||||
set => SetProperty(ref _selectedTextureExportFormat, value);
|
||||
}
|
||||
|
||||
public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; }
|
||||
public ObservableCollection<string> Presets { get; private set; }
|
||||
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
|
||||
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
|
||||
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
|
||||
public ReadOnlyObservableCollection<EDiscordRpc> DiscordRpcs { get; private set; }
|
||||
public ReadOnlyObservableCollection<ECompressedAudio> CompressedAudios { get; private set; }
|
||||
public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; }
|
||||
public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
|
||||
|
||||
public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER;
|
||||
|
||||
private readonly FGame _game;
|
||||
private Game _gamePreset;
|
||||
private string _outputSnapshot;
|
||||
private string _rawDataSnapshot;
|
||||
private string _propertiesSnapshot;
|
||||
private string _textureSnapshot;
|
||||
private string _audioSnapshot;
|
||||
private string _modelSnapshot;
|
||||
private string _gameSnapshot;
|
||||
private EUpdateMode _updateModeSnapshot;
|
||||
private string _presetSnapshot;
|
||||
private ETexturePlatform _uePlatformSnapshot;
|
||||
private EGame _ueGameSnapshot;
|
||||
private List<FCustomVersion> _customVersionsSnapshot;
|
||||
private Dictionary<string, bool> _optionsSnapshot;
|
||||
private ELanguage _assetLanguageSnapshot;
|
||||
private ECompressedAudio _compressedAudioSnapshot;
|
||||
private EIconStyle _cosmeticStyleSnapshot;
|
||||
private EMeshFormat _meshExportFormatSnapshot;
|
||||
private ELodFormat _lodExportFormatSnapshot;
|
||||
private ETextureFormat _textureExportFormatSnapshot;
|
||||
|
||||
public SettingsViewModel(FGame game)
|
||||
{
|
||||
_game = game;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_outputSnapshot = UserSettings.Default.OutputDirectory;
|
||||
_rawDataSnapshot = UserSettings.Default.RawDataDirectory;
|
||||
_propertiesSnapshot = UserSettings.Default.PropertiesDirectory;
|
||||
_textureSnapshot = UserSettings.Default.TextureDirectory;
|
||||
_audioSnapshot = UserSettings.Default.AudioDirectory;
|
||||
_modelSnapshot = UserSettings.Default.ModelDirectory;
|
||||
_gameSnapshot = UserSettings.Default.GameDirectory;
|
||||
_updateModeSnapshot = UserSettings.Default.UpdateMode;
|
||||
_presetSnapshot = UserSettings.Default.Presets[_game];
|
||||
_uePlatformSnapshot = UserSettings.Default.OverridedPlatform;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings))
|
||||
{
|
||||
_ueGameSnapshot = settings.OverridedGame;
|
||||
_customVersionsSnapshot = settings.OverridedCustomVersions;
|
||||
_optionsSnapshot = settings.OverridedOptions;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ueGameSnapshot = UserSettings.Default.OverridedGame[_game];
|
||||
_customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game];
|
||||
_optionsSnapshot = UserSettings.Default.OverridedOptions[_game];
|
||||
}
|
||||
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
|
||||
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
|
||||
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
|
||||
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
|
||||
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
|
||||
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
|
||||
|
||||
SelectedUpdateMode = _updateModeSnapshot;
|
||||
SelectedPreset = _presetSnapshot;
|
||||
SelectedUePlatform = _uePlatformSnapshot;
|
||||
SelectedUeGame = _ueGameSnapshot;
|
||||
SelectedCustomVersions = _customVersionsSnapshot;
|
||||
SelectedOptions = _optionsSnapshot;
|
||||
SelectedAssetLanguage = _assetLanguageSnapshot;
|
||||
SelectedCompressedAudio = _compressedAudioSnapshot;
|
||||
SelectedCosmeticStyle = _cosmeticStyleSnapshot;
|
||||
SelectedMeshExportFormat = _meshExportFormatSnapshot;
|
||||
SelectedLodExportFormat = _lodExportFormatSnapshot;
|
||||
SelectedTextureExportFormat = _textureExportFormatSnapshot;
|
||||
SelectedAesReload = UserSettings.Default.AesReload;
|
||||
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
|
||||
|
||||
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
|
||||
Presets = new ObservableCollection<string>(EnumeratePresets());
|
||||
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
|
||||
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
|
||||
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
|
||||
DiscordRpcs = new ReadOnlyObservableCollection<EDiscordRpc>(new ObservableCollection<EDiscordRpc>(EnumerateDiscordRpcs()));
|
||||
CompressedAudios = new ReadOnlyObservableCollection<ECompressedAudio>(new ObservableCollection<ECompressedAudio>(EnumerateCompressedAudios()));
|
||||
CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles()));
|
||||
MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat()));
|
||||
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
|
||||
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
|
||||
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
|
||||
}
|
||||
|
||||
public async Task InitPresets(string gameName)
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(gameName)) return;
|
||||
_gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName);
|
||||
});
|
||||
|
||||
if (_gamePreset?.Versions == null) return;
|
||||
foreach (var version in _gamePreset.Versions.Keys)
|
||||
{
|
||||
Presets.Add(version);
|
||||
}
|
||||
}
|
||||
|
||||
public void SwitchPreset(string key)
|
||||
{
|
||||
if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return;
|
||||
SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST);
|
||||
|
||||
SelectedCustomVersions = new List<FCustomVersion>();
|
||||
foreach (var (guid, v) in version.CustomVersions)
|
||||
{
|
||||
SelectedCustomVersions.Add(new FCustomVersion {Key = new FGuid(guid), Version = v});
|
||||
}
|
||||
|
||||
SelectedOptions = new Dictionary<string, bool>();
|
||||
foreach (var (k, v) in version.Options)
|
||||
{
|
||||
SelectedOptions[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetPreset()
|
||||
{
|
||||
SelectedUeGame = _ueGameSnapshot;
|
||||
SelectedCustomVersions = _customVersionsSnapshot;
|
||||
SelectedOptions = _optionsSnapshot;
|
||||
}
|
||||
|
||||
public SettingsOut Save()
|
||||
{
|
||||
var ret = SettingsOut.Nothing;
|
||||
|
||||
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
|
||||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
|
||||
_outputSnapshot != UserSettings.Default.OutputDirectory || // textbox
|
||||
_rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox
|
||||
_propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox
|
||||
_textureSnapshot != UserSettings.Default.TextureDirectory || // textbox
|
||||
_audioSnapshot != UserSettings.Default.AudioDirectory || // textbox
|
||||
_modelSnapshot != UserSettings.Default.ModelDirectory || // textbox
|
||||
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
|
||||
ret = SettingsOut.Restart;
|
||||
|
||||
if (_assetLanguageSnapshot != SelectedAssetLanguage)
|
||||
ret = SettingsOut.ReloadLocres;
|
||||
|
||||
if (_updateModeSnapshot != SelectedUpdateMode)
|
||||
ret = SettingsOut.CheckForUpdates;
|
||||
|
||||
UserSettings.Default.UpdateMode = SelectedUpdateMode;
|
||||
UserSettings.Default.Presets[_game] = SelectedPreset;
|
||||
UserSettings.Default.OverridedPlatform = SelectedUePlatform;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
|
||||
{
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions;
|
||||
}
|
||||
else
|
||||
{
|
||||
UserSettings.Default.OverridedGame[_game] = SelectedUeGame;
|
||||
UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions;
|
||||
UserSettings.Default.OverridedOptions[_game] = SelectedOptions;
|
||||
}
|
||||
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
|
||||
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
|
||||
UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle;
|
||||
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
|
||||
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
|
||||
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
|
||||
UserSettings.Default.AesReload = SelectedAesReload;
|
||||
UserSettings.Default.DiscordRpc = SelectedDiscordRpc;
|
||||
|
||||
if (SelectedDiscordRpc == EDiscordRpc.Never)
|
||||
_discordHandler.Shutdown();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
|
||||
private IEnumerable<string> EnumeratePresets()
|
||||
{
|
||||
yield return Constants._NO_PRESET_TRIGGER;
|
||||
}
|
||||
private IEnumerable<EGame> EnumerateUeGames() => Enum.GetValues<EGame>();
|
||||
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
|
||||
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
|
||||
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
|
||||
private IEnumerable<ECompressedAudio> EnumerateCompressedAudios() => Enum.GetValues<ECompressedAudio>();
|
||||
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues<EIconStyle>();
|
||||
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues<EMeshFormat>();
|
||||
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
|
||||
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
|
||||
private IEnumerable<ETexturePlatform> EnumerateUePlatforms() => Enum.GetValues<ETexturePlatform>();
|
||||
get => _selectedUpdateMode;
|
||||
set => SetProperty(ref _selectedUpdateMode, value);
|
||||
}
|
||||
}
|
||||
|
||||
private string _selectedPreset;
|
||||
public string SelectedPreset
|
||||
{
|
||||
get => _selectedPreset;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedPreset, value);
|
||||
RaisePropertyChanged("EnableElements");
|
||||
}
|
||||
}
|
||||
|
||||
private ETexturePlatform _selectedUePlatform;
|
||||
public ETexturePlatform SelectedUePlatform
|
||||
{
|
||||
get => _selectedUePlatform;
|
||||
set => SetProperty(ref _selectedUePlatform, value);
|
||||
}
|
||||
|
||||
private EGame _selectedUeGame;
|
||||
public EGame SelectedUeGame
|
||||
{
|
||||
get => _selectedUeGame;
|
||||
set => SetProperty(ref _selectedUeGame, value);
|
||||
}
|
||||
|
||||
private List<FCustomVersion> _selectedCustomVersions;
|
||||
public List<FCustomVersion> SelectedCustomVersions
|
||||
{
|
||||
get => _selectedCustomVersions;
|
||||
set => SetProperty(ref _selectedCustomVersions, value);
|
||||
}
|
||||
|
||||
private Dictionary<string, bool> _selectedOptions;
|
||||
public Dictionary<string, bool> SelectedOptions
|
||||
{
|
||||
get => _selectedOptions;
|
||||
set => SetProperty(ref _selectedOptions, value);
|
||||
}
|
||||
|
||||
private ELanguage _selectedAssetLanguage;
|
||||
public ELanguage SelectedAssetLanguage
|
||||
{
|
||||
get => _selectedAssetLanguage;
|
||||
set => SetProperty(ref _selectedAssetLanguage, value);
|
||||
}
|
||||
|
||||
private EAesReload _selectedAesReload;
|
||||
public EAesReload SelectedAesReload
|
||||
{
|
||||
get => _selectedAesReload;
|
||||
set => SetProperty(ref _selectedAesReload, value);
|
||||
}
|
||||
|
||||
private EDiscordRpc _selectedDiscordRpc;
|
||||
public EDiscordRpc SelectedDiscordRpc
|
||||
{
|
||||
get => _selectedDiscordRpc;
|
||||
set => SetProperty(ref _selectedDiscordRpc, value);
|
||||
}
|
||||
|
||||
private ECompressedAudio _selectedCompressedAudio;
|
||||
public ECompressedAudio SelectedCompressedAudio
|
||||
{
|
||||
get => _selectedCompressedAudio;
|
||||
set => SetProperty(ref _selectedCompressedAudio, value);
|
||||
}
|
||||
|
||||
private EIconStyle _selectedCosmeticStyle;
|
||||
public EIconStyle SelectedCosmeticStyle
|
||||
{
|
||||
get => _selectedCosmeticStyle;
|
||||
set => SetProperty(ref _selectedCosmeticStyle, value);
|
||||
}
|
||||
|
||||
private EMeshFormat _selectedMeshExportFormat;
|
||||
public EMeshFormat SelectedMeshExportFormat
|
||||
{
|
||||
get => _selectedMeshExportFormat;
|
||||
set => SetProperty(ref _selectedMeshExportFormat, value);
|
||||
}
|
||||
|
||||
private ELodFormat _selectedLodExportFormat;
|
||||
public ELodFormat SelectedLodExportFormat
|
||||
{
|
||||
get => _selectedLodExportFormat;
|
||||
set => SetProperty(ref _selectedLodExportFormat, value);
|
||||
}
|
||||
|
||||
private ETextureFormat _selectedTextureExportFormat;
|
||||
public ETextureFormat SelectedTextureExportFormat
|
||||
{
|
||||
get => _selectedTextureExportFormat;
|
||||
set => SetProperty(ref _selectedTextureExportFormat, value);
|
||||
}
|
||||
|
||||
public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; }
|
||||
public ObservableCollection<string> Presets { get; private set; }
|
||||
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
|
||||
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
|
||||
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
|
||||
public ReadOnlyObservableCollection<EDiscordRpc> DiscordRpcs { get; private set; }
|
||||
public ReadOnlyObservableCollection<ECompressedAudio> CompressedAudios { get; private set; }
|
||||
public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; }
|
||||
public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
|
||||
|
||||
public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER;
|
||||
|
||||
private readonly FGame _game;
|
||||
private Game _gamePreset;
|
||||
private string _outputSnapshot;
|
||||
private string _rawDataSnapshot;
|
||||
private string _propertiesSnapshot;
|
||||
private string _textureSnapshot;
|
||||
private string _audioSnapshot;
|
||||
private string _modelSnapshot;
|
||||
private string _gameSnapshot;
|
||||
private EUpdateMode _updateModeSnapshot;
|
||||
private string _presetSnapshot;
|
||||
private ETexturePlatform _uePlatformSnapshot;
|
||||
private EGame _ueGameSnapshot;
|
||||
private List<FCustomVersion> _customVersionsSnapshot;
|
||||
private Dictionary<string, bool> _optionsSnapshot;
|
||||
private ELanguage _assetLanguageSnapshot;
|
||||
private ECompressedAudio _compressedAudioSnapshot;
|
||||
private EIconStyle _cosmeticStyleSnapshot;
|
||||
private EMeshFormat _meshExportFormatSnapshot;
|
||||
private ELodFormat _lodExportFormatSnapshot;
|
||||
private ETextureFormat _textureExportFormatSnapshot;
|
||||
|
||||
public SettingsViewModel(FGame game)
|
||||
{
|
||||
_game = game;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_outputSnapshot = UserSettings.Default.OutputDirectory;
|
||||
_rawDataSnapshot = UserSettings.Default.RawDataDirectory;
|
||||
_propertiesSnapshot = UserSettings.Default.PropertiesDirectory;
|
||||
_textureSnapshot = UserSettings.Default.TextureDirectory;
|
||||
_audioSnapshot = UserSettings.Default.AudioDirectory;
|
||||
_modelSnapshot = UserSettings.Default.ModelDirectory;
|
||||
_gameSnapshot = UserSettings.Default.GameDirectory;
|
||||
_updateModeSnapshot = UserSettings.Default.UpdateMode;
|
||||
_presetSnapshot = UserSettings.Default.Presets[_game];
|
||||
_uePlatformSnapshot = UserSettings.Default.OverridedPlatform;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings))
|
||||
{
|
||||
_ueGameSnapshot = settings.OverridedGame;
|
||||
_customVersionsSnapshot = settings.OverridedCustomVersions;
|
||||
_optionsSnapshot = settings.OverridedOptions;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ueGameSnapshot = UserSettings.Default.OverridedGame[_game];
|
||||
_customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game];
|
||||
_optionsSnapshot = UserSettings.Default.OverridedOptions[_game];
|
||||
}
|
||||
|
||||
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
|
||||
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
|
||||
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
|
||||
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
|
||||
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
|
||||
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
|
||||
|
||||
SelectedUpdateMode = _updateModeSnapshot;
|
||||
SelectedPreset = _presetSnapshot;
|
||||
SelectedUePlatform = _uePlatformSnapshot;
|
||||
SelectedUeGame = _ueGameSnapshot;
|
||||
SelectedCustomVersions = _customVersionsSnapshot;
|
||||
SelectedOptions = _optionsSnapshot;
|
||||
SelectedAssetLanguage = _assetLanguageSnapshot;
|
||||
SelectedCompressedAudio = _compressedAudioSnapshot;
|
||||
SelectedCosmeticStyle = _cosmeticStyleSnapshot;
|
||||
SelectedMeshExportFormat = _meshExportFormatSnapshot;
|
||||
SelectedLodExportFormat = _lodExportFormatSnapshot;
|
||||
SelectedTextureExportFormat = _textureExportFormatSnapshot;
|
||||
SelectedAesReload = UserSettings.Default.AesReload;
|
||||
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
|
||||
|
||||
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
|
||||
Presets = new ObservableCollection<string>(EnumeratePresets());
|
||||
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
|
||||
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
|
||||
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
|
||||
DiscordRpcs = new ReadOnlyObservableCollection<EDiscordRpc>(new ObservableCollection<EDiscordRpc>(EnumerateDiscordRpcs()));
|
||||
CompressedAudios = new ReadOnlyObservableCollection<ECompressedAudio>(new ObservableCollection<ECompressedAudio>(EnumerateCompressedAudios()));
|
||||
CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles()));
|
||||
MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat()));
|
||||
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
|
||||
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
|
||||
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
|
||||
}
|
||||
|
||||
public async Task InitPresets(string gameName)
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(gameName)) return;
|
||||
_gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName);
|
||||
});
|
||||
|
||||
if (_gamePreset?.Versions == null) return;
|
||||
foreach (var version in _gamePreset.Versions.Keys)
|
||||
{
|
||||
Presets.Add(version);
|
||||
}
|
||||
}
|
||||
|
||||
public void SwitchPreset(string key)
|
||||
{
|
||||
if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return;
|
||||
SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST);
|
||||
|
||||
SelectedCustomVersions = new List<FCustomVersion>();
|
||||
foreach (var (guid, v) in version.CustomVersions)
|
||||
{
|
||||
SelectedCustomVersions.Add(new FCustomVersion { Key = new FGuid(guid), Version = v });
|
||||
}
|
||||
|
||||
SelectedOptions = new Dictionary<string, bool>();
|
||||
foreach (var (k, v) in version.Options)
|
||||
{
|
||||
SelectedOptions[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetPreset()
|
||||
{
|
||||
SelectedUeGame = _ueGameSnapshot;
|
||||
SelectedCustomVersions = _customVersionsSnapshot;
|
||||
SelectedOptions = _optionsSnapshot;
|
||||
}
|
||||
|
||||
public SettingsOut Save()
|
||||
{
|
||||
var ret = SettingsOut.Nothing;
|
||||
|
||||
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
|
||||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
|
||||
_outputSnapshot != UserSettings.Default.OutputDirectory || // textbox
|
||||
_rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox
|
||||
_propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox
|
||||
_textureSnapshot != UserSettings.Default.TextureDirectory || // textbox
|
||||
_audioSnapshot != UserSettings.Default.AudioDirectory || // textbox
|
||||
_modelSnapshot != UserSettings.Default.ModelDirectory || // textbox
|
||||
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
|
||||
ret = SettingsOut.Restart;
|
||||
|
||||
if (_assetLanguageSnapshot != SelectedAssetLanguage)
|
||||
ret = SettingsOut.ReloadLocres;
|
||||
|
||||
if (_updateModeSnapshot != SelectedUpdateMode)
|
||||
ret = SettingsOut.CheckForUpdates;
|
||||
|
||||
UserSettings.Default.UpdateMode = SelectedUpdateMode;
|
||||
UserSettings.Default.Presets[_game] = SelectedPreset;
|
||||
UserSettings.Default.OverridedPlatform = SelectedUePlatform;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
|
||||
{
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions;
|
||||
}
|
||||
else
|
||||
{
|
||||
UserSettings.Default.OverridedGame[_game] = SelectedUeGame;
|
||||
UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions;
|
||||
UserSettings.Default.OverridedOptions[_game] = SelectedOptions;
|
||||
}
|
||||
|
||||
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
|
||||
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
|
||||
UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle;
|
||||
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
|
||||
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
|
||||
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
|
||||
UserSettings.Default.AesReload = SelectedAesReload;
|
||||
UserSettings.Default.DiscordRpc = SelectedDiscordRpc;
|
||||
|
||||
if (SelectedDiscordRpc == EDiscordRpc.Never)
|
||||
_discordHandler.Shutdown();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
|
||||
private IEnumerable<string> EnumeratePresets()
|
||||
{
|
||||
yield return Constants._NO_PRESET_TRIGGER;
|
||||
}
|
||||
private IEnumerable<EGame> EnumerateUeGames() => Enum.GetValues<EGame>();
|
||||
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
|
||||
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
|
||||
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
|
||||
private IEnumerable<ECompressedAudio> EnumerateCompressedAudios() => Enum.GetValues<ECompressedAudio>();
|
||||
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues<EIconStyle>();
|
||||
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues<EMeshFormat>();
|
||||
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
|
||||
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
|
||||
private IEnumerable<ETexturePlatform> EnumerateUePlatforms() => Enum.GetValues<ETexturePlatform>();
|
||||
}
|
||||
|
|
@ -18,430 +18,437 @@ using System.Windows.Media.Imaging;
|
|||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
|
||||
namespace FModel.ViewModels
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class TabImage : ViewModel
|
||||
{
|
||||
public class TabImage : ViewModel
|
||||
public string ExportName { get; }
|
||||
public byte[] ImageBuffer { get; set; }
|
||||
|
||||
public TabImage(string name, bool rnn, SKBitmap img)
|
||||
{
|
||||
public string ExportName { get; }
|
||||
public byte[] ImageBuffer { get; set; }
|
||||
|
||||
public TabImage(string name, bool rnn, SKBitmap img)
|
||||
{
|
||||
ExportName = name;
|
||||
RenderNearestNeighbor = rnn;
|
||||
SetImage(img);
|
||||
}
|
||||
|
||||
private BitmapImage _image;
|
||||
public BitmapImage Image
|
||||
{
|
||||
get => _image;
|
||||
set
|
||||
{
|
||||
if (_image == value) return;
|
||||
SetProperty(ref _image, value);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _renderNearestNeighbor;
|
||||
public bool RenderNearestNeighbor
|
||||
{
|
||||
get => _renderNearestNeighbor;
|
||||
set => SetProperty(ref _renderNearestNeighbor, value);
|
||||
}
|
||||
|
||||
private bool _noAlpha;
|
||||
public bool NoAlpha
|
||||
{
|
||||
get => _noAlpha;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _noAlpha, value);
|
||||
ResetImage();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetImage(SKBitmap bitmap)
|
||||
{
|
||||
_bmp = bitmap;
|
||||
|
||||
using var data = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100);
|
||||
using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false);
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.StreamSource = stream;
|
||||
image.EndInit();
|
||||
image.Freeze();
|
||||
Image = image;
|
||||
}
|
||||
|
||||
private SKBitmap _bmp;
|
||||
private void ResetImage() => SetImage(_bmp);
|
||||
ExportName = name;
|
||||
RenderNearestNeighbor = rnn;
|
||||
SetImage(img);
|
||||
}
|
||||
|
||||
public class TabItem : ViewModel
|
||||
private BitmapImage _image;
|
||||
public BitmapImage Image
|
||||
{
|
||||
private string _header;
|
||||
public string Header
|
||||
get => _image;
|
||||
set
|
||||
{
|
||||
get => _header;
|
||||
set => SetProperty(ref _header, value);
|
||||
}
|
||||
|
||||
private string _directory;
|
||||
public string Directory
|
||||
{
|
||||
get => _directory;
|
||||
set => SetProperty(ref _directory, value);
|
||||
}
|
||||
|
||||
private bool _hasSearchOpen;
|
||||
public bool HasSearchOpen
|
||||
{
|
||||
get => _hasSearchOpen;
|
||||
set => SetProperty(ref _hasSearchOpen, value);
|
||||
}
|
||||
|
||||
private string _textToFind;
|
||||
public string TextToFind
|
||||
{
|
||||
get => _textToFind;
|
||||
set => SetProperty(ref _textToFind, value);
|
||||
}
|
||||
|
||||
private bool _searchUp;
|
||||
public bool SearchUp
|
||||
{
|
||||
get => _searchUp;
|
||||
set => SetProperty(ref _searchUp, value);
|
||||
}
|
||||
|
||||
private bool _caseSensitive;
|
||||
public bool CaseSensitive
|
||||
{
|
||||
get => _caseSensitive;
|
||||
set => SetProperty(ref _caseSensitive, value);
|
||||
}
|
||||
|
||||
private bool _useRegEx;
|
||||
public bool UseRegEx
|
||||
{
|
||||
get => _useRegEx;
|
||||
set => SetProperty(ref _useRegEx, value);
|
||||
}
|
||||
|
||||
private bool _wholeWord;
|
||||
public bool WholeWord
|
||||
{
|
||||
get => _wholeWord;
|
||||
set => SetProperty(ref _wholeWord, value);
|
||||
}
|
||||
|
||||
private TextDocument _document;
|
||||
public TextDocument Document
|
||||
{
|
||||
get => _document;
|
||||
set => SetProperty(ref _document, value);
|
||||
}
|
||||
|
||||
private double _fontSize = 11.0;
|
||||
public double FontSize
|
||||
{
|
||||
get => _fontSize;
|
||||
set => SetProperty(ref _fontSize, value);
|
||||
}
|
||||
|
||||
private double _scrollPosition;
|
||||
public double ScrollPosition
|
||||
{
|
||||
get => _scrollPosition;
|
||||
set => SetProperty(ref _scrollPosition, value);
|
||||
}
|
||||
|
||||
private string _scrollTrigger;
|
||||
public string ScrollTrigger
|
||||
{
|
||||
get => _scrollTrigger;
|
||||
set => SetProperty(ref _scrollTrigger, value);
|
||||
}
|
||||
|
||||
private IHighlightingDefinition _highlighter;
|
||||
public IHighlightingDefinition Highlighter
|
||||
{
|
||||
get => _highlighter;
|
||||
set
|
||||
{
|
||||
if (_highlighter == value) return;
|
||||
SetProperty(ref _highlighter, value);
|
||||
}
|
||||
}
|
||||
|
||||
private TabImage _selectedImage;
|
||||
public TabImage SelectedImage
|
||||
{
|
||||
get => _selectedImage;
|
||||
set
|
||||
{
|
||||
if (_selectedImage == value) return;
|
||||
SetProperty(ref _selectedImage, value);
|
||||
RaisePropertyChanged("HasImage");
|
||||
RaisePropertyChanged("Page");
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasImage => SelectedImage != null;
|
||||
public bool HasMultipleImages => _images.Count > 1;
|
||||
public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}";
|
||||
|
||||
private readonly ObservableCollection<TabImage> _images;
|
||||
|
||||
public bool ShouldScroll => !string.IsNullOrEmpty(ScrollTrigger);
|
||||
|
||||
private TabCommand _tabCommand;
|
||||
public TabCommand TabCommand => _tabCommand ??= new TabCommand(this);
|
||||
private ImageCommand _imageCommand;
|
||||
public ImageCommand ImageCommand => _imageCommand ??= new ImageCommand(this);
|
||||
private GoToCommand _goToCommand;
|
||||
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
|
||||
|
||||
public TabItem(string header, string directory)
|
||||
{
|
||||
Header = header;
|
||||
Directory = directory;
|
||||
_images = new ObservableCollection<TabImage>();
|
||||
}
|
||||
|
||||
public void ClearImages()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
_images.Clear();
|
||||
SelectedImage = null;
|
||||
RaisePropertyChanged("HasMultipleImages");
|
||||
});
|
||||
}
|
||||
|
||||
public void AddImage(UTexture2D texture) => AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform));
|
||||
public void AddImage(string name, bool rnn, SKBitmap[] img)
|
||||
{
|
||||
foreach (var i in img) AddImage(name, rnn, i);
|
||||
}
|
||||
public void AddImage(string name, bool rnn, SKBitmap img)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var t = new TabImage(name, rnn, img);
|
||||
if (UserSettings.Default.IsAutoSaveTextures)
|
||||
SaveImage(t, true);
|
||||
|
||||
_images.Add(t);
|
||||
SelectedImage ??= t;
|
||||
RaisePropertyChanged("Page");
|
||||
RaisePropertyChanged("HasMultipleImages");
|
||||
});
|
||||
}
|
||||
|
||||
public void GoPreviousImage() => SelectedImage = _images.Previous(SelectedImage);
|
||||
public void GoNextImage() => SelectedImage = _images.Next(SelectedImage);
|
||||
|
||||
public void SetDocumentText(string text, bool bulkSave)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Document ??= new TextDocument();
|
||||
Document.Text = text;
|
||||
|
||||
if (UserSettings.Default.IsAutoSaveProps || bulkSave)
|
||||
SaveProperty(true);
|
||||
});
|
||||
}
|
||||
public void ResetDocumentText()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Document ??= new TextDocument();
|
||||
Document.Text = string.Empty;
|
||||
});
|
||||
}
|
||||
|
||||
public void SaveImage(bool autoSave) => SaveImage(SelectedImage, autoSave);
|
||||
private void SaveImage(TabImage image, bool autoSave)
|
||||
{
|
||||
if (image == null) return;
|
||||
var fileName = $"{image.ExportName}.png";
|
||||
var directory = Path.Combine(UserSettings.Default.TextureDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
|
||||
|
||||
if (!autoSave)
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Save Texture",
|
||||
FileName = fileName,
|
||||
InitialDirectory = UserSettings.Default.TextureDirectory,
|
||||
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
|
||||
};
|
||||
var result = saveFileDialog.ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
directory = saveFileDialog.FileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
||||
}
|
||||
|
||||
using (var fs = new FileStream(directory, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length);
|
||||
}
|
||||
|
||||
SaveCheck(directory, fileName);
|
||||
}
|
||||
|
||||
public void SaveProperty(bool autoSave)
|
||||
{
|
||||
var fileName = Path.ChangeExtension(Header, ".json");
|
||||
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/');
|
||||
|
||||
if (!autoSave)
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Save Property",
|
||||
FileName = fileName,
|
||||
InitialDirectory = UserSettings.Default.PropertiesDirectory,
|
||||
Filter = "JSON Files (*.json)|*.json|INI Files (*.ini)|*.ini|XML Files (*.xml)|*.xml|All Files (*.*)|*.*"
|
||||
};
|
||||
var result = saveFileDialog.ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
directory = saveFileDialog.FileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
||||
}
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
|
||||
SaveCheck(directory, fileName);
|
||||
}
|
||||
|
||||
private void SaveCheck(string path, string fileName)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Log.Information("{FileName} successfully saved", fileName);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("{FileName} could not be saved", fileName);
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
|
||||
}
|
||||
if (_image == value) return;
|
||||
SetProperty(ref _image, value);
|
||||
}
|
||||
}
|
||||
|
||||
public class TabControlViewModel : ViewModel
|
||||
private bool _renderNearestNeighbor;
|
||||
public bool RenderNearestNeighbor
|
||||
{
|
||||
private TabItem _selectedTab;
|
||||
public TabItem SelectedTab
|
||||
get => _renderNearestNeighbor;
|
||||
set => SetProperty(ref _renderNearestNeighbor, value);
|
||||
}
|
||||
|
||||
private bool _noAlpha;
|
||||
public bool NoAlpha
|
||||
{
|
||||
get => _noAlpha;
|
||||
set
|
||||
{
|
||||
get => _selectedTab;
|
||||
set => SetProperty(ref _selectedTab, value);
|
||||
SetProperty(ref _noAlpha, value);
|
||||
ResetImage();
|
||||
}
|
||||
}
|
||||
|
||||
private AddTabCommand _addTabCommand;
|
||||
public AddTabCommand AddTabCommand => _addTabCommand ??= new AddTabCommand(this);
|
||||
private void SetImage(SKBitmap bitmap)
|
||||
{
|
||||
_bmp = bitmap;
|
||||
using var data = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100);
|
||||
using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false);
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.StreamSource = stream;
|
||||
image.EndInit();
|
||||
image.Freeze();
|
||||
Image = image;
|
||||
}
|
||||
|
||||
private readonly ObservableCollection<TabItem> _tabItems;
|
||||
public ReadOnlyObservableCollection<TabItem> TabsItems { get; }
|
||||
private SKBitmap _bmp;
|
||||
private void ResetImage() => SetImage(_bmp);
|
||||
}
|
||||
|
||||
public bool HasNoTabs => _tabItems.Count == 0;
|
||||
public bool CanAddTabs => _tabItems.Count < 25;
|
||||
public class TabItem : ViewModel
|
||||
{
|
||||
private string _header;
|
||||
public string Header
|
||||
{
|
||||
get => _header;
|
||||
set => SetProperty(ref _header, value);
|
||||
}
|
||||
|
||||
public TabControlViewModel()
|
||||
private string _directory;
|
||||
public string Directory
|
||||
{
|
||||
get => _directory;
|
||||
set => SetProperty(ref _directory, value);
|
||||
}
|
||||
|
||||
private bool _hasSearchOpen;
|
||||
public bool HasSearchOpen
|
||||
{
|
||||
get => _hasSearchOpen;
|
||||
set => SetProperty(ref _hasSearchOpen, value);
|
||||
}
|
||||
|
||||
private string _textToFind;
|
||||
public string TextToFind
|
||||
{
|
||||
get => _textToFind;
|
||||
set => SetProperty(ref _textToFind, value);
|
||||
}
|
||||
|
||||
private bool _searchUp;
|
||||
public bool SearchUp
|
||||
{
|
||||
get => _searchUp;
|
||||
set => SetProperty(ref _searchUp, value);
|
||||
}
|
||||
|
||||
private bool _caseSensitive;
|
||||
public bool CaseSensitive
|
||||
{
|
||||
get => _caseSensitive;
|
||||
set => SetProperty(ref _caseSensitive, value);
|
||||
}
|
||||
|
||||
private bool _useRegEx;
|
||||
public bool UseRegEx
|
||||
{
|
||||
get => _useRegEx;
|
||||
set => SetProperty(ref _useRegEx, value);
|
||||
}
|
||||
|
||||
private bool _wholeWord;
|
||||
public bool WholeWord
|
||||
{
|
||||
get => _wholeWord;
|
||||
set => SetProperty(ref _wholeWord, value);
|
||||
}
|
||||
|
||||
private TextDocument _document;
|
||||
public TextDocument Document
|
||||
{
|
||||
get => _document;
|
||||
set => SetProperty(ref _document, value);
|
||||
}
|
||||
|
||||
private double _fontSize = 11.0;
|
||||
public double FontSize
|
||||
{
|
||||
get => _fontSize;
|
||||
set => SetProperty(ref _fontSize, value);
|
||||
}
|
||||
|
||||
private double _scrollPosition;
|
||||
public double ScrollPosition
|
||||
{
|
||||
get => _scrollPosition;
|
||||
set => SetProperty(ref _scrollPosition, value);
|
||||
}
|
||||
|
||||
private string _scrollTrigger;
|
||||
public string ScrollTrigger
|
||||
{
|
||||
get => _scrollTrigger;
|
||||
set => SetProperty(ref _scrollTrigger, value);
|
||||
}
|
||||
|
||||
private IHighlightingDefinition _highlighter;
|
||||
public IHighlightingDefinition Highlighter
|
||||
{
|
||||
get => _highlighter;
|
||||
set
|
||||
{
|
||||
_tabItems = new ObservableCollection<TabItem>(EnumerateTabs());
|
||||
TabsItems = new ReadOnlyObservableCollection<TabItem>(_tabItems);
|
||||
SelectedTab = TabsItems.FirstOrDefault();
|
||||
if (_highlighter == value) return;
|
||||
SetProperty(ref _highlighter, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddTab(string header = null, string directory = null)
|
||||
private TabImage _selectedImage;
|
||||
public TabImage SelectedImage
|
||||
{
|
||||
get => _selectedImage;
|
||||
set
|
||||
{
|
||||
if (!CanAddTabs) return;
|
||||
if (_selectedImage == value) return;
|
||||
SetProperty(ref _selectedImage, value);
|
||||
RaisePropertyChanged("HasImage");
|
||||
RaisePropertyChanged("Page");
|
||||
}
|
||||
}
|
||||
|
||||
var h = header ?? "New Tab";
|
||||
var d = directory ?? string.Empty;
|
||||
if (SelectedTab is { Header : "New Tab" })
|
||||
public bool HasImage => SelectedImage != null;
|
||||
public bool HasMultipleImages => _images.Count > 1;
|
||||
public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}";
|
||||
|
||||
private readonly ObservableCollection<TabImage> _images;
|
||||
|
||||
public bool ShouldScroll => !string.IsNullOrEmpty(ScrollTrigger);
|
||||
|
||||
private TabCommand _tabCommand;
|
||||
public TabCommand TabCommand => _tabCommand ??= new TabCommand(this);
|
||||
private ImageCommand _imageCommand;
|
||||
public ImageCommand ImageCommand => _imageCommand ??= new ImageCommand(this);
|
||||
private GoToCommand _goToCommand;
|
||||
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
|
||||
|
||||
public TabItem(string header, string directory)
|
||||
{
|
||||
Header = header;
|
||||
Directory = directory;
|
||||
_images = new ObservableCollection<TabImage>();
|
||||
}
|
||||
|
||||
public void ClearImages()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
_images.Clear();
|
||||
SelectedImage = null;
|
||||
RaisePropertyChanged("HasMultipleImages");
|
||||
});
|
||||
}
|
||||
|
||||
public void AddImage(UTexture2D texture) => AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform));
|
||||
|
||||
public void AddImage(string name, bool rnn, SKBitmap[] img)
|
||||
{
|
||||
foreach (var i in img) AddImage(name, rnn, i);
|
||||
}
|
||||
|
||||
public void AddImage(string name, bool rnn, SKBitmap img)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var t = new TabImage(name, rnn, img);
|
||||
if (UserSettings.Default.IsAutoSaveTextures)
|
||||
SaveImage(t, true);
|
||||
|
||||
_images.Add(t);
|
||||
SelectedImage ??= t;
|
||||
RaisePropertyChanged("Page");
|
||||
RaisePropertyChanged("HasMultipleImages");
|
||||
});
|
||||
}
|
||||
|
||||
public void GoPreviousImage() => SelectedImage = _images.Previous(SelectedImage);
|
||||
public void GoNextImage() => SelectedImage = _images.Next(SelectedImage);
|
||||
|
||||
public void SetDocumentText(string text, bool bulkSave)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Document ??= new TextDocument();
|
||||
Document.Text = text;
|
||||
|
||||
if (UserSettings.Default.IsAutoSaveProps || bulkSave)
|
||||
SaveProperty(true);
|
||||
});
|
||||
}
|
||||
|
||||
public void ResetDocumentText()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Document ??= new TextDocument();
|
||||
Document.Text = string.Empty;
|
||||
});
|
||||
}
|
||||
|
||||
public void SaveImage(bool autoSave) => SaveImage(SelectedImage, autoSave);
|
||||
|
||||
private void SaveImage(TabImage image, bool autoSave)
|
||||
{
|
||||
if (image == null) return;
|
||||
var fileName = $"{image.ExportName}.png";
|
||||
var directory = Path.Combine(UserSettings.Default.TextureDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
|
||||
|
||||
if (!autoSave)
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
SelectedTab.Header = h;
|
||||
SelectedTab.Directory = d;
|
||||
return;
|
||||
}
|
||||
Title = "Save Texture",
|
||||
FileName = fileName,
|
||||
InitialDirectory = UserSettings.Default.TextureDirectory,
|
||||
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
|
||||
};
|
||||
var result = saveFileDialog.ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
directory = saveFileDialog.FileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
||||
}
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
using (var fs = new FileStream(directory, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length);
|
||||
}
|
||||
|
||||
SaveCheck(directory, fileName);
|
||||
}
|
||||
|
||||
public void SaveProperty(bool autoSave)
|
||||
{
|
||||
var fileName = Path.ChangeExtension(Header, ".json");
|
||||
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/');
|
||||
|
||||
if (!autoSave)
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
_tabItems.Add(new TabItem(h, d));
|
||||
SelectedTab = _tabItems.Last();
|
||||
});
|
||||
Title = "Save Property",
|
||||
FileName = fileName,
|
||||
InitialDirectory = UserSettings.Default.PropertiesDirectory,
|
||||
Filter = "JSON Files (*.json)|*.json|INI Files (*.ini)|*.ini|XML Files (*.xml)|*.xml|All Files (*.*)|*.*"
|
||||
};
|
||||
var result = saveFileDialog.ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
directory = saveFileDialog.FileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
||||
}
|
||||
|
||||
public void RemoveTab(TabItem tab = null)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var tabCount = _tabItems.Count;
|
||||
var tabToDelete = tab ?? SelectedTab;
|
||||
switch (tabCount)
|
||||
{
|
||||
case <= 0:
|
||||
return;
|
||||
// select previous tab before deleting current to avoid "ScrollToZero" issue on tab delete
|
||||
case > 1:
|
||||
SelectedTab = _tabItems.Previous(tabToDelete); // will select last if previous is -1 but who cares anyway, still better than having +1 to scroll 0
|
||||
break;
|
||||
}
|
||||
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
|
||||
SaveCheck(directory, fileName);
|
||||
}
|
||||
|
||||
_tabItems.Remove(tabToDelete);
|
||||
OnTabRemove?.Invoke(this, new TabEventArgs(tabToDelete));
|
||||
});
|
||||
private void SaveCheck(string path, string fileName)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Log.Information("{FileName} successfully saved", fileName);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
|
||||
}
|
||||
|
||||
public class TabEventArgs : EventArgs
|
||||
else
|
||||
{
|
||||
public TabItem TabToRemove { get; set; }
|
||||
public TabEventArgs(TabItem tab) { TabToRemove = tab; }
|
||||
}
|
||||
public event EventHandler OnTabRemove;
|
||||
public void GoLeftTab() => SelectedTab = _tabItems.Previous(SelectedTab);
|
||||
public void GoRightTab() => SelectedTab = _tabItems.Next(SelectedTab);
|
||||
|
||||
public void RemoveOtherTabs(TabItem tab)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
foreach (var t in _tabItems.Where(t => t != tab).ToList())
|
||||
{
|
||||
_tabItems.Remove(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveAllTabs()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
SelectedTab = null;
|
||||
_tabItems.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
private static IEnumerable<TabItem> EnumerateTabs()
|
||||
{
|
||||
yield return new TabItem("New Tab", string.Empty);
|
||||
Log.Error("{FileName} could not be saved", fileName);
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TabControlViewModel : ViewModel
|
||||
{
|
||||
private TabItem _selectedTab;
|
||||
public TabItem SelectedTab
|
||||
{
|
||||
get => _selectedTab;
|
||||
set => SetProperty(ref _selectedTab, value);
|
||||
}
|
||||
|
||||
private AddTabCommand _addTabCommand;
|
||||
public AddTabCommand AddTabCommand => _addTabCommand ??= new AddTabCommand(this);
|
||||
|
||||
private readonly ObservableCollection<TabItem> _tabItems;
|
||||
public ReadOnlyObservableCollection<TabItem> TabsItems { get; }
|
||||
|
||||
public bool HasNoTabs => _tabItems.Count == 0;
|
||||
public bool CanAddTabs => _tabItems.Count < 25;
|
||||
|
||||
public TabControlViewModel()
|
||||
{
|
||||
_tabItems = new ObservableCollection<TabItem>(EnumerateTabs());
|
||||
TabsItems = new ReadOnlyObservableCollection<TabItem>(_tabItems);
|
||||
SelectedTab = TabsItems.FirstOrDefault();
|
||||
}
|
||||
|
||||
public void AddTab(string header = null, string directory = null)
|
||||
{
|
||||
if (!CanAddTabs) return;
|
||||
|
||||
var h = header ?? "New Tab";
|
||||
var d = directory ?? string.Empty;
|
||||
if (SelectedTab is { Header : "New Tab" })
|
||||
{
|
||||
SelectedTab.Header = h;
|
||||
SelectedTab.Directory = d;
|
||||
return;
|
||||
}
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
_tabItems.Add(new TabItem(h, d));
|
||||
SelectedTab = _tabItems.Last();
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveTab(TabItem tab = null)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var tabCount = _tabItems.Count;
|
||||
var tabToDelete = tab ?? SelectedTab;
|
||||
switch (tabCount)
|
||||
{
|
||||
case <= 0:
|
||||
return;
|
||||
// select previous tab before deleting current to avoid "ScrollToZero" issue on tab delete
|
||||
case > 1:
|
||||
SelectedTab = _tabItems.Previous(tabToDelete); // will select last if previous is -1 but who cares anyway, still better than having +1 to scroll 0
|
||||
break;
|
||||
}
|
||||
|
||||
_tabItems.Remove(tabToDelete);
|
||||
OnTabRemove?.Invoke(this, new TabEventArgs(tabToDelete));
|
||||
});
|
||||
}
|
||||
|
||||
public class TabEventArgs : EventArgs
|
||||
{
|
||||
public TabItem TabToRemove { get; set; }
|
||||
|
||||
public TabEventArgs(TabItem tab)
|
||||
{
|
||||
TabToRemove = tab;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler OnTabRemove;
|
||||
public void GoLeftTab() => SelectedTab = _tabItems.Previous(SelectedTab);
|
||||
public void GoRightTab() => SelectedTab = _tabItems.Next(SelectedTab);
|
||||
|
||||
public void RemoveOtherTabs(TabItem tab)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
foreach (var t in _tabItems.Where(t => t != tab).ToList())
|
||||
{
|
||||
_tabItems.Remove(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveAllTabs()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
SelectedTab = null;
|
||||
_tabItems.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
private static IEnumerable<TabItem> EnumerateTabs()
|
||||
{
|
||||
yield return new TabItem("New Tab", string.Empty);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
public AudioPlayer()
|
||||
{
|
||||
DataContext = _applicationView;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void Load(byte[] data, string filePath)
|
||||
{
|
||||
_applicationView.AudioPlayer.AddToPlaylist(data, filePath);
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, CancelEventArgs e)
|
||||
{
|
||||
_applicationView.AudioPlayer.Stop();
|
||||
_applicationView.AudioPlayer.Dispose();
|
||||
DiscordService.DiscordHandler.UpdateToSavedPresence();
|
||||
}
|
||||
|
||||
private void OnDeviceSwap(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is not ComboBox {SelectedItem: MMDevice selectedDevice})
|
||||
return;
|
||||
|
||||
UserSettings.Default.AudioDeviceId = selectedDevice.DeviceID;
|
||||
_applicationView.AudioPlayer.Device();
|
||||
}
|
||||
|
||||
private void OnVolumeChange(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_applicationView.AudioPlayer.Volume();
|
||||
}
|
||||
|
||||
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is TextBox)
|
||||
return;
|
||||
|
||||
if (UserSettings.Default.AddAudio.IsTriggered(e.Key))
|
||||
{
|
||||
var openFileDialog = new OpenFileDialog
|
||||
{
|
||||
Title = "Select an audio file",
|
||||
InitialDirectory = UserSettings.Default.AudioDirectory,
|
||||
Filter = "OGG Files (*.ogg)|*.ogg|WAV Files (*.wav)|*.wav|WEM Files (*.wem)|*.wem|ADPCM Files (*.adpcm)|*.adpcm|All Files (*.*)|*.*",
|
||||
Multiselect = true
|
||||
};
|
||||
|
||||
if (!(bool) openFileDialog.ShowDialog()) return;
|
||||
foreach (var file in openFileDialog.FileNames)
|
||||
{
|
||||
_applicationView.AudioPlayer.AddToPlaylist(file);
|
||||
}
|
||||
}
|
||||
else if (UserSettings.Default.PlayPauseAudio.IsTriggered(e.Key))
|
||||
_applicationView.AudioPlayer.PlayPauseOnStart();
|
||||
else if (UserSettings.Default.PreviousAudio.IsTriggered(e.Key))
|
||||
_applicationView.AudioPlayer.Previous();
|
||||
else if (UserSettings.Default.NextAudio.IsTriggered(e.Key))
|
||||
_applicationView.AudioPlayer.Next();
|
||||
}
|
||||
|
||||
private void OnAudioFileMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_applicationView.AudioPlayer.PlayPauseOnForce();
|
||||
}
|
||||
|
||||
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (sender is not TextBox textBox)
|
||||
return;
|
||||
|
||||
var filters = textBox.Text.Trim().Split(' ');
|
||||
_applicationView.AudioPlayer.AudioFilesView.Filter = o =>
|
||||
{
|
||||
return o is AudioFile audio && filters.All(x => audio.FileName.Contains(x, StringComparison.OrdinalIgnoreCase));
|
||||
};
|
||||
}
|
||||
DataContext = _applicationView;
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(byte[] data, string filePath)
|
||||
{
|
||||
_applicationView.AudioPlayer.AddToPlaylist(data, filePath);
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, CancelEventArgs e)
|
||||
{
|
||||
_applicationView.AudioPlayer.Stop();
|
||||
_applicationView.AudioPlayer.Dispose();
|
||||
DiscordService.DiscordHandler.UpdateToSavedPresence();
|
||||
}
|
||||
|
||||
private void OnDeviceSwap(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is not ComboBox { SelectedItem: MMDevice selectedDevice })
|
||||
return;
|
||||
|
||||
UserSettings.Default.AudioDeviceId = selectedDevice.DeviceID;
|
||||
_applicationView.AudioPlayer.Device();
|
||||
}
|
||||
|
||||
private void OnVolumeChange(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_applicationView.AudioPlayer.Volume();
|
||||
}
|
||||
|
||||
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is TextBox)
|
||||
return;
|
||||
|
||||
if (UserSettings.Default.AddAudio.IsTriggered(e.Key))
|
||||
{
|
||||
var openFileDialog = new OpenFileDialog
|
||||
{
|
||||
Title = "Select an audio file",
|
||||
InitialDirectory = UserSettings.Default.AudioDirectory,
|
||||
Filter = "OGG Files (*.ogg)|*.ogg|WAV Files (*.wav)|*.wav|WEM Files (*.wem)|*.wem|ADPCM Files (*.adpcm)|*.adpcm|All Files (*.*)|*.*",
|
||||
Multiselect = true
|
||||
};
|
||||
|
||||
if (!openFileDialog.ShowDialog().GetValueOrDefault()) return;
|
||||
foreach (var file in openFileDialog.FileNames)
|
||||
{
|
||||
_applicationView.AudioPlayer.AddToPlaylist(file);
|
||||
}
|
||||
}
|
||||
else if (UserSettings.Default.PlayPauseAudio.IsTriggered(e.Key))
|
||||
_applicationView.AudioPlayer.PlayPauseOnStart();
|
||||
else if (UserSettings.Default.PreviousAudio.IsTriggered(e.Key))
|
||||
_applicationView.AudioPlayer.Previous();
|
||||
else if (UserSettings.Default.NextAudio.IsTriggered(e.Key))
|
||||
_applicationView.AudioPlayer.Next();
|
||||
}
|
||||
|
||||
private void OnAudioFileMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_applicationView.AudioPlayer.PlayPauseOnForce();
|
||||
}
|
||||
|
||||
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (sender is not TextBox textBox)
|
||||
return;
|
||||
|
||||
var filters = textBox.Text.Trim().Split(' ');
|
||||
_applicationView.AudioPlayer.AudioFilesView.Filter = o => { return o is AudioFile audio && filters.All(x => audio.FileName.Contains(x, StringComparison.OrdinalIgnoreCase)); };
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,64 +2,63 @@
|
|||
using Ookii.Dialogs.Wpf;
|
||||
using System.Windows;
|
||||
|
||||
namespace FModel.Views
|
||||
namespace FModel.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Logique d'interaction pour DirectorySelector.xaml
|
||||
/// </summary>
|
||||
public partial class DirectorySelector
|
||||
{
|
||||
/// <summary>
|
||||
/// Logique d'interaction pour DirectorySelector.xaml
|
||||
/// </summary>
|
||||
public partial class DirectorySelector
|
||||
public DirectorySelector(GameSelectorViewModel gameSelectorViewModel)
|
||||
{
|
||||
public DirectorySelector(GameSelectorViewModel gameSelectorViewModel)
|
||||
DataContext = gameSelectorViewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnBrowseDirectories(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not GameSelectorViewModel gameLauncherViewModel)
|
||||
return;
|
||||
|
||||
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
|
||||
if (folderBrowser.ShowDialog() == true)
|
||||
{
|
||||
DataContext = gameSelectorViewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnBrowseDirectories(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not GameSelectorViewModel gameLauncherViewModel)
|
||||
return;
|
||||
|
||||
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
|
||||
if (folderBrowser.ShowDialog() == true)
|
||||
{
|
||||
gameLauncherViewModel.AddUnknownGame(folderBrowser.SelectedPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBrowseManualDirectories(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
|
||||
if (folderBrowser.ShowDialog() == true)
|
||||
{
|
||||
HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddDirectory(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not GameSelectorViewModel gameLauncherViewModel||
|
||||
string.IsNullOrEmpty(HelloMyNameIsGame.Text) ||
|
||||
string.IsNullOrEmpty(HelloGameMyNameIsDirectory.Text))
|
||||
return;
|
||||
|
||||
gameLauncherViewModel.AddUnknownGame(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text);
|
||||
HelloMyNameIsGame.Clear();
|
||||
HelloGameMyNameIsDirectory.Clear();
|
||||
}
|
||||
|
||||
private void OnDeleteDirectory(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not GameSelectorViewModel gameLauncherViewModel)
|
||||
return;
|
||||
|
||||
gameLauncherViewModel.DeleteSelectedGame();
|
||||
gameLauncherViewModel.AddUnknownGame(folderBrowser.SelectedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBrowseManualDirectories(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
|
||||
if (folderBrowser.ShowDialog() == true)
|
||||
{
|
||||
HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddDirectory(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not GameSelectorViewModel gameLauncherViewModel||
|
||||
string.IsNullOrEmpty(HelloMyNameIsGame.Text) ||
|
||||
string.IsNullOrEmpty(HelloGameMyNameIsDirectory.Text))
|
||||
return;
|
||||
|
||||
gameLauncherViewModel.AddUnknownGame(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text);
|
||||
HelloMyNameIsGame.Clear();
|
||||
HelloGameMyNameIsDirectory.Clear();
|
||||
}
|
||||
|
||||
private void OnDeleteDirectory(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not GameSelectorViewModel gameLauncherViewModel)
|
||||
return;
|
||||
|
||||
gameLauncherViewModel.DeleteSelectedGame();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,10 @@
|
|||
using AdonisUI.Controls;
|
||||
|
||||
using FModel.Extensions;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Resources.Controls;
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using SkiaSharp;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Imaging;
|
||||
|
|
@ -22,281 +17,281 @@ using System.Windows.Controls.Primitives;
|
|||
using System.Windows.Input;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace FModel.Views
|
||||
namespace FModel.Views;
|
||||
|
||||
public partial class ImageMerger
|
||||
{
|
||||
public partial class ImageMerger
|
||||
private const string FILENAME = "Preview.png";
|
||||
private byte[] _imageBuffer;
|
||||
|
||||
public ImageMerger()
|
||||
{
|
||||
private const string FILENAME = "Preview.png";
|
||||
private byte[] _imagebuffer;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ImageMerger()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void DrawPreview(object sender, DragCompletedEventArgs dragCompletedEventArgs)
|
||||
{
|
||||
if (ImagePreview.Source != null)
|
||||
await DrawPreview().ConfigureAwait(false);
|
||||
}
|
||||
private async void Click_DrawPreview(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (ImagePreview.Source != null)
|
||||
await DrawPreview().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task DrawPreview()
|
||||
{
|
||||
AddButton.IsEnabled = false;
|
||||
UpButton.IsEnabled = false;
|
||||
DownButton.IsEnabled = false;
|
||||
DeleteButton.IsEnabled = false;
|
||||
ClearButton.IsEnabled = false;
|
||||
SizeSlider.IsEnabled = false;
|
||||
OpenImageButton.IsEnabled = false;
|
||||
SaveImageButton.IsEnabled = false;
|
||||
|
||||
var margin = UserSettings.Default.ImageMergerMargin;
|
||||
int num = 1, curW = 0, curH = 0, maxWidth = 0, maxHeight = 0, lineMaxHeight = 0, imagesPerRow = Convert.ToInt32(SizeSlider.Value);
|
||||
var positions = new Dictionary<int, SKPoint>();
|
||||
var images = new SKBitmap[ImagesListBox.Items.Count];
|
||||
for (var i = 0; i < images.Length; i++)
|
||||
{
|
||||
var item = (ListBoxItem) ImagesListBox.Items[i];
|
||||
var ms = new MemoryStream();
|
||||
var stream = new FileStream(item.ContentStringFormat, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
if (item.ContentStringFormat.EndsWith(".tif"))
|
||||
{
|
||||
await using var tmp = new MemoryStream();
|
||||
await stream.CopyToAsync(tmp);
|
||||
System.Drawing.Image.FromStream(tmp).Save(ms, ImageFormat.Png);
|
||||
}
|
||||
else
|
||||
{
|
||||
await stream.CopyToAsync(ms);
|
||||
}
|
||||
|
||||
var image = SKBitmap.Decode(ms.ToArray());
|
||||
positions[i] = new SKPoint(curW, curH);
|
||||
images[i] = image;
|
||||
|
||||
if (image.Height > lineMaxHeight)
|
||||
lineMaxHeight = image.Height;
|
||||
|
||||
if (num % imagesPerRow == 0)
|
||||
{
|
||||
maxWidth = curW + image.Width + margin;
|
||||
curH += lineMaxHeight + margin;
|
||||
|
||||
curW = 0;
|
||||
lineMaxHeight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxHeight = curH + lineMaxHeight + margin;
|
||||
curW += image.Width + margin;
|
||||
if (curW > maxWidth)
|
||||
maxWidth = curW;
|
||||
}
|
||||
|
||||
num++;
|
||||
}
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
using var bmp = new SKBitmap(maxWidth - margin, maxHeight - margin, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var canvas = new SKCanvas(bmp);
|
||||
|
||||
for (var i = 0; i < images.Length; i++)
|
||||
{
|
||||
using (images[i])
|
||||
{
|
||||
canvas.DrawBitmap(images[i], positions[i], new SKPaint {FilterQuality = SKFilterQuality.High, IsAntialias = true});
|
||||
}
|
||||
}
|
||||
|
||||
using var data = bmp.Encode(SKEncodedImageFormat.Png, 100);
|
||||
using var stream = new MemoryStream(_imagebuffer = data.ToArray());
|
||||
var photo = new BitmapImage();
|
||||
photo.BeginInit();
|
||||
photo.CacheOption = BitmapCacheOption.OnLoad;
|
||||
photo.StreamSource = stream;
|
||||
photo.EndInit();
|
||||
photo.Freeze();
|
||||
|
||||
Application.Current.Dispatcher.Invoke(delegate { ImagePreview.Source = photo; });
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
AddButton.IsEnabled = true;
|
||||
UpButton.IsEnabled = true;
|
||||
DownButton.IsEnabled = true;
|
||||
DeleteButton.IsEnabled = true;
|
||||
ClearButton.IsEnabled = true;
|
||||
SizeSlider.IsEnabled = true;
|
||||
OpenImageButton.IsEnabled = true;
|
||||
SaveImageButton.IsEnabled = true;
|
||||
}, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
private async void OnImageAdd(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var fileBrowser = new OpenFileDialog
|
||||
{
|
||||
Title = "Add image(s)",
|
||||
InitialDirectory = $"{UserSettings.Default.OutputDirectory}\\Exports",
|
||||
Multiselect = true,
|
||||
Filter = "Image Files (*.png,*.bmp,*.jpg,*.jpeg,*.jfif,*.jpe,*.tiff,*.tif)|*.png;*.bmp;*.jpg;*.jpeg;*.jfif;*.jpe;*.tiff;*.tif|All Files (*.*)|*.*"
|
||||
};
|
||||
var result = fileBrowser.ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
|
||||
foreach (var file in fileBrowser.FileNames)
|
||||
{
|
||||
ImagesListBox.Items.Add(new ListBoxItem
|
||||
{
|
||||
ContentStringFormat = file,
|
||||
Content = Path.GetFileNameWithoutExtension(file)
|
||||
});
|
||||
}
|
||||
|
||||
SizeSlider.Value = Math.Min(ImagesListBox.Items.Count, Math.Round(Math.Sqrt(ImagesListBox.Items.Count)));
|
||||
private async void DrawPreview(object sender, DragCompletedEventArgs dragCompletedEventArgs)
|
||||
{
|
||||
if (ImagePreview.Source != null)
|
||||
await DrawPreview().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async void ModifyItemInList(object sender, RoutedEventArgs e)
|
||||
private async void Click_DrawPreview(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (ImagePreview.Source != null)
|
||||
await DrawPreview().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task DrawPreview()
|
||||
{
|
||||
AddButton.IsEnabled = false;
|
||||
UpButton.IsEnabled = false;
|
||||
DownButton.IsEnabled = false;
|
||||
DeleteButton.IsEnabled = false;
|
||||
ClearButton.IsEnabled = false;
|
||||
SizeSlider.IsEnabled = false;
|
||||
OpenImageButton.IsEnabled = false;
|
||||
SaveImageButton.IsEnabled = false;
|
||||
|
||||
var margin = UserSettings.Default.ImageMergerMargin;
|
||||
int num = 1, curW = 0, curH = 0, maxWidth = 0, maxHeight = 0, lineMaxHeight = 0, imagesPerRow = Convert.ToInt32(SizeSlider.Value);
|
||||
var positions = new Dictionary<int, SKPoint>();
|
||||
var images = new SKBitmap[ImagesListBox.Items.Count];
|
||||
for (var i = 0; i < images.Length; i++)
|
||||
{
|
||||
if (ImagesListBox.Items.Count <= 0 || ImagesListBox.SelectedItems.Count <= 0) return;
|
||||
var indices = ImagesListBox.SelectedItems.Cast<ListBoxItem>().Select(i => ImagesListBox.Items.IndexOf(i)).ToArray();
|
||||
var reloadImage = false;
|
||||
var item = (ListBoxItem) ImagesListBox.Items[i];
|
||||
var ms = new MemoryStream();
|
||||
var stream = new FileStream(item.ContentStringFormat, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
switch (((Button) sender).Name)
|
||||
if (item.ContentStringFormat.EndsWith(".tif"))
|
||||
{
|
||||
case "UpButton":
|
||||
{
|
||||
if (indices.Length > 0 && indices[0] > 0)
|
||||
{
|
||||
for (var i = 0; i < ImagesListBox.Items.Count; i++)
|
||||
{
|
||||
if (!indices.Contains(i)) continue;
|
||||
var item = (ListBoxItem) ImagesListBox.Items[i];
|
||||
ImagesListBox.Items.Remove(item);
|
||||
ImagesListBox.Items.Insert(i - 1, item);
|
||||
item.IsSelected = true;
|
||||
reloadImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImagesListBox.SelectedItems.Add(indices);
|
||||
if (reloadImage)
|
||||
{
|
||||
await DrawPreview().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "DownButton":
|
||||
{
|
||||
if (indices.Length > 0 && indices[^1] < ImagesListBox.Items.Count - 1)
|
||||
{
|
||||
for (var i = ImagesListBox.Items.Count - 1; i > -1; --i)
|
||||
{
|
||||
if (!indices.Contains(i)) continue;
|
||||
var item = (ListBoxItem) ImagesListBox.Items[i];
|
||||
ImagesListBox.Items.Remove(item);
|
||||
ImagesListBox.Items.Insert(i + 1, item);
|
||||
item.IsSelected = true;
|
||||
reloadImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (reloadImage)
|
||||
{
|
||||
await DrawPreview().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "DeleteButton":
|
||||
{
|
||||
if (ImagesListBox.Items.Count > 0 && ImagesListBox.SelectedItems.Count > 0)
|
||||
{
|
||||
for (var i = ImagesListBox.SelectedItems.Count - 1; i >= 0; --i)
|
||||
ImagesListBox.Items.Remove(ImagesListBox.SelectedItems[i]);
|
||||
}
|
||||
|
||||
await DrawPreview().ConfigureAwait(false);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClear(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ImagesListBox.Items.Clear();
|
||||
ImagePreview.Source = null;
|
||||
}
|
||||
|
||||
private void OnOpenImage(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ImagePreview.Source == null) return;
|
||||
Helper.OpenWindow<AdonisWindow>("Merged Image", () =>
|
||||
{
|
||||
new ImagePopout
|
||||
{
|
||||
Title = "Merged Image",
|
||||
Width = ImagePreview.Source.Width,
|
||||
Height = ImagePreview.Source.Height,
|
||||
WindowState = ImagePreview.Source.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
|
||||
ImageCtrl = {Source = ImagePreview.Source}
|
||||
}.Show();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnSaveImage(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(delegate
|
||||
{
|
||||
if (ImagePreview.Source == null) return;
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Save Image",
|
||||
FileName = FILENAME,
|
||||
InitialDirectory = UserSettings.Default.OutputDirectory,
|
||||
Filter = "Png Files (*.png)|*.png|All Files (*.*)|*.*"
|
||||
};
|
||||
var result = saveFileDialog.ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
|
||||
using (var fs = new FileStream(saveFileDialog.FileName, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
fs.Write(_imagebuffer, 0, _imagebuffer.Length);
|
||||
}
|
||||
|
||||
SaveCheck(saveFileDialog.FileName, Path.GetFileName(saveFileDialog.FileName));
|
||||
});
|
||||
}
|
||||
|
||||
private static void SaveCheck(string path, string fileName)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Log.Information("{FileName} successfully saved", fileName);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
|
||||
await using var tmp = new MemoryStream();
|
||||
await stream.CopyToAsync(tmp);
|
||||
System.Drawing.Image.FromStream(tmp).Save(ms, ImageFormat.Png);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("{FileName} could not be saved", fileName);
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
|
||||
await stream.CopyToAsync(ms);
|
||||
}
|
||||
|
||||
var image = SKBitmap.Decode(ms.ToArray());
|
||||
positions[i] = new SKPoint(curW, curH);
|
||||
images[i] = image;
|
||||
|
||||
if (image.Height > lineMaxHeight)
|
||||
lineMaxHeight = image.Height;
|
||||
|
||||
if (num % imagesPerRow == 0)
|
||||
{
|
||||
maxWidth = curW + image.Width + margin;
|
||||
curH += lineMaxHeight + margin;
|
||||
|
||||
curW = 0;
|
||||
lineMaxHeight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxHeight = curH + lineMaxHeight + margin;
|
||||
curW += image.Width + margin;
|
||||
if (curW > maxWidth)
|
||||
maxWidth = curW;
|
||||
}
|
||||
|
||||
num++;
|
||||
}
|
||||
|
||||
private void OnCopyImage(object sender, RoutedEventArgs e)
|
||||
await Task.Run(() =>
|
||||
{
|
||||
ClipboardExtensions.SetImage(_imagebuffer, FILENAME);
|
||||
using var bmp = new SKBitmap(maxWidth - margin, maxHeight - margin, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var canvas = new SKCanvas(bmp);
|
||||
|
||||
for (var i = 0; i < images.Length; i++)
|
||||
{
|
||||
using (images[i])
|
||||
{
|
||||
canvas.DrawBitmap(images[i], positions[i], new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
}
|
||||
}
|
||||
|
||||
using var data = bmp.Encode(SKEncodedImageFormat.Png, 100);
|
||||
using var stream = new MemoryStream(_imageBuffer = data.ToArray());
|
||||
var photo = new BitmapImage();
|
||||
photo.BeginInit();
|
||||
photo.CacheOption = BitmapCacheOption.OnLoad;
|
||||
photo.StreamSource = stream;
|
||||
photo.EndInit();
|
||||
photo.Freeze();
|
||||
|
||||
Application.Current.Dispatcher.Invoke(delegate { ImagePreview.Source = photo; });
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
AddButton.IsEnabled = true;
|
||||
UpButton.IsEnabled = true;
|
||||
DownButton.IsEnabled = true;
|
||||
DeleteButton.IsEnabled = true;
|
||||
ClearButton.IsEnabled = true;
|
||||
SizeSlider.IsEnabled = true;
|
||||
OpenImageButton.IsEnabled = true;
|
||||
SaveImageButton.IsEnabled = true;
|
||||
}, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
private async void OnImageAdd(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var fileBrowser = new OpenFileDialog
|
||||
{
|
||||
Title = "Add image(s)",
|
||||
InitialDirectory = $"{UserSettings.Default.OutputDirectory}\\Exports",
|
||||
Multiselect = true,
|
||||
Filter = "Image Files (*.png,*.bmp,*.jpg,*.jpeg,*.jfif,*.jpe,*.tiff,*.tif)|*.png;*.bmp;*.jpg;*.jpeg;*.jfif;*.jpe;*.tiff;*.tif|All Files (*.*)|*.*"
|
||||
};
|
||||
var result = fileBrowser.ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
|
||||
foreach (var file in fileBrowser.FileNames)
|
||||
{
|
||||
ImagesListBox.Items.Add(new ListBoxItem
|
||||
{
|
||||
ContentStringFormat = file,
|
||||
Content = Path.GetFileNameWithoutExtension(file)
|
||||
});
|
||||
}
|
||||
|
||||
SizeSlider.Value = Math.Min(ImagesListBox.Items.Count, Math.Round(Math.Sqrt(ImagesListBox.Items.Count)));
|
||||
await DrawPreview().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void ModifyItemInList(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ImagesListBox.Items.Count <= 0 || ImagesListBox.SelectedItems.Count <= 0) return;
|
||||
var indices = ImagesListBox.SelectedItems.Cast<ListBoxItem>().Select(i => ImagesListBox.Items.IndexOf(i)).ToArray();
|
||||
var reloadImage = false;
|
||||
|
||||
switch (((Button) sender).Name)
|
||||
{
|
||||
case "UpButton":
|
||||
{
|
||||
if (indices.Length > 0 && indices[0] > 0)
|
||||
{
|
||||
for (var i = 0; i < ImagesListBox.Items.Count; i++)
|
||||
{
|
||||
if (!indices.Contains(i)) continue;
|
||||
var item = (ListBoxItem) ImagesListBox.Items[i];
|
||||
ImagesListBox.Items.Remove(item);
|
||||
ImagesListBox.Items.Insert(i - 1, item);
|
||||
item.IsSelected = true;
|
||||
reloadImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImagesListBox.SelectedItems.Add(indices);
|
||||
if (reloadImage)
|
||||
{
|
||||
await DrawPreview().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "DownButton":
|
||||
{
|
||||
if (indices.Length > 0 && indices[^1] < ImagesListBox.Items.Count - 1)
|
||||
{
|
||||
for (var i = ImagesListBox.Items.Count - 1; i > -1; --i)
|
||||
{
|
||||
if (!indices.Contains(i)) continue;
|
||||
var item = (ListBoxItem) ImagesListBox.Items[i];
|
||||
ImagesListBox.Items.Remove(item);
|
||||
ImagesListBox.Items.Insert(i + 1, item);
|
||||
item.IsSelected = true;
|
||||
reloadImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (reloadImage)
|
||||
{
|
||||
await DrawPreview().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "DeleteButton":
|
||||
{
|
||||
if (ImagesListBox.Items.Count > 0 && ImagesListBox.SelectedItems.Count > 0)
|
||||
{
|
||||
for (var i = ImagesListBox.SelectedItems.Count - 1; i >= 0; --i)
|
||||
ImagesListBox.Items.Remove(ImagesListBox.SelectedItems[i]);
|
||||
}
|
||||
|
||||
await DrawPreview().ConfigureAwait(false);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClear(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ImagesListBox.Items.Clear();
|
||||
ImagePreview.Source = null;
|
||||
}
|
||||
|
||||
private void OnOpenImage(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ImagePreview.Source == null) return;
|
||||
Helper.OpenWindow<AdonisWindow>("Merged Image", () =>
|
||||
{
|
||||
new ImagePopout
|
||||
{
|
||||
Title = "Merged Image",
|
||||
Width = ImagePreview.Source.Width,
|
||||
Height = ImagePreview.Source.Height,
|
||||
WindowState = ImagePreview.Source.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
|
||||
ImageCtrl = { Source = ImagePreview.Source }
|
||||
}.Show();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnSaveImage(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(delegate
|
||||
{
|
||||
if (ImagePreview.Source == null) return;
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Save Image",
|
||||
FileName = FILENAME,
|
||||
InitialDirectory = UserSettings.Default.OutputDirectory,
|
||||
Filter = "Png Files (*.png)|*.png|All Files (*.*)|*.*"
|
||||
};
|
||||
var result = saveFileDialog.ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
|
||||
using (var fs = new FileStream(saveFileDialog.FileName, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
fs.Write(_imageBuffer, 0, _imageBuffer.Length);
|
||||
}
|
||||
|
||||
SaveCheck(saveFileDialog.FileName, Path.GetFileName(saveFileDialog.FileName));
|
||||
});
|
||||
}
|
||||
|
||||
private static void SaveCheck(string path, string fileName)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Log.Information("{FileName} successfully saved", fileName);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("{FileName} could not be saved", fileName);
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCopyImage(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ClipboardExtensions.SetImage(_imageBuffer, FILENAME);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,71 +11,70 @@ using FModel.Views.Resources.Controls;
|
|||
using Microsoft.Win32;
|
||||
using Serilog;
|
||||
|
||||
namespace FModel.Views
|
||||
namespace FModel.Views;
|
||||
|
||||
public partial class MapViewer
|
||||
{
|
||||
public partial class MapViewer
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
||||
public MapViewer()
|
||||
{
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
DataContext = _applicationView;
|
||||
_applicationView.MapViewer.Initialize();
|
||||
|
||||
public MapViewer()
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, CancelEventArgs e) => DiscordService.DiscordHandler.UpdateToSavedPresence();
|
||||
|
||||
private void OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_applicationView.MapViewer.MapImage == null) return;
|
||||
var path = Path.Combine(UserSettings.Default.TextureDirectory, "MiniMap.png");
|
||||
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
DataContext = _applicationView;
|
||||
_applicationView.MapViewer.Initialize();
|
||||
Title = "Save MiniMap",
|
||||
FileName = "MiniMap.png",
|
||||
InitialDirectory = path.SubstringBeforeLast('\\'),
|
||||
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
|
||||
};
|
||||
|
||||
InitializeComponent();
|
||||
if (!saveFileDialog.ShowDialog().GetValueOrDefault()) return;
|
||||
path = saveFileDialog.FileName;
|
||||
|
||||
using var fileStream = new FileStream(path, FileMode.Create);
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(_applicationView.MapViewer.GetImageToSave()));
|
||||
encoder.Save(fileStream);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Log.Information("MiniMap.png successfully saved");
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText("Successfully saved 'MiniMap.png'", Constants.WHITE, true);
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, CancelEventArgs e) => DiscordService.DiscordHandler.UpdateToSavedPresence();
|
||||
|
||||
private void OnClick(object sender, RoutedEventArgs e)
|
||||
else
|
||||
{
|
||||
if (_applicationView.MapViewer.MapImage == null) return;
|
||||
var path = Path.Combine(UserSettings.Default.TextureDirectory, "MiniMap.png");
|
||||
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Save MiniMap",
|
||||
FileName = "MiniMap.png",
|
||||
InitialDirectory = path.SubstringBeforeLast('\\'),
|
||||
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
|
||||
};
|
||||
|
||||
if (!(bool) saveFileDialog.ShowDialog()) return;
|
||||
path = saveFileDialog.FileName;
|
||||
|
||||
using var fileStream = new FileStream(path, FileMode.Create);
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(_applicationView.MapViewer.GetImageToSave()));
|
||||
encoder.Save(fileStream);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Log.Information("MiniMap.png successfully saved");
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText("Successfully saved 'MiniMap.png'", Constants.WHITE, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("MiniMap.png could not be saved");
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("Could not save 'MiniMap.png'", Constants.WHITE, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var item in MapTree.Items)
|
||||
{
|
||||
if (item is not TreeViewItem {IsSelected: true})
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
_applicationView.MapViewer.MapIndex = i;
|
||||
break;
|
||||
}
|
||||
Log.Error("MiniMap.png could not be saved");
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("Could not save 'MiniMap.png'", Constants.WHITE, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var item in MapTree.Items)
|
||||
{
|
||||
if (item is not TreeViewItem { IsSelected: true })
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
_applicationView.MapViewer.MapIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,107 +10,106 @@ using HelixToolkit.Wpf.SharpDX;
|
|||
using MessageBox = AdonisUI.Controls.MessageBox;
|
||||
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
|
||||
|
||||
namespace FModel.Views
|
||||
namespace FModel.Views;
|
||||
|
||||
public partial class ModelViewer
|
||||
{
|
||||
public partial class ModelViewer
|
||||
private bool _messageShown;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
||||
public ModelViewer()
|
||||
{
|
||||
private bool _messageShown;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
DataContext = _applicationView;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ModelViewer()
|
||||
public void Load(UObject export) => _applicationView.ModelViewer.LoadExport(export);
|
||||
public void Overwrite(UMaterialInstance materialInstance)
|
||||
{
|
||||
if (_applicationView.ModelViewer.TryOverwriteMaterial(materialInstance))
|
||||
{
|
||||
DataContext = _applicationView;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void Load(UObject export) => _applicationView.ModelViewer.LoadExport(export);
|
||||
public void Overwrite(UMaterialInstance materialInstance)
|
||||
{
|
||||
if (_applicationView.ModelViewer.TryOverwriteMaterial(materialInstance))
|
||||
{
|
||||
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show(new MessageBoxModel
|
||||
{
|
||||
Text = "An attempt to load a material failed.",
|
||||
Caption = "Error",
|
||||
Icon = MessageBoxImage.Error,
|
||||
Buttons = MessageBoxButtons.OkCancel(),
|
||||
IsSoundEnabled = false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, CancelEventArgs e)
|
||||
{
|
||||
_applicationView.ModelViewer.Clear();
|
||||
_applicationView.ModelViewer.AppendMode = false;
|
||||
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = false;
|
||||
MyAntiCrashGroup.ItemsSource = null; // <3
|
||||
}
|
||||
|
||||
private async void OnWindowKeyDown(object sender, KeyEventArgs e)
|
||||
else
|
||||
{
|
||||
switch (e.Key)
|
||||
MessageBox.Show(new MessageBoxModel
|
||||
{
|
||||
case Key.W:
|
||||
_applicationView.ModelViewer.WirefreameToggle();
|
||||
break;
|
||||
case Key.H:
|
||||
_applicationView.ModelViewer.RenderingToggle();
|
||||
break;
|
||||
case Key.D:
|
||||
_applicationView.ModelViewer.DiffuseOnlyToggle();
|
||||
break;
|
||||
case Key.M:
|
||||
_applicationView.ModelViewer.MaterialColorToggle();
|
||||
break;
|
||||
case Key.Decimal:
|
||||
_applicationView.ModelViewer.FocusOnSelectedMesh();
|
||||
break;
|
||||
case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift):
|
||||
_applicationView.ModelViewer.SaveAsScene();
|
||||
break;
|
||||
case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control):
|
||||
await _applicationView.ModelViewer.SaveLoadedModels();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouse3DDown(object sender, MouseDown3DEventArgs e)
|
||||
{
|
||||
if (!Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) || e.HitTestResult.ModelHit is not CustomMeshGeometryModel3D m) return;
|
||||
_applicationView.ModelViewer.SelectedModel.SelectedGeometry = m;
|
||||
MaterialsListName.ScrollIntoView(m);
|
||||
}
|
||||
|
||||
private void OnFocusClick(object sender, RoutedEventArgs e)
|
||||
=> _applicationView.ModelViewer.FocusOnSelectedMesh();
|
||||
|
||||
private void OnCopyClick(object sender, RoutedEventArgs e)
|
||||
=> _applicationView.ModelViewer.CopySelectedMaterialName();
|
||||
|
||||
private async void Save(object sender, RoutedEventArgs e)
|
||||
=> await _applicationView.ModelViewer.SaveLoadedModels();
|
||||
|
||||
private void OnOverwriteMaterialClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = true;
|
||||
if (!_messageShown)
|
||||
{
|
||||
MessageBox.Show(new MessageBoxModel
|
||||
{
|
||||
Text = "Simply extract a material once FModel will be brought to the foreground. This message will be shown once per Model Viewer's lifetime, close it to begin.",
|
||||
Caption = "How To Overwrite Material?",
|
||||
Icon = MessageBoxImage.Information,
|
||||
IsSoundEnabled = false
|
||||
});
|
||||
_messageShown = true;
|
||||
}
|
||||
|
||||
MainWindow.YesWeCats.Activate();
|
||||
Text = "An attempt to load a material failed.",
|
||||
Caption = "Error",
|
||||
Icon = MessageBoxImage.Error,
|
||||
Buttons = MessageBoxButtons.OkCancel(),
|
||||
IsSoundEnabled = false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, CancelEventArgs e)
|
||||
{
|
||||
_applicationView.ModelViewer.Clear();
|
||||
_applicationView.ModelViewer.AppendMode = false;
|
||||
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = false;
|
||||
MyAntiCrashGroup.ItemsSource = null; // <3
|
||||
}
|
||||
|
||||
private async void OnWindowKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.W:
|
||||
_applicationView.ModelViewer.WirefreameToggle();
|
||||
break;
|
||||
case Key.H:
|
||||
_applicationView.ModelViewer.RenderingToggle();
|
||||
break;
|
||||
case Key.D:
|
||||
_applicationView.ModelViewer.DiffuseOnlyToggle();
|
||||
break;
|
||||
case Key.M:
|
||||
_applicationView.ModelViewer.MaterialColorToggle();
|
||||
break;
|
||||
case Key.Decimal:
|
||||
_applicationView.ModelViewer.FocusOnSelectedMesh();
|
||||
break;
|
||||
case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift):
|
||||
_applicationView.ModelViewer.SaveAsScene();
|
||||
break;
|
||||
case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control):
|
||||
await _applicationView.ModelViewer.SaveLoadedModels();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouse3DDown(object sender, MouseDown3DEventArgs e)
|
||||
{
|
||||
if (!Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) || e.HitTestResult.ModelHit is not CustomMeshGeometryModel3D m) return;
|
||||
_applicationView.ModelViewer.SelectedModel.SelectedGeometry = m;
|
||||
MaterialsListName.ScrollIntoView(m);
|
||||
}
|
||||
|
||||
private void OnFocusClick(object sender, RoutedEventArgs e)
|
||||
=> _applicationView.ModelViewer.FocusOnSelectedMesh();
|
||||
|
||||
private void OnCopyClick(object sender, RoutedEventArgs e)
|
||||
=> _applicationView.ModelViewer.CopySelectedMaterialName();
|
||||
|
||||
private async void Save(object sender, RoutedEventArgs e)
|
||||
=> await _applicationView.ModelViewer.SaveLoadedModels();
|
||||
|
||||
private void OnOverwriteMaterialClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = true;
|
||||
if (!_messageShown)
|
||||
{
|
||||
MessageBox.Show(new MessageBoxModel
|
||||
{
|
||||
Text = "Simply extract a material once FModel will be brought to the foreground. This message will be shown once per Model Viewer's lifetime, close it to begin.",
|
||||
Caption = "How To Overwrite Material?",
|
||||
Icon = MessageBoxImage.Information,
|
||||
IsSoundEnabled = false
|
||||
});
|
||||
_messageShown = true;
|
||||
}
|
||||
|
||||
MainWindow.YesWeCats.Activate();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,127 +3,128 @@ using ICSharpCode.AvalonEdit;
|
|||
using ICSharpCode.AvalonEdit.Document;
|
||||
using ICSharpCode.AvalonEdit.Folding;
|
||||
|
||||
namespace FModel.Views.Resources.Controls
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
public class JsonFoldingStrategies
|
||||
{
|
||||
public class JsonFoldingStrategies
|
||||
{
|
||||
private readonly BraceFoldingStrategy _strategy;
|
||||
private readonly FoldingManager _foldingManager;
|
||||
private readonly BraceFoldingStrategy _strategy;
|
||||
private readonly FoldingManager _foldingManager;
|
||||
|
||||
public JsonFoldingStrategies(TextEditor avalonEditor)
|
||||
{
|
||||
_foldingManager = FoldingManager.Install(avalonEditor.TextArea);
|
||||
_strategy = new BraceFoldingStrategy(avalonEditor);
|
||||
}
|
||||
|
||||
public void UpdateFoldings(TextDocument document)
|
||||
{
|
||||
_foldingManager.UpdateFoldings(_strategy.UpdateFoldings(document), -1);
|
||||
}
|
||||
|
||||
public void UnfoldAll()
|
||||
{
|
||||
if (_foldingManager.AllFoldings == null)
|
||||
return;
|
||||
|
||||
foreach (var folding in _foldingManager.AllFoldings)
|
||||
{
|
||||
folding.IsFolded = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void FoldToggle(int offset)
|
||||
{
|
||||
if (_foldingManager.AllFoldings == null)
|
||||
return;
|
||||
|
||||
var foldSection = _foldingManager.GetFoldingsContaining(offset);
|
||||
if (foldSection.Count > 0)
|
||||
foldSection[^1].IsFolded = !foldSection[^1].IsFolded;
|
||||
}
|
||||
|
||||
public void FoldToggleAtLevel(int level = 0)
|
||||
{
|
||||
if (_foldingManager.AllFoldings == null)
|
||||
return;
|
||||
|
||||
var dowhat = -1;
|
||||
var foldunfold = false;
|
||||
foreach (var folding in _foldingManager.AllFoldings)
|
||||
{
|
||||
if (folding.Tag is not CustomNewFolding realFolding || realFolding.Level != level) continue;
|
||||
|
||||
if (dowhat < 0) // determine if we fold or unfold based on the first one
|
||||
{
|
||||
dowhat = 1;
|
||||
foldunfold = !folding.IsFolded;
|
||||
}
|
||||
folding.IsFolded = foldunfold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BraceFoldingStrategy
|
||||
{
|
||||
public BraceFoldingStrategy(TextEditor editor)
|
||||
{
|
||||
UpdateFoldings(editor.Document);
|
||||
}
|
||||
|
||||
public IEnumerable<CustomNewFolding> UpdateFoldings(TextDocument document)
|
||||
{
|
||||
return CreateNewFoldings(document);
|
||||
}
|
||||
|
||||
public IEnumerable<CustomNewFolding> CreateNewFoldings(ITextSource document)
|
||||
{
|
||||
var newFoldings = new List<CustomNewFolding>();
|
||||
var startOffsets = new Stack<int>();
|
||||
var lastNewLineOffset = 0;
|
||||
var level = -1;
|
||||
|
||||
for (var i = 0; i < document.TextLength; i++)
|
||||
{
|
||||
var c = document.GetCharAt(i);
|
||||
switch (c)
|
||||
{
|
||||
case '{' or '[':
|
||||
level++;
|
||||
startOffsets.Push(i);
|
||||
break;
|
||||
case '}' or ']' when startOffsets.Count > 0:
|
||||
{
|
||||
var startOffset = startOffsets.Pop();
|
||||
if (startOffset < lastNewLineOffset)
|
||||
{
|
||||
newFoldings.Add(new CustomNewFolding(startOffset, i + 1, level));
|
||||
}
|
||||
level--;
|
||||
break;
|
||||
}
|
||||
case '\n' or '\r':
|
||||
lastNewLineOffset = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset));
|
||||
return newFoldings;
|
||||
}
|
||||
public JsonFoldingStrategies(TextEditor avalonEditor)
|
||||
{
|
||||
_foldingManager = FoldingManager.Install(avalonEditor.TextArea);
|
||||
_strategy = new BraceFoldingStrategy(avalonEditor);
|
||||
}
|
||||
|
||||
public class CustomNewFolding : NewFolding
|
||||
{
|
||||
public int Level { get; }
|
||||
|
||||
public CustomNewFolding(int start, int end, int level) : base(start, end)
|
||||
{
|
||||
Level = level;
|
||||
}
|
||||
public void UpdateFoldings(TextDocument document)
|
||||
{
|
||||
_foldingManager.UpdateFoldings(_strategy.UpdateFoldings(document), -1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Level}] {StartOffset} -> {EndOffset}";
|
||||
}
|
||||
}
|
||||
public void UnfoldAll()
|
||||
{
|
||||
if (_foldingManager.AllFoldings == null)
|
||||
return;
|
||||
|
||||
foreach (var folding in _foldingManager.AllFoldings)
|
||||
{
|
||||
folding.IsFolded = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void FoldToggle(int offset)
|
||||
{
|
||||
if (_foldingManager.AllFoldings == null)
|
||||
return;
|
||||
|
||||
var foldSection = _foldingManager.GetFoldingsContaining(offset);
|
||||
if (foldSection.Count > 0)
|
||||
foldSection[^1].IsFolded = !foldSection[^1].IsFolded;
|
||||
}
|
||||
|
||||
public void FoldToggleAtLevel(int level = 0)
|
||||
{
|
||||
if (_foldingManager.AllFoldings == null)
|
||||
return;
|
||||
|
||||
var dowhat = -1;
|
||||
var foldunfold = false;
|
||||
foreach (var folding in _foldingManager.AllFoldings)
|
||||
{
|
||||
if (folding.Tag is not CustomNewFolding realFolding || realFolding.Level != level) continue;
|
||||
|
||||
if (dowhat < 0) // determine if we fold or unfold based on the first one
|
||||
{
|
||||
dowhat = 1;
|
||||
foldunfold = !folding.IsFolded;
|
||||
}
|
||||
|
||||
folding.IsFolded = foldunfold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BraceFoldingStrategy
|
||||
{
|
||||
public BraceFoldingStrategy(TextEditor editor)
|
||||
{
|
||||
UpdateFoldings(editor.Document);
|
||||
}
|
||||
|
||||
public IEnumerable<CustomNewFolding> UpdateFoldings(TextDocument document)
|
||||
{
|
||||
return CreateNewFoldings(document);
|
||||
}
|
||||
|
||||
public IEnumerable<CustomNewFolding> CreateNewFoldings(ITextSource document)
|
||||
{
|
||||
var newFoldings = new List<CustomNewFolding>();
|
||||
var startOffsets = new Stack<int>();
|
||||
var lastNewLineOffset = 0;
|
||||
var level = -1;
|
||||
|
||||
for (var i = 0; i < document.TextLength; i++)
|
||||
{
|
||||
var c = document.GetCharAt(i);
|
||||
switch (c)
|
||||
{
|
||||
case '{' or '[':
|
||||
level++;
|
||||
startOffsets.Push(i);
|
||||
break;
|
||||
case '}' or ']' when startOffsets.Count > 0:
|
||||
{
|
||||
var startOffset = startOffsets.Pop();
|
||||
if (startOffset < lastNewLineOffset)
|
||||
{
|
||||
newFoldings.Add(new CustomNewFolding(startOffset, i + 1, level));
|
||||
}
|
||||
|
||||
level--;
|
||||
break;
|
||||
}
|
||||
case '\n' or '\r':
|
||||
lastNewLineOffset = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset));
|
||||
return newFoldings;
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomNewFolding : NewFolding
|
||||
{
|
||||
public int Level { get; }
|
||||
|
||||
public CustomNewFolding(int start, int end, int level) : base(start, end)
|
||||
{
|
||||
Level = level;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Level}] {StartOffset} -> {EndOffset}";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +1,36 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using ICSharpCode.AvalonEdit.Rendering;
|
||||
|
||||
namespace FModel.Views.Resources.Controls
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
public class GamePathElementGenerator : VisualLineElementGenerator
|
||||
{
|
||||
public class GamePathElementGenerator : VisualLineElementGenerator
|
||||
private readonly Regex _gamePathRegex =
|
||||
new("\"(?:ObjectPath|AssetPathName|AssetName|ParameterName|CollisionProfileName|TableId)\": \"(?'target'(?!/?Script/)(.*/.*))\",?$",
|
||||
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
public GamePathElementGenerator()
|
||||
{
|
||||
private readonly Regex _gamePathRegex =
|
||||
new("\"(?:ObjectPath|AssetPathName|AssetName|ParameterName|CollisionProfileName|TableId)\": \"(?'target'(?!/?Script/)(.*/.*))\",?$",
|
||||
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
}
|
||||
|
||||
public GamePathElementGenerator()
|
||||
{
|
||||
}
|
||||
private Match FindMatch(int startOffset)
|
||||
{
|
||||
var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
|
||||
var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);
|
||||
return _gamePathRegex.Match(relevantText);
|
||||
}
|
||||
|
||||
private Match FindMatch(int startOffset)
|
||||
{
|
||||
var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
|
||||
var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);
|
||||
return _gamePathRegex.Match(relevantText);
|
||||
}
|
||||
public override int GetFirstInterestedOffset(int startOffset)
|
||||
{
|
||||
var m = FindMatch(startOffset);
|
||||
return m.Success ? startOffset + m.Index : -1;
|
||||
}
|
||||
|
||||
public override int GetFirstInterestedOffset(int startOffset)
|
||||
{
|
||||
var m = FindMatch(startOffset);
|
||||
return m.Success ? startOffset + m.Index : -1;
|
||||
}
|
||||
public override VisualLineElement ConstructElement(int offset)
|
||||
{
|
||||
var m = FindMatch(offset);
|
||||
if (!m.Success || m.Index != 0) return null;
|
||||
|
||||
public override VisualLineElement ConstructElement(int offset)
|
||||
{
|
||||
var m = FindMatch(offset);
|
||||
if (!m.Success || m.Index != 0) return null;
|
||||
|
||||
return m.Groups.TryGetValue("target", out var g) ?
|
||||
new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) :
|
||||
null;
|
||||
}
|
||||
return m.Groups.TryGetValue("target", out var g) ? new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) : null;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,73 +7,73 @@ using FModel.Services;
|
|||
using FModel.ViewModels;
|
||||
using ICSharpCode.AvalonEdit.Rendering;
|
||||
|
||||
namespace FModel.Views.Resources.Controls
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
public class GamePathVisualLineText : VisualLineText
|
||||
{
|
||||
public class GamePathVisualLineText : VisualLineText
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
||||
public delegate void GamePathOnClick(string gamePath);
|
||||
|
||||
public event GamePathOnClick OnGamePathClicked;
|
||||
private readonly string _gamePath;
|
||||
|
||||
public GamePathVisualLineText(string gamePath, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
|
||||
{
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
||||
public delegate void GamePathOnClick(string gamePath);
|
||||
public event GamePathOnClick OnGamePathClicked;
|
||||
private readonly string _gamePath;
|
||||
|
||||
public GamePathVisualLineText(string gamePath, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
|
||||
{
|
||||
_gamePath = gamePath;
|
||||
}
|
||||
|
||||
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
|
||||
{
|
||||
if (context == null)
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
|
||||
TextRunProperties.SetForegroundBrush(Brushes.Plum);
|
||||
return base.CreateTextRun(startVisualColumn, context);
|
||||
}
|
||||
|
||||
private bool GamePathIsClickable() => !string.IsNullOrEmpty(_gamePath) && Keyboard.Modifiers == ModifierKeys.None;
|
||||
|
||||
protected override void OnQueryCursor(QueryCursorEventArgs e)
|
||||
{
|
||||
if (!GamePathIsClickable()) return;
|
||||
e.Handled = true;
|
||||
e.Cursor = Cursors.Hand;
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton != MouseButton.Left || !GamePathIsClickable())
|
||||
return;
|
||||
if (e.Handled || OnGamePathClicked == null)
|
||||
return;
|
||||
|
||||
OnGamePathClicked(_gamePath);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override VisualLineText CreateInstance(int length)
|
||||
{
|
||||
var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length);
|
||||
a.OnGamePathClicked += async gamePath =>
|
||||
{
|
||||
var obj = gamePath.SubstringAfterLast('.');
|
||||
var package = gamePath.SubstringBeforeLast('.');
|
||||
var fullPath = _applicationView.CUE4Parse.Provider.FixPath(package, StringComparison.Ordinal);
|
||||
if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj);
|
||||
var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
|
||||
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
|
||||
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
_applicationView.CUE4Parse.ExtractAndScroll(fullPath, obj));
|
||||
}
|
||||
};
|
||||
return a;
|
||||
}
|
||||
_gamePath = gamePath;
|
||||
}
|
||||
}
|
||||
|
||||
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
|
||||
{
|
||||
if (context == null)
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
|
||||
TextRunProperties.SetForegroundBrush(Brushes.Plum);
|
||||
return base.CreateTextRun(startVisualColumn, context);
|
||||
}
|
||||
|
||||
private bool GamePathIsClickable() => !string.IsNullOrEmpty(_gamePath) && Keyboard.Modifiers == ModifierKeys.None;
|
||||
|
||||
protected override void OnQueryCursor(QueryCursorEventArgs e)
|
||||
{
|
||||
if (!GamePathIsClickable()) return;
|
||||
e.Handled = true;
|
||||
e.Cursor = Cursors.Hand;
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton != MouseButton.Left || !GamePathIsClickable())
|
||||
return;
|
||||
if (e.Handled || OnGamePathClicked == null)
|
||||
return;
|
||||
|
||||
OnGamePathClicked(_gamePath);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override VisualLineText CreateInstance(int length)
|
||||
{
|
||||
var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length);
|
||||
a.OnGamePathClicked += async gamePath =>
|
||||
{
|
||||
var obj = gamePath.SubstringAfterLast('.');
|
||||
var package = gamePath.SubstringBeforeLast('.');
|
||||
var fullPath = _applicationView.CUE4Parse.Provider.FixPath(package, StringComparison.Ordinal);
|
||||
if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj);
|
||||
var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
|
||||
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
|
||||
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
_applicationView.CUE4Parse.ExtractAndScroll(fullPath, obj));
|
||||
}
|
||||
};
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +1,37 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using ICSharpCode.AvalonEdit.Rendering;
|
||||
|
||||
namespace FModel.Views.Resources.Controls
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
public class HexColorElementGenerator : VisualLineElementGenerator
|
||||
{
|
||||
public class HexColorElementGenerator : VisualLineElementGenerator
|
||||
private readonly Regex _hexColorRegex = new("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$",
|
||||
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
public HexColorElementGenerator()
|
||||
{
|
||||
private readonly Regex _hexColorRegex =
|
||||
new Regex("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$",
|
||||
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
}
|
||||
|
||||
public HexColorElementGenerator()
|
||||
{
|
||||
}
|
||||
private Match FindMatch(int startOffset)
|
||||
{
|
||||
var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
|
||||
var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);
|
||||
return _hexColorRegex.Match(relevantText);
|
||||
}
|
||||
|
||||
private Match FindMatch(int startOffset)
|
||||
{
|
||||
var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
|
||||
var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);
|
||||
return _hexColorRegex.Match(relevantText);
|
||||
}
|
||||
public override int GetFirstInterestedOffset(int startOffset)
|
||||
{
|
||||
var m = FindMatch(startOffset);
|
||||
return m.Success ? startOffset + m.Index : -1;
|
||||
}
|
||||
|
||||
public override int GetFirstInterestedOffset(int startOffset)
|
||||
{
|
||||
var m = FindMatch(startOffset);
|
||||
return m.Success ? startOffset + m.Index : -1;
|
||||
}
|
||||
public override VisualLineElement ConstructElement(int offset)
|
||||
{
|
||||
var m = FindMatch(offset);
|
||||
if (!m.Success || m.Index != 0) return null;
|
||||
|
||||
public override VisualLineElement ConstructElement(int offset)
|
||||
{
|
||||
var m = FindMatch(offset);
|
||||
if (!m.Success || m.Index != 0) return null;
|
||||
|
||||
return m.Groups.TryGetValue("target", out var g) ?
|
||||
new HexColorVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) :
|
||||
null;
|
||||
}
|
||||
return m.Groups.TryGetValue("target", out var g) ?
|
||||
new HexColorVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) :
|
||||
null;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user