mirror of
https://github.com/4sval/FModel.git
synced 2026-04-04 16:15:52 -05:00
Compare commits
212 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b95b403bb | ||
|
|
5776444020 | ||
|
|
5b4494fda2 | ||
|
|
3098273a33 | ||
|
|
c9a97c73a1 | ||
|
|
a68b469556 | ||
|
|
4e0efe779b | ||
|
|
5d34bfaf4e | ||
|
|
f082d39bf9 | ||
|
|
1a2766a7e6 | ||
|
|
6b4503abc3 | ||
|
|
819fbc42ec | ||
|
|
012f780c08 | ||
|
|
139c61d9db | ||
|
|
f51ad82e1f | ||
|
|
1846b82802 | ||
|
|
5d3ab103ee | ||
|
|
d58acea554 | ||
|
|
5b13bb0b46 | ||
|
|
3b82f14290 | ||
|
|
c525243c50 | ||
|
|
2888c9261c | ||
|
|
879b5a2ce8 | ||
|
|
604e59be33 | ||
|
|
1f10656d91 | ||
|
|
69d83d5257 | ||
|
|
6baf1fcfee | ||
|
|
b8147b3c0c | ||
|
|
e05baeb580 | ||
|
|
e14cae0637 | ||
|
|
6fa336c3ee | ||
|
|
ae52058d26 | ||
|
|
de34f9c5ce | ||
|
|
35e05d947f | ||
|
|
570bdd6180 | ||
|
|
b51d539c24 | ||
|
|
f366ad1189 | ||
|
|
a4e92b6c59 | ||
|
|
407be3d1a7 | ||
|
|
001ddc1057 | ||
|
|
8eeae5d59b | ||
|
|
378c911083 | ||
|
|
d3f93021c6 | ||
|
|
17d3586032 | ||
|
|
2c0c5d8694 | ||
|
|
528603e147 | ||
|
|
0cc8da95e1 | ||
|
|
a0586e8acc | ||
|
|
af6ff0bf9f | ||
|
|
344b40361f | ||
|
|
1449f78665 | ||
|
|
a1ddc72b79 | ||
|
|
623656c702 | ||
|
|
52979f2a67 | ||
|
|
b71c25606a | ||
|
|
af4435e444 | ||
|
|
fe7106df3a | ||
|
|
934b4995d5 | ||
|
|
ed129819de | ||
|
|
bca41b0a5c | ||
|
|
0760e11058 | ||
|
|
eabda151d4 | ||
|
|
6ff3c987d1 | ||
|
|
b51983a36a | ||
|
|
a49dddaec3 | ||
|
|
4300eb58a3 | ||
|
|
1e3e4df206 | ||
|
|
51541e5ef9 | ||
|
|
4e76469169 | ||
|
|
75bdfc1797 | ||
|
|
66bdddd686 | ||
|
|
4bc93c05e3 | ||
|
|
1f57279206 | ||
|
|
4a9a44139d | ||
|
|
e6ef05092e | ||
|
|
364df7f402 | ||
|
|
cfbee11e58 | ||
|
|
7598ae0a74 | ||
|
|
1f1d2ae3c2 | ||
|
|
046677b875 | ||
|
|
0f3d80bafb | ||
|
|
62b103f958 | ||
|
|
a8a5207dec | ||
|
|
233b7ab03b | ||
|
|
89c8cbaccc | ||
|
|
df3fdbbd2b | ||
|
|
00bcb7ca16 | ||
|
|
e78c7a7be9 | ||
|
|
84030f5e20 | ||
|
|
b22c996a58 | ||
|
|
656d3b59ca | ||
|
|
b0625bbd6f | ||
|
|
f64a333847 | ||
|
|
394bcf356f | ||
|
|
03a4f79c3a | ||
|
|
ba0aedba68 | ||
|
|
2dd98d4d64 | ||
|
|
ff58050fe9 | ||
|
|
aa75a8bbf2 | ||
|
|
6f19cacaeb | ||
|
|
61da5a9ae0 | ||
|
|
9f4597f542 | ||
|
|
8ba756ade5 | ||
|
|
06416cb7f8 | ||
|
|
72930bcd14 | ||
|
|
c63721b644 | ||
|
|
99bcfb2c29 | ||
|
|
b75413306f | ||
|
|
47d003737b | ||
|
|
746107875c | ||
|
|
c10ff9dfc2 | ||
|
|
cf1f19f615 | ||
|
|
c0a03ac600 | ||
|
|
7d4b9e7094 | ||
|
|
3b24be37d2 | ||
|
|
7785a33b5e | ||
|
|
7d425f6176 | ||
|
|
c8de28130c | ||
|
|
134e32757f | ||
|
|
4e43a07f8e | ||
|
|
a4ffb29841 | ||
|
|
4d3d43c704 | ||
|
|
22168b37c9 | ||
|
|
60292cb24f | ||
|
|
cc2ff71759 | ||
|
|
037058da52 | ||
|
|
a6c508c743 | ||
|
|
4a1a758896 | ||
|
|
31382e3dbe | ||
|
|
c5e78e7ba7 | ||
|
|
adc19388c9 | ||
|
|
9318838ea7 | ||
|
|
48d172446e | ||
|
|
7581310ef3 | ||
|
|
0d9a2a34e9 | ||
|
|
2bd0f5163e | ||
|
|
556ae6e036 | ||
|
|
e943ea314a | ||
|
|
39f8ea5702 | ||
|
|
edebff0925 | ||
|
|
273b124840 | ||
|
|
cecd3b7fe6 | ||
|
|
e8b061f018 | ||
|
|
0e4d0431a3 | ||
|
|
819c046f3f | ||
|
|
fe20eb1483 | ||
|
|
239f130e66 | ||
|
|
e3764ef2d0 | ||
|
|
20a5c63baa | ||
|
|
814f34d534 | ||
|
|
2f7d20fc73 | ||
|
|
1707f41197 | ||
|
|
e7148d870c | ||
|
|
91edf0352e | ||
|
|
ae0e638854 | ||
|
|
34116d62b2 | ||
|
|
4c790af232 | ||
|
|
7bfadc4e20 | ||
|
|
c92103857e | ||
|
|
75e4028bc7 | ||
|
|
b5c1fdccc6 | ||
|
|
a97d773c64 | ||
|
|
e1b9529d6f | ||
|
|
37025228a2 | ||
|
|
6dfbd11ad4 | ||
|
|
71468dca23 | ||
|
|
93fa0b2bb6 | ||
|
|
dbf618e417 | ||
|
|
3d0bdf05e1 | ||
|
|
a704612544 | ||
|
|
32334a079f | ||
|
|
a3cbf3555f | ||
|
|
298f459dc7 | ||
|
|
ff2eac3d8e | ||
|
|
73fffc5545 | ||
|
|
f8c7996a61 | ||
|
|
7431ef5592 | ||
|
|
e313bb27e9 | ||
|
|
988b4a5847 | ||
|
|
09da05218c | ||
|
|
9a7d7effa5 | ||
|
|
db988f68c9 | ||
|
|
4385255426 | ||
|
|
d48aea4744 | ||
|
|
7dfeb1730d | ||
|
|
0882062a96 | ||
|
|
c07dc02714 | ||
|
|
6807e3ada8 | ||
|
|
4ff41502fb | ||
|
|
0f6c806d39 | ||
|
|
8bc9a5535e | ||
|
|
7402284c80 | ||
|
|
fa0f6447dc | ||
|
|
f341bb53ad | ||
|
|
b6bba28b6d | ||
|
|
8a41247acb | ||
|
|
062d54e366 | ||
|
|
3edcd31450 | ||
|
|
21e727e7b9 | ||
|
|
6bb325c10c | ||
|
|
20adb800f3 | ||
|
|
f1a33b68d0 | ||
|
|
6d3f6f79e7 | ||
|
|
4ddf887ba3 | ||
|
|
6285804cc9 | ||
|
|
fcb30ddaea | ||
|
|
0395562353 | ||
|
|
b846878e54 | ||
|
|
55b15ab2b5 | ||
|
|
7c15ddffa1 | ||
|
|
ac95ca7ebe | ||
|
|
090e7445f6 |
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: GIT Checkout
|
- name: GIT Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ jobs:
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
- name: .NET 8 Setup
|
- name: .NET 8 Setup
|
||||||
uses: actions/setup-dotnet@v2
|
uses: actions/setup-dotnet@v5
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ jobs:
|
||||||
run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net8.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 net8.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
|
- name: ZIP File
|
||||||
uses: papeloto/action-zip@v1
|
uses: papeloto/action-zip@v1.2
|
||||||
with:
|
with:
|
||||||
files: ./FModel/bin/Publish/FModel.exe
|
files: ./FModel/bin/Publish/FModel.exe
|
||||||
dest: FModel.zip # will end up in working directory not the Publish folder
|
dest: FModel.zip # will end up in working directory not the Publish folder
|
||||||
|
|
|
||||||
12
.github/workflows/qa.yml
vendored
12
.github/workflows/qa.yml
vendored
|
|
@ -10,17 +10,17 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: GIT Checkout
|
- name: GIT Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
|
||||||
- name: .NET 8 Setup
|
- name: .NET 8 Setup
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v5
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
- name: .NET Restore
|
- name: .NET Restore
|
||||||
run: dotnet restore FModel
|
run: dotnet restore "./FModel/FModel.slnx"
|
||||||
|
|
||||||
- name: .NET Publish
|
- name: .NET Publish
|
||||||
run: dotnet publish "./FModel/FModel.csproj" -c Release --no-restore --no-self-contained -r win-x64 -f net8.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false
|
run: dotnet publish "./FModel/FModel.csproj" -c Release --no-restore --no-self-contained -r win-x64 -f net8.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false
|
||||||
|
|
@ -33,7 +33,7 @@ jobs:
|
||||||
path: ./FModel/bin/Publish/FModel.exe
|
path: ./FModel/bin/Publish/FModel.exe
|
||||||
|
|
||||||
- name: Edit QA Artifact
|
- name: Edit QA Artifact
|
||||||
uses: ncipollo/release-action@v1.14.0
|
uses: ncipollo/release-action@v1.20.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
name: 'FModel QA Testing'
|
name: 'FModel QA Testing'
|
||||||
|
|
@ -51,13 +51,13 @@ jobs:
|
||||||
|
|
||||||
- name: FModel Auth
|
- name: FModel Auth
|
||||||
id: fmodel_auth
|
id: fmodel_auth
|
||||||
uses: fjogeleit/http-request-action@v1.15.5
|
uses: fjogeleit/http-request-action@v1.16.6
|
||||||
with:
|
with:
|
||||||
url: "https://api.fmodel.app/v1/oauth/token"
|
url: "https://api.fmodel.app/v1/oauth/token"
|
||||||
data: '{"username": "${{ secrets.API_USERNAME }}", "password": "${{ secrets.API_PASSWORD }}"}'
|
data: '{"username": "${{ secrets.API_USERNAME }}", "password": "${{ secrets.API_PASSWORD }}"}'
|
||||||
|
|
||||||
- name: FModel Deploy Build
|
- name: FModel Deploy Build
|
||||||
uses: fjogeleit/http-request-action@v1.15.5
|
uses: fjogeleit/http-request-action@v1.16.6
|
||||||
with:
|
with:
|
||||||
url: "https://api.fmodel.app/v1/infos/${{ secrets.QA_ID }}"
|
url: "https://api.fmodel.app/v1/infos/${{ secrets.QA_ID }}"
|
||||||
method: "PATCH"
|
method: "PATCH"
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 455b72e5e38bfe9476b5823bc642fc8ef488347f
|
Subproject commit d2f6ce6e618576dbbe7f6dd9ed3171f14513182a
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Application x:Class="FModel.App"
|
<Application x:Class="FModel.App"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||||
|
|
@ -9,6 +9,14 @@
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceDictionary Source="pack://application:,,,/AdonisUI;component/ColorSchemes/Dark.xaml"/>
|
<ResourceDictionary Source="pack://application:,,,/AdonisUI;component/ColorSchemes/Dark.xaml"/>
|
||||||
<ResourceDictionary Source="pack://application:,,,/AdonisUI.ClassicTheme;component/Resources.xaml"/>
|
<ResourceDictionary Source="pack://application:,,,/AdonisUI.ClassicTheme;component/Resources.xaml"/>
|
||||||
|
|
||||||
|
<ResourceDictionary Source="Views/Resources/Colors.xaml" />
|
||||||
|
<ResourceDictionary Source="Views/Resources/Icons.xaml" />
|
||||||
|
<ResourceDictionary Source="Views/Resources/Controls/ContextMenus/FileContextMenu.xaml" />
|
||||||
|
<ResourceDictionary Source="Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml" />
|
||||||
|
|
||||||
|
<ResourceDictionary Source="Views/Resources/Resources.xaml" />
|
||||||
|
<ResourceDictionary Source="Views/Resources/Controls/TiledExplorer/Resources.xaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
<Color x:Key="{x:Static adonisUi:Colors.AccentColor}">#206BD4</Color>
|
<Color x:Key="{x:Static adonisUi:Colors.AccentColor}">#206BD4</Color>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using CUE4Parse;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
using FModel.Settings;
|
using FModel.Settings;
|
||||||
|
|
@ -51,13 +52,20 @@ public partial class App
|
||||||
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
|
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
|
||||||
{
|
{
|
||||||
var currentDir = Directory.GetCurrentDirectory();
|
var currentDir = Directory.GetCurrentDirectory();
|
||||||
var dirInfo = new DirectoryInfo(currentDir);
|
try
|
||||||
if (dirInfo.Attributes.HasFlag(FileAttributes.Archive))
|
{
|
||||||
throw new Exception("FModel cannot be run from an archive file. Please extract it and try again.");
|
var outputDir = Directory.CreateDirectory(Path.Combine(currentDir, "Output"));
|
||||||
if (dirInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
|
using (File.Create(Path.Combine(outputDir.FullName, Path.GetRandomFileName()), 1, FileOptions.DeleteOnClose))
|
||||||
throw new Exception("FModel cannot be run from a read-only directory. Please move it to a writable location.");
|
{
|
||||||
|
|
||||||
UserSettings.Default.OutputDirectory = Path.Combine(currentDir, "Output");
|
}
|
||||||
|
|
||||||
|
UserSettings.Default.OutputDirectory = outputDir.FullName;
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException exception)
|
||||||
|
{
|
||||||
|
throw new Exception("FModel cannot create the output directory where it is currently located. Please move FModel.exe to a different location.", exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
|
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
|
||||||
|
|
@ -96,15 +104,20 @@ public partial class App
|
||||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
|
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
|
||||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
||||||
|
|
||||||
|
const string template = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Enriched}: {Message:lj}{NewLine}{Exception}";
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
|
.Enrich.With<SourceEnricher>()
|
||||||
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
|
.MinimumLevel.Verbose()
|
||||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
|
.WriteTo.Console(outputTemplate: template, theme: AnsiConsoleTheme.Literate)
|
||||||
|
.WriteTo.File(outputTemplate: template,
|
||||||
|
path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.log"))
|
||||||
#else
|
#else
|
||||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
|
.Enrich.With<CallerEnricher>()
|
||||||
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
|
.WriteTo.File(outputTemplate: template,
|
||||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
|
path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.log"))
|
||||||
#endif
|
#endif
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
Log.Information("Version {Version} ({CommitId})", Constants.APP_VERSION, Constants.APP_COMMIT_ID);
|
Log.Information("Version {Version} ({CommitId})", Constants.APP_VERSION, Constants.APP_COMMIT_ID);
|
||||||
Log.Information("{OS}", GetOperatingSystemProductName());
|
Log.Information("{OS}", GetOperatingSystemProductName());
|
||||||
|
|
@ -126,15 +139,15 @@ public partial class App
|
||||||
|
|
||||||
var messageBox = new MessageBoxModel
|
var messageBox = new MessageBoxModel
|
||||||
{
|
{
|
||||||
Text = $"An unhandled exception occurred: {e.Exception.Message}",
|
Text = $"An unhandled {e.Exception.GetBaseException().GetType()} occurred: {e.Exception.Message}",
|
||||||
Caption = "Fatal Error",
|
Caption = "Fatal Error",
|
||||||
Icon = MessageBoxImage.Error,
|
Icon = MessageBoxImage.Error,
|
||||||
Buttons = new[]
|
Buttons =
|
||||||
{
|
[
|
||||||
MessageBoxButtons.Custom("Reset Settings", EErrorKind.ResetSettings),
|
MessageBoxButtons.Custom("Reset Settings", EErrorKind.ResetSettings),
|
||||||
MessageBoxButtons.Custom("Restart", EErrorKind.Restart),
|
MessageBoxButtons.Custom("Restart", EErrorKind.Restart),
|
||||||
MessageBoxButtons.Custom("OK", EErrorKind.Ignore)
|
MessageBoxButtons.Custom("OK", EErrorKind.Ignore)
|
||||||
},
|
],
|
||||||
IsSoundEnabled = false
|
IsSoundEnabled = false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reflection;
|
|
||||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||||
using FModel.Extensions;
|
using CUE4Parse.Utils;
|
||||||
|
|
||||||
namespace FModel;
|
namespace FModel;
|
||||||
|
|
||||||
|
|
@ -12,8 +11,9 @@ public static class Constants
|
||||||
{
|
{
|
||||||
public static readonly string APP_PATH = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
|
public static readonly string APP_PATH = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
|
||||||
public static readonly string APP_VERSION = FileVersionInfo.GetVersionInfo(APP_PATH).FileVersion;
|
public static readonly string APP_VERSION = FileVersionInfo.GetVersionInfo(APP_PATH).FileVersion;
|
||||||
public static readonly string APP_COMMIT_ID = FileVersionInfo.GetVersionInfo(APP_PATH).ProductVersion.SubstringAfter('+');
|
public static readonly string APP_COMMIT_ID = FileVersionInfo.GetVersionInfo(APP_PATH).ProductVersion?.SubstringAfter('+');
|
||||||
public static readonly string APP_SHORT_COMMIT_ID = APP_COMMIT_ID[..7];
|
public static readonly string APP_SHORT_COMMIT_ID = APP_COMMIT_ID[..7];
|
||||||
|
public static readonly DateTime APP_BUILD_DATE = File.GetLastWriteTime(APP_PATH);
|
||||||
|
|
||||||
public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000";
|
public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||||
public static readonly FGuid ZERO_GUID = new(0U);
|
public static readonly FGuid ZERO_GUID = new(0U);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using CUE4Parse.UE4.Assets.Exports;
|
||||||
using CUE4Parse.UE4.Assets.Objects;
|
using CUE4Parse.UE4.Assets.Objects;
|
||||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||||
using CUE4Parse.UE4.Objects.UObject;
|
using CUE4Parse.UE4.Objects.UObject;
|
||||||
using FModel.Extensions;
|
using CUE4Parse.Utils;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using SkiaSharp.HarfBuzz;
|
using SkiaSharp.HarfBuzz;
|
||||||
|
|
@ -13,12 +13,11 @@ namespace FModel.Creator.Bases.FN;
|
||||||
public class BaseBundle : UCreator
|
public class BaseBundle : UCreator
|
||||||
{
|
{
|
||||||
private IList<BaseQuest> _quests;
|
private IList<BaseQuest> _quests;
|
||||||
private const int _headerHeight = 100;
|
|
||||||
|
|
||||||
public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style)
|
public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||||
{
|
{
|
||||||
Width = 1024;
|
Width = 1024;
|
||||||
Height = _headerHeight;
|
Height = 0;
|
||||||
Margin = 0;
|
Margin = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,84 +55,31 @@ public class BaseBundle : UCreator
|
||||||
if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") ||
|
if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") ||
|
||||||
!completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue;
|
!completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue;
|
||||||
|
|
||||||
|
var quest = new BaseQuest(completionCount, Style);
|
||||||
foreach (var reward in rewards)
|
foreach (var reward in rewards)
|
||||||
{
|
{
|
||||||
if (!reward.TryGetValue(out int quantity, "Quantity") ||
|
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue;
|
||||||
!reward.TryGetValue(out string templateId, "TemplateId") ||
|
quest.AddCompletionReward(itemDefinition);
|
||||||
!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(quest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Height += 256 * _quests.Count;
|
Height += 200 * _quests.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SKBitmap[] Draw()
|
public override SKBitmap[] Draw()
|
||||||
{
|
{
|
||||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Unpremul);
|
||||||
using var c = new SKCanvas(ret);
|
using var c = new SKCanvas(ret);
|
||||||
|
|
||||||
DrawHeader(c);
|
var y = 0;
|
||||||
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)
|
|
||||||
{
|
|
||||||
_headerPaint.TextSize -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var shaper = new CustomSKShaper(_headerPaint.Typeface);
|
|
||||||
c.DrawShapedText(shaper, DisplayName, Width / 2f, _headerHeight / 2f + _headerPaint.TextSize / 2 - 10, _headerPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawQuests(SKCanvas c)
|
|
||||||
{
|
|
||||||
var y = _headerHeight;
|
|
||||||
foreach (var quest in _quests)
|
foreach (var quest in _quests)
|
||||||
{
|
{
|
||||||
quest.DrawQuest(c, y);
|
quest.DrawQuest(c, y);
|
||||||
y += quest.Height;
|
y += quest.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [ret];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ public class BaseCommunity : BaseIcon
|
||||||
|
|
||||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||||
CheckGameplayTags(gameplayTags);
|
CheckGameplayTags(gameplayTags);
|
||||||
|
else if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
|
||||||
|
CheckGameplayTags(dataList);
|
||||||
|
|
||||||
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
||||||
CosmeticSource = cosmeticItem.Name.ToUpper();
|
CosmeticSource = cosmeticItem.Name.ToUpper();
|
||||||
|
|
||||||
|
|
@ -91,7 +94,7 @@ public class BaseCommunity : BaseIcon
|
||||||
return new[] { ret };
|
return new[] { ret };
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
protected override void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
||||||
{
|
{
|
||||||
if (_design == null) return;
|
if (_design == null) return;
|
||||||
if (_design.DrawSource)
|
if (_design.DrawSource)
|
||||||
|
|
@ -120,8 +123,9 @@ public class BaseCommunity : BaseIcon
|
||||||
{
|
{
|
||||||
if (!bShort) return base.GetCosmeticSeason(seasonNumber);
|
if (!bShort) return base.GetCosmeticSeason(seasonNumber);
|
||||||
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
||||||
(int chapterIdx, int seasonIdx) = GetInternalSID(int.Parse(s));
|
(string chapterIdx, string seasonIdx, bool onlySeason) = GetInternalSID(s);
|
||||||
return $"C{chapterIdx} S{seasonIdx}";
|
var prefix = int.TryParse(seasonIdx, out _) ? "S" : "";
|
||||||
|
return onlySeason ? $"{prefix}{seasonIdx}" : $"C{chapterIdx} {prefix}{seasonIdx}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private new void DrawBackground(SKCanvas c)
|
private new void DrawBackground(SKCanvas c)
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ public class BaseIcon : UCreator
|
||||||
// text
|
// text
|
||||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
|
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
|
||||||
DisplayName = displayName.Text;
|
DisplayName = displayName.Text;
|
||||||
if (Object.TryGetValue(out FText description, "Description", "ItemDescription", "BundleDescription", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
|
if (Object.TryGetValue(out FText description, "Description", "ItemDescription", "SetDescription", "BundleDescription", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
|
||||||
Description = description.Text;
|
Description = description.Text;
|
||||||
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
|
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
|
||||||
Description = string.Join('\n', descriptions.Select(x => x.Text));
|
Description = string.Join('\n', descriptions.Select(x => x.Text));
|
||||||
|
|
@ -88,6 +88,8 @@ public class BaseIcon : UCreator
|
||||||
{
|
{
|
||||||
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
|
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
|
||||||
|
|
||||||
|
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
|
||||||
|
CheckGameplayTags(dataList);
|
||||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||||
CheckGameplayTags(gameplayTags);
|
CheckGameplayTags(gameplayTags);
|
||||||
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
||||||
|
|
@ -157,7 +159,7 @@ public class BaseIcon : UCreator
|
||||||
{
|
{
|
||||||
if (uObject is UTexture2D texture2D)
|
if (uObject is UTexture2D texture2D)
|
||||||
{
|
{
|
||||||
SeriesBackground = texture2D.Decode();
|
SeriesBackground = texture2D.Decode().ToSkBitmap();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,46 +217,31 @@ public class BaseIcon : UCreator
|
||||||
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
|
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
|
||||||
name = displayName.Text;
|
name = displayName.Text;
|
||||||
|
|
||||||
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
|
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership", "\nPart of the <SetName>{0}</> set.");
|
||||||
return string.Format(format, name);
|
return Utils.RemoveHtmlTags(string.Format(format, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected (int, int) GetInternalSID(int number)
|
protected (string, string, bool) GetInternalSID(string number)
|
||||||
{
|
{
|
||||||
static int GetSeasonsInChapter(int chapter) => chapter switch
|
if (!Utils.TryLoadObject("FortniteGame/Plugins/GameFeatures/BattlePassBase/Content/DataTables/Athena_SeasonTitles.Athena_SeasonTitles", out UDataTable seasonTitles) ||
|
||||||
{
|
!seasonTitles.TryGetDataTableRow(number, StringComparison.InvariantCulture, out var row) ||
|
||||||
1 => 10,
|
!row.TryGetValue(out FText chapterText, "DisplayChapterText") ||
|
||||||
2 => 8,
|
!row.TryGetValue(out FText seasonText, "DisplaySeasonText") ||
|
||||||
3 => 4,
|
!row.TryGetValue(out FName displayType, "DisplayType"))
|
||||||
4 => 5,
|
return (string.Empty, string.Empty, true);
|
||||||
_ => 10
|
|
||||||
};
|
|
||||||
|
|
||||||
var chapterIdx = 0;
|
var onlySeason = displayType.Text.EndsWith("::OnlySeason") || (chapterText.Text == seasonText.Text && !int.TryParse(seasonText.Text, out _));
|
||||||
var seasonIdx = 0;
|
return (chapterText.Text, seasonText.Text, onlySeason);
|
||||||
while (number > 0)
|
|
||||||
{
|
|
||||||
var seasonsInChapter = GetSeasonsInChapter(++chapterIdx);
|
|
||||||
if (number > seasonsInChapter)
|
|
||||||
number -= seasonsInChapter;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
seasonIdx = number;
|
|
||||||
number = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (chapterIdx, seasonIdx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string GetCosmeticSeason(string seasonNumber)
|
protected string GetCosmeticSeason(string seasonNumber)
|
||||||
{
|
{
|
||||||
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
||||||
var initial = int.Parse(s);
|
(string chapterIdx, string seasonIdx, bool onlySeason) = GetInternalSID(s);
|
||||||
(int chapterIdx, int seasonIdx) = GetInternalSID(initial);
|
|
||||||
|
|
||||||
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
|
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
|
||||||
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
|
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
|
||||||
if (initial <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
|
if (onlySeason) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, seasonIdx)));
|
||||||
|
|
||||||
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
|
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
|
||||||
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
|
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
|
||||||
|
|
@ -262,7 +249,15 @@ public class BaseIcon : UCreator
|
||||||
return Utils.RemoveHtmlTags(string.Format(introduced, d));
|
return Utils.RemoveHtmlTags(string.Format(introduced, d));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
protected void CheckGameplayTags(FInstancedStruct[] dataList)
|
||||||
|
{
|
||||||
|
if (dataList.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FGameplayTagContainer _, "Tags") ?? false) is { NonConstStruct: not null } tags)
|
||||||
|
{
|
||||||
|
CheckGameplayTags(tags.NonConstStruct.Get<FGameplayTagContainer>("Tags"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
||||||
{
|
{
|
||||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
|
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
|
||||||
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
|
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
|
||||||
|
|
|
||||||
|
|
@ -84,44 +84,80 @@ public class BaseIconStats : BaseIcon
|
||||||
weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") &&
|
weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") &&
|
||||||
dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue))
|
dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue))
|
||||||
{
|
{
|
||||||
if (weaponRowValue.TryGetValue(out int bpc, "BulletsPerCartridge"))
|
weaponRowValue.TryGetValue(out float dmgPb, "DmgPB"); //Damage at point blank
|
||||||
|
weaponRowValue.TryGetValue(out float mdpc, "MaxDamagePerCartridge"); //Max damage a weapon can do in a single hit, usually used for shotguns
|
||||||
|
weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"); //Headshot multiplier
|
||||||
|
weaponRowValue.TryGetValue(out int clipSize, "ClipSize"); //Item magazine size
|
||||||
|
weaponRowValue.TryGetValue(out float firingRate, "FiringRate"); //Item firing rate, value is shots per second
|
||||||
|
weaponRowValue.TryGetValue(out float swingTime, "SwingTime"); //Item swing rate, value is swing per second
|
||||||
|
weaponRowValue.TryGetValue(out float armTime, "ArmTime"); //Time it takes for traps to be able to be set off
|
||||||
|
weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime"); //Time it takes for a weapon to reload
|
||||||
|
weaponRowValue.TryGetValue(out int bpc, "BulletsPerCartridge"); //Amount of pellets shot by a weapon at once, usually for shotguns
|
||||||
|
weaponRowValue.TryGetValue(out float heatMax, "OverheatingMaxValue"); //Maximum heat overheating weapons can hold before they need to cool off
|
||||||
|
weaponRowValue.TryGetValue(out float heatPerShot, "OverheatHeatingValue"); //Heat generated per shot on overheat weapons
|
||||||
|
weaponRowValue.TryGetValue(out float overheatCooldown, "OverheatedCooldownDelay"); //Cooldown after a weapon reaches its maximum heat capacity
|
||||||
|
weaponRowValue.TryGetValue(out int cartridgePerFire, "CartridgePerFire"); //Amount of bullets shot after pressing the fire button once
|
||||||
|
weaponRowValue.TryGetValue(out float burstFiringRate, "BurstFiringRate"); //Item firing rate during a burst, value is shots per second
|
||||||
{
|
{
|
||||||
var multiplier = bpc != 0f ? bpc : 1;
|
var multiplier = bpc != 0f ? bpc : 1;
|
||||||
if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f)
|
if (dmgPb != 0f)
|
||||||
{
|
{
|
||||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200));
|
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 160));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weaponRowValue.TryGetValue(out float mdpc, "MaxDamagePerCartridge") && mdpc >= 0f)
|
if (mdpc > 0f && dmgPb * dmgCritical * multiplier > mdpc)
|
||||||
{
|
{
|
||||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), mdpc, 200));
|
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), mdpc, 160));
|
||||||
}
|
}
|
||||||
else if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"))
|
|
||||||
|
else if (dmgCritical != 0f && dmgCritical != 1f && dmgPb != 0f)
|
||||||
{
|
{
|
||||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200));
|
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 160));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (clipSize > 999f || clipSize == 0f)
|
||||||
if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0)
|
|
||||||
{
|
{
|
||||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50));
|
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), Utils.GetLocalizedResource("", "0FAE8E5445029F2AA209ADB0FE49B23C", "Infinite"), -1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f)
|
else if (clipSize != 0f)
|
||||||
{
|
{
|
||||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15));
|
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 40));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f)
|
var burstEquation = cartridgePerFire != 0f && burstFiringRate != 0f ? (cartridgePerFire / (((cartridgePerFire - 1f) / burstFiringRate) + (1f / firingRate))) : 0f;
|
||||||
|
if (burstEquation != 0f)
|
||||||
{
|
{
|
||||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125));
|
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), burstEquation, 11));
|
||||||
|
}
|
||||||
|
else if (firingRate != 0f)
|
||||||
|
{
|
||||||
|
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 11));
|
||||||
|
}
|
||||||
|
else if (swingTime != 0f)
|
||||||
|
{
|
||||||
|
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), swingTime, 11));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime") && reloadTime != 0f)
|
if (armTime != 0f)
|
||||||
{
|
{
|
||||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 15));
|
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reloadTime != 0f && clipSize < 999f && clipSize != 0f)
|
||||||
|
{
|
||||||
|
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overheatCooldown != 0f && clipSize > 999f || overheatCooldown != 0f && clipSize == 0f)
|
||||||
|
{
|
||||||
|
_statistics.Add(new IconStat("Overheat Cooldown", overheatCooldown, 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heatMax != 0f && heatPerShot != 0f && clipSize > 999f || heatMax != 0f && heatPerShot != 0f && clipSize == 0f)
|
||||||
|
{
|
||||||
|
_statistics.Add(new IconStat("Shots to Overheat", Math.Ceiling(heatMax / heatPerShot), 80));
|
||||||
|
}
|
||||||
if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) ||
|
if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) ||
|
||||||
Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) &&
|
Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) &&
|
||||||
weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") &&
|
weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") &&
|
||||||
|
|
@ -215,6 +251,7 @@ public class BaseIconStats : BaseIcon
|
||||||
_informationPaint.TextSize = 50;
|
_informationPaint.TextSize = 50;
|
||||||
_informationPaint.Color = SKColors.White;
|
_informationPaint.Color = SKColors.White;
|
||||||
_informationPaint.Typeface = Utils.Typefaces.Bundle;
|
_informationPaint.Typeface = Utils.Typefaces.Bundle;
|
||||||
|
_informationPaint.FakeBoldText = true;
|
||||||
while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2)
|
while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2)
|
||||||
{
|
{
|
||||||
_informationPaint.TextSize -= 1;
|
_informationPaint.TextSize -= 1;
|
||||||
|
|
@ -284,6 +321,10 @@ public class IconStat
|
||||||
_statPaint.Color = SKColors.White;
|
_statPaint.Color = SKColors.White;
|
||||||
c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint);
|
c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint);
|
||||||
|
|
||||||
|
if (_maxValue == -1) //fill bar if max value is set to -1, for things that don't return a number here but should still be represented as the maximum value
|
||||||
|
{
|
||||||
|
c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint);
|
||||||
|
}
|
||||||
if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return;
|
if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return;
|
||||||
if (floatValue < 0)
|
if (floatValue < 0)
|
||||||
floatValue = 0;
|
floatValue = 0;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ public class BaseJuno : BaseIcon
|
||||||
{
|
{
|
||||||
foreach (var data in additionalData)
|
foreach (var data in additionalData)
|
||||||
{
|
{
|
||||||
if (data.NonConstStruct?.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SmallPreviewImage") ?? false)
|
if (data.NonConstStruct?.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SmallPreviewImage") == true)
|
||||||
{
|
{
|
||||||
_character.Preview = Utils.GetBitmap(largePreview);
|
_character.Preview = Utils.GetBitmap(largePreview);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CUE4Parse.UE4.Assets.Exports;
|
using CUE4Parse.UE4.Assets.Exports;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||||
|
|
@ -7,7 +8,7 @@ using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||||
using CUE4Parse.UE4.Assets.Objects;
|
using CUE4Parse.UE4.Assets.Objects;
|
||||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||||
using CUE4Parse.UE4.Objects.UObject;
|
using CUE4Parse.UE4.Objects.UObject;
|
||||||
using FModel.Extensions;
|
using CUE4Parse.Utils;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using SkiaSharp.HarfBuzz;
|
using SkiaSharp.HarfBuzz;
|
||||||
|
|
@ -17,9 +18,8 @@ namespace FModel.Creator.Bases.FN;
|
||||||
public class BaseQuest : BaseIcon
|
public class BaseQuest : BaseIcon
|
||||||
{
|
{
|
||||||
private int _count;
|
private int _count;
|
||||||
private Reward _reward;
|
private readonly List<Reward> _rewards;
|
||||||
private readonly bool _screenLayer;
|
private readonly bool _screenLayer;
|
||||||
private readonly string[] _unauthorizedReward = { "Token", "ChallengeBundle", "GiftBox" };
|
|
||||||
|
|
||||||
public string NextQuestName { get; private set; }
|
public string NextQuestName { get; private set; }
|
||||||
|
|
||||||
|
|
@ -27,15 +27,15 @@ public class BaseQuest : BaseIcon
|
||||||
{
|
{
|
||||||
Margin = 0;
|
Margin = 0;
|
||||||
Width = 1024;
|
Width = 1024;
|
||||||
Height = 256;
|
Height = 200;
|
||||||
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
|
_rewards = [];
|
||||||
if (uObject != null)
|
if (uObject != null)
|
||||||
{
|
{
|
||||||
_screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase);
|
_screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion
|
public BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion
|
||||||
{
|
{
|
||||||
var description = completionCount < 0 ?
|
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_All", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">all {0} challenges</> to earn the reward item") :
|
||||||
|
|
@ -44,14 +44,14 @@ public class BaseQuest : BaseIcon
|
||||||
DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0);
|
DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseQuest(int completionCount, FSoftObjectPath itemDefinition, EIconStyle style) : this(completionCount, style) // completion
|
public void AddCompletionReward(FSoftObjectPath itemDefinition)
|
||||||
{
|
{
|
||||||
_reward = Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject) ? new Reward(uObject) : new Reward();
|
_rewards.Add(itemDefinition.TryLoad(out UObject uObject) ? new Reward(uObject) : new Reward());
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseQuest(int completionCount, int quantity, string reward, EIconStyle style) : this(completionCount, style) // completion
|
public void AddCompletionReward(int quantity, string reward)
|
||||||
{
|
{
|
||||||
_reward = new Reward(quantity, reward);
|
_rewards.Add(new Reward(quantity, reward));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ParseForInfo()
|
public override void ParseForInfo()
|
||||||
|
|
@ -69,12 +69,7 @@ public class BaseQuest : BaseIcon
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(ShortDescription))
|
Description = string.Empty;
|
||||||
Description = ShortDescription;
|
|
||||||
if (string.IsNullOrEmpty(DisplayName) && !string.IsNullOrEmpty(Description))
|
|
||||||
DisplayName = Description;
|
|
||||||
if (DisplayName == Description)
|
|
||||||
Description = string.Empty;
|
|
||||||
|
|
||||||
if ((Object.TryGetValue(out FSoftObjectPath icon, "QuestGiverWidgetIcon", "NotificationIconOverride") &&
|
if ((Object.TryGetValue(out FSoftObjectPath icon, "QuestGiverWidgetIcon", "NotificationIconOverride") &&
|
||||||
Utils.TryLoadObject(icon.AssetPathName.Text, out UObject iconObject)) ||
|
Utils.TryLoadObject(icon.AssetPathName.Text, out UObject iconObject)) ||
|
||||||
|
|
@ -98,8 +93,8 @@ public class BaseQuest : BaseIcon
|
||||||
if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0)
|
if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0)
|
||||||
{
|
{
|
||||||
// actual description doesn't exist
|
// actual description doesn't exist
|
||||||
if (string.IsNullOrEmpty(Description) && objectives[0].TryGetValue(out FText description, "Description"))
|
if (string.IsNullOrEmpty(DisplayName) && objectives[0].TryGetValue(out FText description, "Description"))
|
||||||
Description = description.Text;
|
DisplayName = description.Text;
|
||||||
|
|
||||||
// ObjectiveCompletionCount doesn't exist
|
// ObjectiveCompletionCount doesn't exist
|
||||||
if (_count == 0)
|
if (_count == 0)
|
||||||
|
|
@ -111,77 +106,65 @@ public class BaseQuest : BaseIcon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.TryGetValue(out FStructFallback[] rewards, "Rewards"))
|
if (Object.TryGetValue(out FPackageIndex[] questDefinitionComponents, "QuestDefinitionComponents"))
|
||||||
{
|
{
|
||||||
foreach (var reward in rewards)
|
foreach (var questDefinitionComponent in questDefinitionComponents)
|
||||||
{
|
{
|
||||||
if (!reward.TryGetValue(out FStructFallback itemPrimaryAssetId, "ItemPrimaryAssetId") ||
|
if (!questDefinitionComponent.Name.StartsWith("FortQuestDefinitionComponent_Rewards") ||
|
||||||
!reward.TryGetValue(out int quantity, "Quantity")) continue;
|
!questDefinitionComponent.TryLoad(out var rewardComponent) ||
|
||||||
|
!rewardComponent.TryGetValue(out FInstancedStruct[] questRewardsArray, "QuestRewardsArray")) continue;
|
||||||
|
|
||||||
if (!itemPrimaryAssetId.TryGetValue(out FStructFallback primaryAssetType, "PrimaryAssetType") ||
|
foreach (var questReward in questRewardsArray)
|
||||||
!itemPrimaryAssetId.TryGetValue(out FName primaryAssetName, "PrimaryAssetName") ||
|
|
||||||
!primaryAssetType.TryGetValue(out FName name, "Name")) continue;
|
|
||||||
|
|
||||||
if (name.Text.Equals("Quest", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
NextQuestName = primaryAssetName.Text;
|
if (questReward.NonConstStruct.TryGetValue(out FStructFallback[] resourceDataTableRewards, "ResourceDataTableRewards") &&
|
||||||
|
resourceDataTableRewards.Length > 0 && resourceDataTableRewards[0].TryGetValue(out FStructFallback tableRowEntry, "TableRowEntry") &&
|
||||||
|
tableRowEntry.TryGetValue(out UDataTable rewardsTable, "DataTable") &&
|
||||||
|
tableRowEntry.TryGetValue(out FName rowName, "RowName") &&
|
||||||
|
rewardsTable.TryGetDataTableRow(rowName.Text, StringComparison.InvariantCulture, out var row) &&
|
||||||
|
row.TryGetValue(out FSoftObjectPath resourceDefinition, "ResourceDefinition") &&
|
||||||
|
row.TryGetValue(out int quantity, "Quantity"))
|
||||||
|
{
|
||||||
|
_rewards.Add(new Reward(quantity, resourceDefinition));
|
||||||
|
}
|
||||||
|
else if (questReward.NonConstStruct.TryGetValue(out FInstancedStruct[] rewards, "CosmeticRewards", "CurrencyRewards", "VariantTokenRewards", "ResourceRewards"))
|
||||||
|
{
|
||||||
|
foreach (var reward in rewards)
|
||||||
|
{
|
||||||
|
if (reward.NonConstStruct.TryGetValue(out FSoftObjectPath cosmeticDefinition, "CosmeticDefinition", "CurrencyDefinition", "VariantTokenDefinition", "ResourceDefinition"))
|
||||||
|
{
|
||||||
|
if (reward.NonConstStruct.TryGetValue(out int count, "CurrencyCount", "ResourceCount"))
|
||||||
|
{
|
||||||
|
_rewards.Add(new Reward(count, cosmeticDefinition));
|
||||||
|
}
|
||||||
|
else if (cosmeticDefinition.TryLoad(out var cosmetic))
|
||||||
|
{
|
||||||
|
_rewards.Add(new Reward(cosmetic));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!_unauthorizedReward.Contains(name.Text))
|
|
||||||
{
|
|
||||||
_reward = new Reward(quantity, $"{name}:{primaryAssetName}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_reward == null)
|
|
||||||
{
|
|
||||||
FName rowName = null;
|
|
||||||
if (Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
|
|
||||||
rowName = new FName("Default");
|
|
||||||
else if (Object.TryGetValue(out FStructFallback[] rewardTableRows, "IndividualRewardTableRows") &&
|
|
||||||
rewardTableRows.Length > 0 && rewardTableRows[0].TryGetValue(out rowName, "RowName") &&
|
|
||||||
rewardTableRows[0].TryGetValue(out rewardsTable, "DataTable")) {}
|
|
||||||
|
|
||||||
if (rewardsTable != null && rowName != null && rewardsTable.TryGetDataTableRow(rowName.Text, 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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_reward ??= new Reward();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawQuest(SKCanvas c, int y)
|
public void DrawQuest(SKCanvas c, int y)
|
||||||
{
|
{
|
||||||
DrawBackground(c, y);
|
var x = Preview is null ? 0 : Height + 10;
|
||||||
|
DrawBackground(c, x, y);
|
||||||
DrawPreview(c, y);
|
DrawPreview(c, y);
|
||||||
DrawTexts(c, y);
|
DrawTexts(c, x + 50, y, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SKBitmap[] Draw()
|
public override SKBitmap[] Draw()
|
||||||
{
|
{
|
||||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Unpremul);
|
||||||
using var c = new SKCanvas(ret);
|
using var c = new SKCanvas(ret);
|
||||||
|
|
||||||
DrawQuest(c, 0);
|
DrawQuest(c, 0);
|
||||||
|
|
||||||
return new[] { ret };
|
return [ret];
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ReformatString(string s, string completionCount, bool isAll)
|
private string ReformatString(string s, string completionCount, bool isAll)
|
||||||
|
|
@ -202,77 +185,77 @@ public class BaseQuest : BaseIcon
|
||||||
private readonly SKPaint _informationPaint = new()
|
private readonly SKPaint _informationPaint = new()
|
||||||
{
|
{
|
||||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||||
Color = SKColor.Parse("#262630")
|
Color = SKColor.Parse("#183E94"),
|
||||||
};
|
};
|
||||||
|
|
||||||
private void DrawBackground(SKCanvas c, int y)
|
private void DrawBackground(SKCanvas c, int x, int y)
|
||||||
{
|
{
|
||||||
c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint);
|
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width * 0.75f, y + Height * 0.5f), Width * 0.75f,
|
||||||
|
[SKColor.Parse("#1565D0"), SKColor.Parse("#1B1150")], SKShaderTileMode.Clamp);
|
||||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4,
|
c.DrawRoundRect(new SKRect(x, y, Width, y + Height), 25, 25, _informationPaint);
|
||||||
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)
|
private void DrawPreview(SKCanvas c, int y)
|
||||||
{
|
{
|
||||||
|
if (Preview is null) return;
|
||||||
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
|
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
|
||||||
c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, Height - Margin, y + Height), ImagePaint);
|
|
||||||
|
var rect = new SKRect(0, y, Height, y + Height);
|
||||||
|
|
||||||
|
c.Save();
|
||||||
|
using (var roundRectPath = new SKPath())
|
||||||
|
{
|
||||||
|
roundRectPath.AddRoundRect(rect, 15, 15);
|
||||||
|
c.ClipPath(roundRectPath, antialias: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
_informationPaint.Shader = SKShader.CreateLinearGradient(
|
||||||
|
new SKPoint(rect.Left, rect.Bottom),
|
||||||
|
new SKPoint(rect.Left, rect.Top),
|
||||||
|
[
|
||||||
|
_informationPaint.Color,
|
||||||
|
_informationPaint.Color.WithAlpha(0)
|
||||||
|
],
|
||||||
|
[0, 1],
|
||||||
|
SKShaderTileMode.Clamp
|
||||||
|
);
|
||||||
|
c.DrawRect(rect, _informationPaint);
|
||||||
|
c.DrawBitmap(Preview, rect, ImagePaint);
|
||||||
|
c.Restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawTexts(SKCanvas c, int y)
|
private void DrawTexts(SKCanvas c, int x, int y, int padding)
|
||||||
{
|
{
|
||||||
_informationPaint.Shader = null;
|
_informationPaint.Shader = null;
|
||||||
|
_informationPaint.Color = SKColors.White;
|
||||||
|
|
||||||
|
float maxX = Width - padding;
|
||||||
|
float steps = Height * 0.5f;
|
||||||
|
foreach (var reward in _rewards)
|
||||||
|
{
|
||||||
|
reward.DrawQuest(c, new SKRect(maxX - steps, y + padding, maxX, y + padding + steps));
|
||||||
|
maxX -= steps;
|
||||||
|
}
|
||||||
|
maxX -= steps * 0.5f;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(DisplayName))
|
if (!string.IsNullOrWhiteSpace(DisplayName))
|
||||||
{
|
{
|
||||||
_informationPaint.TextSize = 40;
|
_informationPaint.TextSize = 25;
|
||||||
_informationPaint.Color = SKColors.White;
|
|
||||||
_informationPaint.Typeface = Utils.Typefaces.Bundle;
|
_informationPaint.Typeface = Utils.Typefaces.Bundle;
|
||||||
while (_informationPaint.MeasureText(DisplayName) > Width - Height - 10)
|
Utils.DrawMultilineText(c, DisplayName, Width - padding, 0, SKTextAlign.Left,
|
||||||
{
|
new SKRect(x, y + padding, maxX, Height - padding * 1.5f), _informationPaint, out _);
|
||||||
_informationPaint.TextSize -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var shaper = new CustomSKShaper(_informationPaint.Typeface);
|
|
||||||
c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var outY = y + 75f;
|
maxX -= steps * 0.5f;
|
||||||
if (!string.IsNullOrWhiteSpace(Description))
|
if (maxX > Width * 0.7f) maxX = Width * 0.7f;
|
||||||
{
|
|
||||||
_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)
|
if (_count > 0)
|
||||||
{
|
{
|
||||||
_informationPaint.TextSize = 25;
|
_informationPaint.TextSize = 20;
|
||||||
_informationPaint.Color = SKColors.White;
|
|
||||||
_informationPaint.Typeface = Utils.Typefaces.BundleNumber;
|
_informationPaint.Typeface = Utils.Typefaces.BundleNumber;
|
||||||
c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint);
|
c.DrawText($"0/{_count}", new SKPoint(maxX, y + Height - padding), _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));
|
_informationPaint.Color = SKColor.Parse("#121A45").WithAlpha(200);
|
||||||
|
c.DrawRoundRect(new SKRect(x, y + Height - padding - 12.5f, maxX - 12.5f, y + Height - padding), 5, 5, _informationPaint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,18 @@ public class Reward
|
||||||
_rewardQuantity = "x0";
|
_rewardQuantity = "x0";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Reward(int quantity, FSoftObjectPath softObjectPath) : this()
|
||||||
|
{
|
||||||
|
_rewardQuantity = $"{quantity / 1000f}k";
|
||||||
|
|
||||||
|
if (softObjectPath.TryLoad(out UObject d))
|
||||||
|
{
|
||||||
|
_theReward = new BaseIcon(d, EIconStyle.Default);
|
||||||
|
_theReward.ParseForReward(false);
|
||||||
|
_theReward.Border[0] = SKColors.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
|
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +45,7 @@ public class Reward
|
||||||
|
|
||||||
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
|
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p))
|
if (!Utils.TryLoadObject($"FortniteGame/Plugins/GameFeatures/BRCosmetics/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_theReward = new BaseIcon(p, EIconStyle.Default);
|
_theReward = new BaseIcon(p, EIconStyle.Default);
|
||||||
|
|
@ -51,7 +63,6 @@ public class Reward
|
||||||
_theReward = new BaseIcon(uObject, EIconStyle.Default);
|
_theReward = new BaseIcon(uObject, EIconStyle.Default);
|
||||||
_theReward.ParseForReward(false);
|
_theReward.ParseForReward(false);
|
||||||
_theReward.Border[0] = SKColors.White;
|
_theReward.Border[0] = SKColors.White;
|
||||||
_rewardQuantity = _theReward.DisplayName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly SKPaint _rewardPaint = new()
|
private readonly SKPaint _rewardPaint = new()
|
||||||
|
|
@ -61,28 +72,30 @@ public class Reward
|
||||||
|
|
||||||
public void DrawQuest(SKCanvas c, SKRect rect)
|
public void DrawQuest(SKCanvas c, SKRect rect)
|
||||||
{
|
{
|
||||||
_rewardPaint.TextSize = 50;
|
_rewardPaint.TextSize = 25;
|
||||||
if (HasReward())
|
if (HasReward())
|
||||||
{
|
{
|
||||||
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).Resize((int) rect.Height), new SKPoint(rect.Left, rect.Top), _rewardPaint);
|
var origin = new SKPoint(rect.Left, rect.Top);
|
||||||
|
if (!string.IsNullOrEmpty(_rewardQuantity))
|
||||||
_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;
|
origin.Y -= _rewardPaint.TextSize / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
var shaper = new CustomSKShaper(_rewardPaint.Typeface);
|
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).Resize((int) rect.Height), origin, _rewardPaint);
|
||||||
c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Height + 25, rect.MidY + 20, _rewardPaint);
|
if (string.IsNullOrEmpty(_rewardQuantity)) return;
|
||||||
}
|
|
||||||
else
|
_rewardPaint.TextAlign = SKTextAlign.Center;
|
||||||
{
|
_rewardPaint.FakeBoldText = true;
|
||||||
_rewardPaint.Color = SKColors.White;
|
_rewardPaint.Color = SKColors.White;
|
||||||
_rewardPaint.Typeface = Utils.Typefaces.BundleNumber;
|
var shaper = new CustomSKShaper(Utils.Typefaces.BundleNumber);
|
||||||
c.DrawText("No Reward", new SKPoint(rect.Left, rect.MidY + 20), _rewardPaint);
|
c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Width * 0.5f, rect.Bottom, _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)
|
public void DrawSeasonWin(SKCanvas c, int size)
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,24 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using CUE4Parse.UE4.Assets.Exports;
|
using CUE4Parse.UE4.Assets.Exports;
|
||||||
using FModel.Creator.Bases;
|
using FModel.Creator.Bases;
|
||||||
using FModel.Creator.Bases.BB;
|
|
||||||
using FModel.Creator.Bases.FN;
|
using FModel.Creator.Bases.FN;
|
||||||
using FModel.Creator.Bases.MV;
|
using FModel.Creator.Bases.MV;
|
||||||
using FModel.Creator.Bases.SB;
|
|
||||||
|
|
||||||
namespace FModel.Creator;
|
namespace FModel.Creator;
|
||||||
|
|
||||||
public class CreatorPackage : IDisposable
|
public class CreatorPackage : IDisposable
|
||||||
{
|
{
|
||||||
private UObject _object;
|
private string _pkgName;
|
||||||
|
private string _exportType;
|
||||||
|
private Lazy<UObject> _object;
|
||||||
private EIconStyle _style;
|
private EIconStyle _style;
|
||||||
|
|
||||||
public CreatorPackage(UObject uObject, EIconStyle style)
|
public CreatorPackage(string packageName, string exportType, Lazy<UObject> uObject, EIconStyle style)
|
||||||
{
|
{
|
||||||
|
_pkgName = packageName;
|
||||||
|
_exportType = exportType;
|
||||||
_object = uObject;
|
_object = uObject;
|
||||||
_style = style;
|
_style = style;
|
||||||
}
|
}
|
||||||
|
|
@ -28,12 +31,12 @@ public class CreatorPackage : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool TryConstructCreator(out UCreator creator)
|
public bool TryConstructCreator([MaybeNullWhen(false)] out UCreator creator)
|
||||||
{
|
{
|
||||||
switch (_object.ExportType)
|
// TODO: convert to a type based system
|
||||||
|
switch (_exportType)
|
||||||
{
|
{
|
||||||
// Fortnite
|
// Fortnite
|
||||||
case "FortCreativeWeaponMeleeItemDefinition":
|
|
||||||
case "AthenaConsumableEmoteItemDefinition":
|
case "AthenaConsumableEmoteItemDefinition":
|
||||||
case "AthenaSkyDiveContrailItemDefinition":
|
case "AthenaSkyDiveContrailItemDefinition":
|
||||||
case "AthenaLoadingScreenItemDefinition":
|
case "AthenaLoadingScreenItemDefinition":
|
||||||
|
|
@ -44,9 +47,13 @@ public class CreatorPackage : IDisposable
|
||||||
case "AthenaCharacterItemDefinition":
|
case "AthenaCharacterItemDefinition":
|
||||||
case "AthenaMapMarkerItemDefinition":
|
case "AthenaMapMarkerItemDefinition":
|
||||||
case "AthenaBackpackItemDefinition":
|
case "AthenaBackpackItemDefinition":
|
||||||
|
case "CosmeticShoesItemDefinition":
|
||||||
|
case "CosmeticCompanionItemDefinition":
|
||||||
|
case "CosmeticCompanionReactFXItemDefinition":
|
||||||
case "AthenaPickaxeItemDefinition":
|
case "AthenaPickaxeItemDefinition":
|
||||||
case "AthenaGadgetItemDefinition":
|
case "AthenaGadgetItemDefinition":
|
||||||
case "AthenaGliderItemDefinition":
|
case "AthenaGliderItemDefinition":
|
||||||
|
case "AthenaHatItemDefinition":
|
||||||
case "AthenaSprayItemDefinition":
|
case "AthenaSprayItemDefinition":
|
||||||
case "AthenaDanceItemDefinition":
|
case "AthenaDanceItemDefinition":
|
||||||
case "AthenaEmojiItemDefinition":
|
case "AthenaEmojiItemDefinition":
|
||||||
|
|
@ -88,6 +95,7 @@ public class CreatorPackage : IDisposable
|
||||||
case "FortBackpackItemDefinition":
|
case "FortBackpackItemDefinition":
|
||||||
case "FortEventQuestMapDataAsset":
|
case "FortEventQuestMapDataAsset":
|
||||||
case "FortBuildingItemDefinition":
|
case "FortBuildingItemDefinition":
|
||||||
|
case "FortItemCacheItemDefinition":
|
||||||
case "FortWeaponModItemDefinition":
|
case "FortWeaponModItemDefinition":
|
||||||
case "FortCodeTokenItemDefinition":
|
case "FortCodeTokenItemDefinition":
|
||||||
case "FortSchematicItemDefinition":
|
case "FortSchematicItemDefinition":
|
||||||
|
|
@ -100,7 +108,7 @@ public class CreatorPackage : IDisposable
|
||||||
case "FortConsumableItemDefinition":
|
case "FortConsumableItemDefinition":
|
||||||
case "StWFortAccoladeItemDefinition":
|
case "StWFortAccoladeItemDefinition":
|
||||||
case "FortAccountBuffItemDefinition":
|
case "FortAccountBuffItemDefinition":
|
||||||
case "FortWeaponMeleeItemDefinition":
|
case "FortFOBCoreDecoItemDefinition":
|
||||||
case "FortPlayerPerksItemDefinition":
|
case "FortPlayerPerksItemDefinition":
|
||||||
case "FortPlaysetPropItemDefinition":
|
case "FortPlaysetPropItemDefinition":
|
||||||
case "FortPrerollDataItemDefinition":
|
case "FortPrerollDataItemDefinition":
|
||||||
|
|
@ -110,6 +118,7 @@ public class CreatorPackage : IDisposable
|
||||||
case "FortPlayerAugmentItemDefinition":
|
case "FortPlayerAugmentItemDefinition":
|
||||||
case "FortSmartBuildingItemDefinition":
|
case "FortSmartBuildingItemDefinition":
|
||||||
case "FortGiftBoxUnlockItemDefinition":
|
case "FortGiftBoxUnlockItemDefinition":
|
||||||
|
case "FortCreativeGadgetItemDefinition":
|
||||||
case "FortWeaponModItemDefinitionOptic":
|
case "FortWeaponModItemDefinitionOptic":
|
||||||
case "RadioContentSourceItemDefinition":
|
case "RadioContentSourceItemDefinition":
|
||||||
case "FortPlaysetGrenadeItemDefinition":
|
case "FortPlaysetGrenadeItemDefinition":
|
||||||
|
|
@ -134,9 +143,7 @@ public class CreatorPackage : IDisposable
|
||||||
case "FortCampaignHeroLoadoutItemDefinition":
|
case "FortCampaignHeroLoadoutItemDefinition":
|
||||||
case "FortConditionalResourceItemDefinition":
|
case "FortConditionalResourceItemDefinition":
|
||||||
case "FortChallengeBundleScheduleDefinition":
|
case "FortChallengeBundleScheduleDefinition":
|
||||||
case "FortWeaponMeleeDualWieldItemDefinition":
|
|
||||||
case "FortDailyRewardScheduleTokenDefinition":
|
case "FortDailyRewardScheduleTokenDefinition":
|
||||||
case "FortCreativeWeaponRangedItemDefinition":
|
|
||||||
case "FortVehicleCosmeticsItemDefinition_Body":
|
case "FortVehicleCosmeticsItemDefinition_Body":
|
||||||
case "FortVehicleCosmeticsItemDefinition_Skin":
|
case "FortVehicleCosmeticsItemDefinition_Skin":
|
||||||
case "FortVehicleCosmeticsItemDefinition_Wheel":
|
case "FortVehicleCosmeticsItemDefinition_Wheel":
|
||||||
|
|
@ -148,43 +155,46 @@ public class CreatorPackage : IDisposable
|
||||||
case "FortVehicleCosmeticsItemDefinition_EngineAudio":
|
case "FortVehicleCosmeticsItemDefinition_EngineAudio":
|
||||||
creator = _style switch
|
creator = _style switch
|
||||||
{
|
{
|
||||||
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
|
EIconStyle.Cataba => new BaseCommunity(_object.Value, _style, "Cataba"),
|
||||||
_ => new BaseIcon(_object, _style)
|
_ => new BaseIcon(_object.Value, _style)
|
||||||
};
|
};
|
||||||
return true;
|
return true;
|
||||||
case "JunoAthenaCharacterItemOverrideDefinition":
|
case "JunoAthenaCharacterItemOverrideDefinition":
|
||||||
case "JunoAthenaDanceItemOverrideDefinition":
|
case "JunoAthenaDanceItemOverrideDefinition":
|
||||||
creator = new BaseJuno(_object, _style);
|
creator = new BaseJuno(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "FortTandemCharacterData":
|
case "FortTandemCharacterData":
|
||||||
creator = new BaseTandem(_object, _style);
|
creator = new BaseTandem(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "FortTrapItemDefinition":
|
case "FortTrapItemDefinition":
|
||||||
case "FortSpyTechItemDefinition":
|
case "FortSpyTechItemDefinition":
|
||||||
case "FortAccoladeItemDefinition":
|
case "FortAccoladeItemDefinition":
|
||||||
case "FortContextTrapItemDefinition":
|
case "FortContextTrapItemDefinition":
|
||||||
|
case "FortWeaponMeleeItemDefinition":
|
||||||
case "FortWeaponRangedItemDefinition":
|
case "FortWeaponRangedItemDefinition":
|
||||||
|
case "FortCreativeWeaponMeleeItemDefinition":
|
||||||
|
case "FortWeaponMeleeDualWieldItemDefinition":
|
||||||
|
case "FortCreativeWeaponRangedItemDefinition":
|
||||||
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
|
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
|
||||||
creator = new BaseIconStats(_object, _style);
|
creator = new BaseIconStats(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "FortItemSeriesDefinition":
|
case "FortItemSeriesDefinition":
|
||||||
creator = new BaseSeries(_object, _style);
|
creator = new BaseSeries(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "MaterialInstanceConstant"
|
case "MaterialInstanceConstant"
|
||||||
when _object.Owner != null &&
|
when _pkgName.Contains("/MI_OfferImages/", StringComparison.OrdinalIgnoreCase) ||
|
||||||
(_object.Owner.Name.Contains("/MI_OfferImages/", StringComparison.OrdinalIgnoreCase) ||
|
_pkgName.Contains("/RenderSwitch_Materials/", StringComparison.OrdinalIgnoreCase) ||
|
||||||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
|
_pkgName.Contains("/MI_BPTile/", StringComparison.OrdinalIgnoreCase):
|
||||||
_object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)):
|
creator = new BaseMaterialInstance(_object.Value, _style);
|
||||||
creator = new BaseMaterialInstance(_object, _style);
|
|
||||||
return true;
|
return true;
|
||||||
case "AthenaItemShopOfferDisplayData":
|
case "AthenaItemShopOfferDisplayData":
|
||||||
creator = new BaseOfferDisplayData(_object, _style);
|
creator = new BaseOfferDisplayData(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "FortMtxOfferData":
|
case "FortMtxOfferData":
|
||||||
creator = new BaseMtxOffer(_object, _style);
|
creator = new BaseMtxOffer(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "FortPlaylistAthena":
|
case "FortPlaylistAthena":
|
||||||
creator = new BasePlaylist(_object, _style);
|
creator = new BasePlaylist(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "FortFeatItemDefinition":
|
case "FortFeatItemDefinition":
|
||||||
case "FortQuestItemDefinition":
|
case "FortQuestItemDefinition":
|
||||||
|
|
@ -192,17 +202,18 @@ public class CreatorPackage : IDisposable
|
||||||
case "FortQuestItemDefinition_Campaign":
|
case "FortQuestItemDefinition_Campaign":
|
||||||
case "AthenaDailyQuestDefinition":
|
case "AthenaDailyQuestDefinition":
|
||||||
case "FortUrgentQuestItemDefinition":
|
case "FortUrgentQuestItemDefinition":
|
||||||
creator = new Bases.FN.BaseQuest(_object, _style);
|
creator = new Bases.FN.BaseQuest(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "FortCompendiumItemDefinition":
|
case "FortCompendiumItemDefinition":
|
||||||
|
case "FortCompendiumBundleDefinition":
|
||||||
case "FortChallengeBundleItemDefinition":
|
case "FortChallengeBundleItemDefinition":
|
||||||
creator = new BaseBundle(_object, _style);
|
creator = new BaseBundle(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
// case "AthenaSeasonItemDefinition":
|
// case "AthenaSeasonItemDefinition":
|
||||||
// creator = new BaseSeason(_object, _style);
|
// creator = new BaseSeason(_object, _style);
|
||||||
// return true;
|
// return true;
|
||||||
case "FortItemAccessTokenType":
|
case "FortItemAccessTokenType":
|
||||||
creator = new BaseItemAccessToken(_object, _style);
|
creator = new BaseItemAccessToken(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "FortCreativeOption":
|
case "FortCreativeOption":
|
||||||
case "PlaylistUserOptionEnum":
|
case "PlaylistUserOptionEnum":
|
||||||
|
|
@ -216,14 +227,14 @@ public class CreatorPackage : IDisposable
|
||||||
case "PlaylistUserTintedIconIntEnum":
|
case "PlaylistUserTintedIconIntEnum":
|
||||||
case "PlaylistUserOptionPrimaryAsset":
|
case "PlaylistUserOptionPrimaryAsset":
|
||||||
case "PlaylistUserOptionCollisionProfileEnum":
|
case "PlaylistUserOptionCollisionProfileEnum":
|
||||||
creator = new BaseUserControl(_object, _style);
|
creator = new BaseUserControl(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
// PandaGame
|
// PandaGame
|
||||||
case "CharacterData":
|
case "CharacterData":
|
||||||
creator = new BaseFighter(_object, _style);
|
creator = new BaseFighter(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "PerkGroup":
|
case "PerkGroup":
|
||||||
creator = new BasePerkGroup(_object, _style);
|
creator = new BasePerkGroup(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "StatTrackingBundleData":
|
case "StatTrackingBundleData":
|
||||||
case "HydraSyncedDataAsset":
|
case "HydraSyncedDataAsset":
|
||||||
|
|
@ -236,10 +247,10 @@ public class CreatorPackage : IDisposable
|
||||||
case "TauntData":
|
case "TauntData":
|
||||||
case "SkinData":
|
case "SkinData":
|
||||||
case "PerkData":
|
case "PerkData":
|
||||||
creator = new BasePandaIcon(_object, _style);
|
creator = new BasePandaIcon(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
case "QuestData":
|
case "QuestData":
|
||||||
creator = new Bases.MV.BaseQuest(_object, _style);
|
creator = new Bases.MV.BaseQuest(_object.Value, _style);
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
creator = null;
|
creator = null;
|
||||||
|
|
@ -247,7 +258,7 @@ public class CreatorPackage : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"{_object.ExportType} | {_style}";
|
public override string ToString() => $"{_exportType} | {_style}";
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public class Typefaces
|
||||||
private const string _EXT = ".ufont";
|
private const string _EXT = ".ufont";
|
||||||
|
|
||||||
// FortniteGame
|
// FortniteGame
|
||||||
private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
|
private const string _FORTNITE_BASE_PATH = "FortniteGame/Plugins/FortUILibrary/Content/Fonts/";
|
||||||
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
|
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
|
||||||
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
|
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_BLACK = "BurbankBigRegular-Black";
|
||||||
|
|
@ -67,7 +67,7 @@ public class Typefaces
|
||||||
|
|
||||||
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
|
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
|
||||||
|
|
||||||
switch (viewModel.Provider.InternalGameName.ToUpperInvariant())
|
switch (viewModel.Provider.ProjectName.ToUpperInvariant())
|
||||||
{
|
{
|
||||||
case "FORTNITEGAME":
|
case "FORTNITEGAME":
|
||||||
{
|
{
|
||||||
|
|
@ -105,7 +105,7 @@ public class Typefaces
|
||||||
_ => _BURBANK_SMALL_BOLD
|
_ => _BURBANK_SMALL_BOLD
|
||||||
} + _EXT, true);
|
} + _EXT, true);
|
||||||
|
|
||||||
BundleNumber = OnTheFly(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT);
|
BundleNumber = OnTheFly(_FORTNITE_BASE_PATH + _BURBANK_SMALL_BOLD + _EXT);
|
||||||
|
|
||||||
Bundle = OnTheFly(_FORTNITE_BASE_PATH +
|
Bundle = OnTheFly(_FORTNITE_BASE_PATH +
|
||||||
language switch
|
language switch
|
||||||
|
|
@ -116,7 +116,7 @@ public class Typefaces
|
||||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||||
_ => string.Empty
|
_ => _BURBANK_SMALL_BOLD
|
||||||
} + _EXT, true) ?? BundleNumber;
|
} + _EXT, true) ?? BundleNumber;
|
||||||
|
|
||||||
TandemDisplayName = OnTheFly(_FORTNITE_BASE_PATH +
|
TandemDisplayName = OnTheFly(_FORTNITE_BASE_PATH +
|
||||||
|
|
@ -193,7 +193,7 @@ public class Typefaces
|
||||||
public SKTypeface OnTheFly(string path, bool fallback = false)
|
public SKTypeface OnTheFly(string path, bool fallback = false)
|
||||||
{
|
{
|
||||||
if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return fallback ? null : Default;
|
if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return fallback ? null : Default;
|
||||||
var m = new MemoryStream(data) { Position = 0 };
|
var m = new MemoryStream(data) { Position = _viewModel.Provider.Versions.Game >= EGame.GAME_UE5_6 ? 4 : 0 };
|
||||||
return SKTypeface.FromStream(m);
|
return SKTypeface.FromStream(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ using CUE4Parse.UE4.Objects.UObject;
|
||||||
using CUE4Parse.UE4.Versions;
|
using CUE4Parse.UE4.Versions;
|
||||||
using CUE4Parse_Conversion.Textures;
|
using CUE4Parse_Conversion.Textures;
|
||||||
using CUE4Parse.UE4.Assets.Objects;
|
using CUE4Parse.UE4.Assets.Objects;
|
||||||
|
using CUE4Parse.Utils;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Extensions;
|
using FModel.Extensions;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
|
|
@ -127,9 +128,9 @@ public static class Utils
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) { Position = 0 });
|
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(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.Load<UTexture2D>());
|
||||||
public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null;
|
public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null;
|
||||||
public static SKBitmap GetBitmap(UTexture2D texture) => texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform);
|
public static SKBitmap GetBitmap(UTexture2D texture) => texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform).ToSkBitmap();
|
||||||
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
|
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
|
||||||
|
|
||||||
public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height)
|
public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height)
|
||||||
|
|
@ -160,12 +161,7 @@ public static class Utils
|
||||||
// fullpath must be either without any extension or with the export objectname
|
// 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
|
public static bool TryLoadObject<T>(string fullPath, out T export) where T : UObject
|
||||||
{
|
{
|
||||||
return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export);
|
return _applicationView.CUE4Parse.Provider.TryLoadPackageObject(fullPath, out export);
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<UObject> LoadExports(string packagePath)
|
|
||||||
{
|
|
||||||
return _applicationView.CUE4Parse.Provider.LoadAllObjects(packagePath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f)
|
public static float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f)
|
||||||
|
|
@ -199,11 +195,11 @@ public static class Utils
|
||||||
|
|
||||||
public static string GetLocalizedResource(string @namespace, string key, string defaultValue)
|
public static string GetLocalizedResource(string @namespace, string key, string defaultValue)
|
||||||
{
|
{
|
||||||
return _applicationView.CUE4Parse.Provider.GetLocalizedString(@namespace, key, defaultValue);
|
return _applicationView.CUE4Parse.Provider.Internationalization.SafeGet(@namespace, key, defaultValue);
|
||||||
}
|
}
|
||||||
public static string GetLocalizedResource<T>(T @enum) where T : Enum
|
public static string GetLocalizedResource<T>(T @enum) where T : Enum
|
||||||
{
|
{
|
||||||
var resource = _applicationView.CUE4Parse.Provider.GetLocalizedString("", @enum.GetDescription(), @enum.ToString());
|
var resource = _applicationView.CUE4Parse.Provider.Internationalization.SafeGet("", @enum.GetDescription(), @enum.ToString());
|
||||||
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(resource.ToLower());
|
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(resource.ToLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -417,4 +413,4 @@ public static class Utils
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using FModel.Extensions;
|
||||||
|
|
||||||
namespace FModel;
|
namespace FModel;
|
||||||
|
|
||||||
|
|
@ -60,19 +61,11 @@ public enum ELoadingMode
|
||||||
[Description("All (New)")]
|
[Description("All (New)")]
|
||||||
AllButNew,
|
AllButNew,
|
||||||
[Description("All (Modified)")]
|
[Description("All (Modified)")]
|
||||||
AllButModified
|
AllButModified,
|
||||||
|
[Description("All (Except Patched Assets)")]
|
||||||
|
AllButPatched,
|
||||||
}
|
}
|
||||||
|
|
||||||
// public enum EUpdateMode
|
|
||||||
// {
|
|
||||||
// [Description("Stable")]
|
|
||||||
// Stable,
|
|
||||||
// [Description("Beta")]
|
|
||||||
// Beta,
|
|
||||||
// [Description("QA Testing")]
|
|
||||||
// Qa
|
|
||||||
// }
|
|
||||||
|
|
||||||
public enum ECompressedAudio
|
public enum ECompressedAudio
|
||||||
{
|
{
|
||||||
[Description("Play the decompressed data")]
|
[Description("Play the decompressed data")]
|
||||||
|
|
@ -112,5 +105,46 @@ public enum EBulkType
|
||||||
Textures = 1 << 2,
|
Textures = 1 << 2,
|
||||||
Meshes = 1 << 3,
|
Meshes = 1 << 3,
|
||||||
Skeletons = 1 << 4,
|
Skeletons = 1 << 4,
|
||||||
Animations = 1 << 5
|
Animations = 1 << 5,
|
||||||
|
Audio = 1 << 6
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EAssetCategory : uint
|
||||||
|
{
|
||||||
|
All = AssetCategoryExtensions.CategoryBase + (0 << 16),
|
||||||
|
Blueprints = AssetCategoryExtensions.CategoryBase + (1 << 16),
|
||||||
|
BlueprintGeneratedClass = Blueprints + 1,
|
||||||
|
WidgetBlueprintGeneratedClass = Blueprints + 2,
|
||||||
|
AnimBlueprintGeneratedClass = Blueprints + 3,
|
||||||
|
RigVMBlueprintGeneratedClass = Blueprints + 4,
|
||||||
|
UserDefinedEnum = Blueprints + 5,
|
||||||
|
UserDefinedStruct = Blueprints + 6,
|
||||||
|
//Metadata
|
||||||
|
Blueprint = Blueprints + 8,
|
||||||
|
CookedMetaData = Blueprints + 9,
|
||||||
|
Mesh = AssetCategoryExtensions.CategoryBase + (2 << 16),
|
||||||
|
StaticMesh = Mesh + 1,
|
||||||
|
SkeletalMesh = Mesh + 2,
|
||||||
|
Skeleton = Mesh + 3,
|
||||||
|
Texture = AssetCategoryExtensions.CategoryBase + (3 << 16),
|
||||||
|
Materials = AssetCategoryExtensions.CategoryBase + (4 << 16),
|
||||||
|
Material = Materials + 1,
|
||||||
|
MaterialEditorData = Materials + 2,
|
||||||
|
MaterialFunction = Materials + 3,
|
||||||
|
MaterialParameterCollection = Materials + 4,
|
||||||
|
Animation = AssetCategoryExtensions.CategoryBase + (5 << 16),
|
||||||
|
Level = AssetCategoryExtensions.CategoryBase + (6 << 16),
|
||||||
|
World = Level + 1,
|
||||||
|
BuildData = Level + 2,
|
||||||
|
LevelSequence = Level + 3,
|
||||||
|
Foliage = Level + 4,
|
||||||
|
Data = AssetCategoryExtensions.CategoryBase + (7 << 16),
|
||||||
|
ItemDefinitionBase = Data + 1,
|
||||||
|
CurveBase = Data + 2,
|
||||||
|
PhysicsAsset = Data + 3,
|
||||||
|
Media = AssetCategoryExtensions.CategoryBase + (8 << 16),
|
||||||
|
Audio = Media + 1,
|
||||||
|
Video = Media + 2,
|
||||||
|
Font = Media + 3,
|
||||||
|
Particle = AssetCategoryExtensions.CategoryBase + (9 << 16),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
30
FModel/Extensions/AssetCategoryExtensions.cs
Normal file
30
FModel/Extensions/AssetCategoryExtensions.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FModel.Extensions;
|
||||||
|
|
||||||
|
public static class AssetCategoryExtensions
|
||||||
|
{
|
||||||
|
public const uint CategoryBase = 0x00010000;
|
||||||
|
|
||||||
|
public static EAssetCategory GetBaseCategory(this EAssetCategory category)
|
||||||
|
{
|
||||||
|
return (EAssetCategory) ((uint) category & 0xFFFF0000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsOfCategory(this EAssetCategory item, EAssetCategory category)
|
||||||
|
{
|
||||||
|
return item.GetBaseCategory() == category.GetBaseCategory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsBaseCategory(this EAssetCategory category)
|
||||||
|
{
|
||||||
|
return category == category.GetBaseCategory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<EAssetCategory> GetBaseCategories()
|
||||||
|
{
|
||||||
|
return Enum.GetValues<EAssetCategory>().Where(c => c.IsBaseCategory());
|
||||||
|
}
|
||||||
|
}
|
||||||
70
FModel/Extensions/CUE4ParseExtensions.cs
Normal file
70
FModel/Extensions/CUE4ParseExtensions.cs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
using System;
|
||||||
|
using CUE4Parse.FileProvider;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
|
using CUE4Parse.UE4.Assets;
|
||||||
|
using CUE4Parse.UE4.Objects.UObject;
|
||||||
|
using FModel.Settings;
|
||||||
|
|
||||||
|
namespace FModel.Extensions;
|
||||||
|
|
||||||
|
public static class CUE4ParseExtensions
|
||||||
|
{
|
||||||
|
public class LoadPackageResult
|
||||||
|
{
|
||||||
|
// more than 1 export per page currently break the inner package navigation feature
|
||||||
|
// if you have 1k exports per page, at page 2, you click on export index 932
|
||||||
|
// it will find the export index 932 in the current page, which would realistically be 1932
|
||||||
|
// fix would be to use InclusiveStart and ExclusiveEnd to determine the page the export index is in
|
||||||
|
// giving the document access to this would fix the issue and we could re-use Package instead of reloading it but it's quite a bit of work atm
|
||||||
|
|
||||||
|
private const int PaginationThreshold = 5000;
|
||||||
|
private const int MaxExportPerPage = 1;
|
||||||
|
|
||||||
|
public IPackage Package;
|
||||||
|
public int RequestedIndex;
|
||||||
|
|
||||||
|
public bool IsPaginated => Package.ExportMapLength >= PaginationThreshold;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// index of the first export on the current page
|
||||||
|
/// this index is the starting point for additional data preview
|
||||||
|
///
|
||||||
|
/// it can be >0 even if <see cref="IsPaginated"/> is false if we want to focus data preview on a specific export
|
||||||
|
/// in this case, we will display all exports but only the focused one will be checked for data preview
|
||||||
|
/// </summary>
|
||||||
|
public int InclusiveStart => Math.Max(0, RequestedIndex - RequestedIndex % MaxExportPerPage);
|
||||||
|
/// <summary>
|
||||||
|
/// last exclusive export index of the current page
|
||||||
|
/// </summary>
|
||||||
|
public int ExclusiveEnd => IsPaginated
|
||||||
|
? Math.Min(InclusiveStart + MaxExportPerPage, Package.ExportMapLength)
|
||||||
|
: Package.ExportMapLength;
|
||||||
|
public int PageSize => ExclusiveEnd - InclusiveStart;
|
||||||
|
|
||||||
|
public string TabTitleExtra => IsPaginated ? $"Export{(PageSize > 1 ? "s" : "")} {InclusiveStart}{(PageSize > 1 ? $"-{ExclusiveEnd - 1}" : "")} of {Package.ExportMapLength - 1}" : null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// display all exports unless paginated
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="save">if we save the data we will display all exports even if <see cref="IsPaginated"/> is true</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object GetDisplayData(bool save = false) => !save && IsPaginated
|
||||||
|
? Package.GetExports(InclusiveStart, PageSize)
|
||||||
|
: Package.GetExports();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LoadPackageResult GetLoadPackageResult(this IFileProvider provider, GameFile file, string objectName = null)
|
||||||
|
{
|
||||||
|
var result = new LoadPackageResult { Package = provider.LoadPackage(file) };
|
||||||
|
if (result.IsPaginated || (result.Package.HasFlags(EPackageFlags.PKG_ContainsMap) && UserSettings.Default.PreviewWorlds)) // focus on UWorld if it's a map we want to preview
|
||||||
|
{
|
||||||
|
result.RequestedIndex = result.Package.GetExportIndex(file.NameWithoutExtension);
|
||||||
|
if (objectName != null)
|
||||||
|
{
|
||||||
|
result.RequestedIndex = int.TryParse(objectName, out var index) ? index : result.Package.GetExportIndex(objectName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using CUE4Parse.UE4.Versions;
|
||||||
|
|
||||||
namespace FModel.Extensions;
|
namespace FModel.Extensions;
|
||||||
|
|
||||||
|
|
@ -18,7 +19,8 @@ public static class EnumExtensions
|
||||||
|
|
||||||
var suffix = $"{value:D}";
|
var suffix = $"{value:D}";
|
||||||
var current = Convert.ToInt32(suffix);
|
var current = Convert.ToInt32(suffix);
|
||||||
var target = current & ~0xF;
|
var mask = value.GetType() == typeof(EGame) ? ~0xFFFF : ~0xF;
|
||||||
|
var target = current & mask;
|
||||||
if (current != target)
|
if (current != target)
|
||||||
{
|
{
|
||||||
var values = Enum.GetValues(value.GetType());
|
var values = Enum.GetValues(value.GetType());
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,14 @@ using ICSharpCode.AvalonEdit.Document;
|
||||||
|
|
||||||
namespace FModel.Extensions;
|
namespace FModel.Extensions;
|
||||||
|
|
||||||
public static class StringExtensions
|
public static partial class StringExtensions
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static string GetReadableSize(double size)
|
public static string GetReadableSize(double size)
|
||||||
{
|
{
|
||||||
if (size == 0) return "0 B";
|
if (size == 0) return "0 B";
|
||||||
|
|
||||||
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
|
string[] sizes = ["B", "KB", "MB", "GB", "TB"];
|
||||||
var order = 0;
|
var order = 0;
|
||||||
while (size >= 1024 && order < sizes.Length - 1)
|
while (size >= 1024 && order < sizes.Length - 1)
|
||||||
{
|
{
|
||||||
|
|
@ -24,94 +24,30 @@ public static class StringExtensions
|
||||||
return $"{size:# ###.##} {sizes[order]}".TrimStart();
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int GetNameLineNumber(this string s, string lineToFind)
|
public static int GetNameLineNumber(this string s, string lineToFind)
|
||||||
{
|
{
|
||||||
|
if (KismetRegex().IsMatch(lineToFind))
|
||||||
|
return s.GetKismetLineNumber(lineToFind);
|
||||||
|
|
||||||
if (int.TryParse(lineToFind, out var index))
|
if (int.TryParse(lineToFind, out var index))
|
||||||
return s.GetLineNumber(index);
|
return s.GetLineNumber(index);
|
||||||
|
|
||||||
lineToFind = $" \"Name\": \"{lineToFind}\",";
|
return s.GetNameLineNumberText($" \"Name\": \"{lineToFind}\",");
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int GetNameLineNumberText(this string s, string lineToFind)
|
||||||
|
{
|
||||||
using var reader = new StringReader(s);
|
using var reader = new StringReader(s);
|
||||||
var lineNum = 0;
|
var lineNum = 0;
|
||||||
string line;
|
while (reader.ReadLine() is { } line)
|
||||||
while ((line = reader.ReadLine()) != null)
|
|
||||||
{
|
{
|
||||||
lineNum++;
|
lineNum++;
|
||||||
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
|
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
|
||||||
return lineNum;
|
return lineNum;
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
@ -134,18 +70,17 @@ public static class StringExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int GetKismetLineNumber(this string s, string input)
|
private static int GetKismetLineNumber(this string s, string input)
|
||||||
{
|
{
|
||||||
var match = Regex.Match(input, @"^(.+)\[(\d+)\]$");
|
var match = KismetRegex().Match(input);
|
||||||
var name = match.Groups[1].Value;
|
var name = match.Groups[1].Value;
|
||||||
int index = int.Parse(match.Groups[2].Value);
|
int index = int.Parse(match.Groups[2].Value);
|
||||||
var lineToFind = $" \"Name\": \"{name}\",";
|
var lineToFind = $" \"Name\": \"{name}\",";
|
||||||
var offset = $"\"StatementIndex\": {index}";
|
var offset = $"\"StatementIndex\": {index}";
|
||||||
using var reader = new StringReader(s);
|
using var reader = new StringReader(s);
|
||||||
var lineNum = 0;
|
var lineNum = 0;
|
||||||
string line;
|
|
||||||
|
|
||||||
while ((line = reader.ReadLine()) != null)
|
while (reader.ReadLine() is { } line)
|
||||||
{
|
{
|
||||||
lineNum++;
|
lineNum++;
|
||||||
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
|
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
|
||||||
|
|
@ -161,7 +96,7 @@ public static class StringExtensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
@ -169,8 +104,7 @@ public static class StringExtensions
|
||||||
{
|
{
|
||||||
using var reader = new StringReader(s);
|
using var reader = new StringReader(s);
|
||||||
var lineNum = 0;
|
var lineNum = 0;
|
||||||
string line;
|
while (reader.ReadLine() is { } line)
|
||||||
while ((line = reader.ReadLine()) != null)
|
|
||||||
{
|
{
|
||||||
lineNum++;
|
lineNum++;
|
||||||
if (line.Equals(" {"))
|
if (line.Equals(" {"))
|
||||||
|
|
@ -180,6 +114,9 @@ public static class StringExtensions
|
||||||
return lineNum + 1;
|
return lineNum + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"^(.+)\[(\d+)\]$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
|
||||||
|
private static partial Regex KismetRegex();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,25 @@
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsPublishable>true</IsPublishable>
|
<IsPublishable>true</IsPublishable>
|
||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
<PublishSingleFile Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">true</PublishSingleFile>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||||
<StartupObject>FModel.App</StartupObject>
|
<StartupObject>FModel.App</StartupObject>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
<NoWarn>1701;1702;NU1701</NoWarn>
|
<NoWarn>1701;1702;NU1701</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
<NoWarn>NU1701</NoWarn>
|
<NoWarn>NU1701</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
|
||||||
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Resources\android.png" />
|
<None Remove="Resources\android.png" />
|
||||||
<None Remove="Resources\apple.png" />
|
<None Remove="Resources\apple.png" />
|
||||||
|
|
@ -102,6 +104,7 @@
|
||||||
<None Remove="Resources\npcleftside.png" />
|
<None Remove="Resources\npcleftside.png" />
|
||||||
<None Remove="Resources\default.frag" />
|
<None Remove="Resources\default.frag" />
|
||||||
<None Remove="Resources\default.vert" />
|
<None Remove="Resources\default.vert" />
|
||||||
|
<None Remove="Resources\spline.vert" />
|
||||||
<None Remove="Resources\grid.frag" />
|
<None Remove="Resources\grid.frag" />
|
||||||
<None Remove="Resources\grid.vert" />
|
<None Remove="Resources\grid.vert" />
|
||||||
<None Remove="Resources\skybox.frag" />
|
<None Remove="Resources\skybox.frag" />
|
||||||
|
|
@ -122,6 +125,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Resources\Json.xshd" />
|
<EmbeddedResource Include="Resources\Json.xshd" />
|
||||||
<EmbeddedResource Include="Resources\Ini.xshd" />
|
<EmbeddedResource Include="Resources\Ini.xshd" />
|
||||||
|
<EmbeddedResource Include="Resources\spline.vert" />
|
||||||
<EmbeddedResource Include="Resources\Verse.xshd" />
|
<EmbeddedResource Include="Resources\Verse.xshd" />
|
||||||
<EmbeddedResource Include="Resources\Xml.xshd" />
|
<EmbeddedResource Include="Resources\Xml.xshd" />
|
||||||
<EmbeddedResource Include="Resources\Cpp.xshd" />
|
<EmbeddedResource Include="Resources\Cpp.xshd" />
|
||||||
|
|
@ -149,22 +153,24 @@
|
||||||
<PackageReference Include="AdonisUI" Version="1.17.1" />
|
<PackageReference Include="AdonisUI" Version="1.17.1" />
|
||||||
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
|
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
|
||||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
|
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
|
||||||
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
|
<PackageReference Include="AvalonEdit" Version="6.3.1.120" />
|
||||||
<PackageReference Include="CSCore" Version="1.2.1.2" />
|
<PackageReference Include="CSCore" Version="1.2.1.2" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageReference Include="DiscordRichPresence" Version="1.6.1.70" />
|
||||||
<PackageReference Include="EpicManifestParser" Version="2.3.3" />
|
<PackageReference Include="EpicManifestParser" Version="2.4.1" />
|
||||||
<PackageReference Include="ImGui.NET" Version="1.91.0.1" />
|
<PackageReference Include="EpicManifestParser.ZlibngDotNetDecompressor" Version="1.0.1" />
|
||||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
|
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="NVorbis" Version="0.10.5" />
|
<PackageReference Include="NVorbis" Version="0.10.5" />
|
||||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||||
<PackageReference Include="OpenTK" Version="4.8.2" />
|
<PackageReference Include="OpenTK" Version="4.9.4" />
|
||||||
<PackageReference Include="RestSharp" Version="112.1.0" />
|
<PackageReference Include="RestSharp" Version="113.0.0" />
|
||||||
<PackageReference Include="Serilog" Version="4.0.2" />
|
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.8" />
|
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.9" />
|
||||||
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
|
<PackageReference Include="Svg.Skia" Version="3.2.1" />
|
||||||
|
<PackageReference Include="Twizzle.ImGui-Bundle.NET" Version="1.91.5.2" />
|
||||||
|
<PackageReference Include="VirtualizingWrapPanel" Version="2.3.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
5
FModel/FModel.slnx
Normal file
5
FModel/FModel.slnx
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<Solution>
|
||||||
|
<Project Path="../CUE4Parse/CUE4Parse-Conversion/CUE4Parse-Conversion.csproj" />
|
||||||
|
<Project Path="../CUE4Parse/CUE4Parse/CUE4Parse.csproj" />
|
||||||
|
<Project Path="FModel.csproj" />
|
||||||
|
</Solution>
|
||||||
23
FModel/Framework/FakeCUE4Parse.cs
Normal file
23
FModel/Framework/FakeCUE4Parse.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
using CUE4Parse.Compression;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
|
using CUE4Parse.UE4.Assets.Objects;
|
||||||
|
using CUE4Parse.UE4.Readers;
|
||||||
|
|
||||||
|
namespace FModel.Framework;
|
||||||
|
|
||||||
|
public class FakeGameFile(string path) : GameFile(path, 0)
|
||||||
|
{
|
||||||
|
public override bool IsEncrypted => false;
|
||||||
|
public override CompressionMethod CompressionMethod => CompressionMethod.None;
|
||||||
|
|
||||||
|
public override byte[] Read(FByteBulkDataHeader? header = null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override FArchive CreateReader(FByteBulkDataHeader? header = null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,12 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using FModel.Settings;
|
using FModel.Settings;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using ImGuizmoNET;
|
||||||
using OpenTK.Graphics.OpenGL4;
|
using OpenTK.Graphics.OpenGL4;
|
||||||
using OpenTK.Windowing.Desktop;
|
using OpenTK.Windowing.Desktop;
|
||||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||||
|
|
@ -54,11 +54,11 @@ public class ImGuiController : IDisposable
|
||||||
|
|
||||||
int major = GL.GetInteger(GetPName.MajorVersion);
|
int major = GL.GetInteger(GetPName.MajorVersion);
|
||||||
int minor = GL.GetInteger(GetPName.MinorVersion);
|
int minor = GL.GetInteger(GetPName.MinorVersion);
|
||||||
|
|
||||||
KHRDebugAvailable = (major == 4 && minor >= 3) || IsExtensionSupported("KHR_debug");
|
KHRDebugAvailable = (major == 4 && minor >= 3) || IsExtensionSupported("KHR_debug");
|
||||||
|
|
||||||
IntPtr context = ImGui.CreateContext();
|
IntPtr context = ImGui.CreateContext();
|
||||||
ImGui.SetCurrentContext(context);
|
ImGui.SetCurrentContext(context);
|
||||||
|
ImGuizmo.SetImGuiContext(context);
|
||||||
|
|
||||||
var io = ImGui.GetIO();
|
var io = ImGui.GetIO();
|
||||||
unsafe
|
unsafe
|
||||||
|
|
@ -69,29 +69,24 @@ public class ImGuiController : IDisposable
|
||||||
FontNormal = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 16 * DpiScale);
|
FontNormal = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 16 * DpiScale);
|
||||||
FontBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.ttf", 16 * DpiScale);
|
FontBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.ttf", 16 * DpiScale);
|
||||||
FontSemiBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 16 * DpiScale);
|
FontSemiBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 16 * DpiScale);
|
||||||
|
io.Fonts.AddFontDefault();
|
||||||
|
io.Fonts.Build(); // Build font atlas
|
||||||
|
|
||||||
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
|
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
|
||||||
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
|
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
|
||||||
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
|
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
|
||||||
io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines;
|
io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines;
|
||||||
|
io.ConfigDockingWithShift = true;
|
||||||
|
io.ConfigWindowsMoveFromTitleBarOnly = true;
|
||||||
|
io.BackendRendererUserData = 0;
|
||||||
|
|
||||||
CreateDeviceResources();
|
CreateDeviceResources();
|
||||||
|
|
||||||
SetPerFrameImGuiData(1f / 60f);
|
|
||||||
|
|
||||||
ImGui.NewFrame();
|
|
||||||
_frameBegun = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Normal() => PushFont(FontNormal);
|
||||||
public void Bold() => PushFont(FontBold);
|
public void Bold() => PushFont(FontBold);
|
||||||
public void SemiBold() => PushFont(FontSemiBold);
|
public void SemiBold() => PushFont(FontSemiBold);
|
||||||
|
|
||||||
public void PopFont()
|
|
||||||
{
|
|
||||||
ImGui.PopFont();
|
|
||||||
PushFont(FontNormal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PushFont(ImFontPtr ptr) => ImGui.PushFont(ptr);
|
private void PushFont(ImFontPtr ptr) => ImGui.PushFont(ptr);
|
||||||
|
|
||||||
public void WindowResized(int width, int height)
|
public void WindowResized(int width, int height)
|
||||||
|
|
@ -129,34 +124,42 @@ public class ImGuiController : IDisposable
|
||||||
|
|
||||||
RecreateFontDeviceTexture();
|
RecreateFontDeviceTexture();
|
||||||
|
|
||||||
string VertexSource = @"#version 460 core
|
string VertexSource = @"#version 330 core
|
||||||
|
|
||||||
uniform mat4 projection_matrix;
|
uniform mat4 projection_matrix;
|
||||||
|
|
||||||
layout(location = 0) in vec2 in_position;
|
layout(location = 0) in vec2 in_position;
|
||||||
layout(location = 1) in vec2 in_texCoord;
|
layout(location = 1) in vec2 in_texCoord;
|
||||||
layout(location = 2) in vec4 in_color;
|
layout(location = 2) in vec4 in_color;
|
||||||
|
|
||||||
out vec4 color;
|
out vec4 color;
|
||||||
out vec2 texCoord;
|
out vec2 texCoord;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_Position = projection_matrix * vec4(in_position, 0, 1);
|
gl_Position = projection_matrix * vec4(in_position, 0, 1);
|
||||||
color = in_color;
|
color = in_color;
|
||||||
texCoord = in_texCoord;
|
texCoord = in_texCoord;
|
||||||
}";
|
}";
|
||||||
string FragmentSource = @"#version 460 core
|
string FragmentSource = @"#version 330 core
|
||||||
|
|
||||||
uniform sampler2D in_fontTexture;
|
uniform sampler2D in_fontTexture;
|
||||||
|
|
||||||
in vec4 color;
|
in vec4 color;
|
||||||
in vec2 texCoord;
|
in vec2 texCoord;
|
||||||
|
|
||||||
out vec4 outputColor;
|
out vec4 outputColor;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
outputColor = color * texture(in_fontTexture, texCoord);
|
outputColor = color * texture(in_fontTexture, texCoord);
|
||||||
}";
|
}";
|
||||||
|
|
||||||
_shader = CreateProgram("ImGui", VertexSource, FragmentSource);
|
_shader = CreateProgram("ImGui", VertexSource, FragmentSource);
|
||||||
_shaderProjectionMatrixLocation = GL.GetUniformLocation(_shader, "projection_matrix");
|
_shaderProjectionMatrixLocation = GL.GetUniformLocation(_shader, "projection_matrix");
|
||||||
_shaderFontTextureLocation = GL.GetUniformLocation(_shader, "in_fontTexture");
|
_shaderFontTextureLocation = GL.GetUniformLocation(_shader, "in_fontTexture");
|
||||||
|
|
||||||
int stride = Unsafe.SizeOf<ImDrawVert>();
|
int stride = Marshal.SizeOf<ImDrawVert>();
|
||||||
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, stride, 0);
|
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, stride, 0);
|
||||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, 8);
|
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, 8);
|
||||||
GL.VertexAttribPointer(2, 4, VertexAttribPointerType.UnsignedByte, true, stride, 16);
|
GL.VertexAttribPointer(2, 4, VertexAttribPointerType.UnsignedByte, true, stride, 16);
|
||||||
|
|
@ -222,7 +225,6 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
||||||
ImGui.Render();
|
ImGui.Render();
|
||||||
RenderImDrawData(ImGui.GetDrawData());
|
RenderImDrawData(ImGui.GetDrawData());
|
||||||
}
|
}
|
||||||
CheckGLError("End of frame");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -240,6 +242,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
||||||
|
|
||||||
_frameBegun = true;
|
_frameBegun = true;
|
||||||
ImGui.NewFrame();
|
ImGui.NewFrame();
|
||||||
|
ImGuizmo.BeginFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -249,7 +252,6 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
||||||
private void SetPerFrameImGuiData(float deltaSeconds)
|
private void SetPerFrameImGuiData(float deltaSeconds)
|
||||||
{
|
{
|
||||||
ImGuiIOPtr io = ImGui.GetIO();
|
ImGuiIOPtr io = ImGui.GetIO();
|
||||||
// if (io.WantSaveIniSettings) ImGui.SaveIniSettingsToDisk(_iniPath);
|
|
||||||
io.DisplaySize = new Vector2(
|
io.DisplaySize = new Vector2(
|
||||||
_windowWidth / _scaleFactor.X,
|
_windowWidth / _scaleFactor.X,
|
||||||
_windowHeight / _scaleFactor.Y);
|
_windowHeight / _scaleFactor.Y);
|
||||||
|
|
@ -262,6 +264,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
||||||
private void UpdateImGuiInput(GameWindow wnd)
|
private void UpdateImGuiInput(GameWindow wnd)
|
||||||
{
|
{
|
||||||
ImGuiIOPtr io = ImGui.GetIO();
|
ImGuiIOPtr io = ImGui.GetIO();
|
||||||
|
|
||||||
var mState = wnd.MouseState;
|
var mState = wnd.MouseState;
|
||||||
var kState = wnd.KeyboardState;
|
var kState = wnd.KeyboardState;
|
||||||
|
|
||||||
|
|
@ -273,7 +276,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
||||||
io.AddMouseButtonEvent(4, mState[MouseButton.Button2]);
|
io.AddMouseButtonEvent(4, mState[MouseButton.Button2]);
|
||||||
io.AddMouseWheelEvent(mState.ScrollDelta.X, mState.ScrollDelta.Y);
|
io.AddMouseWheelEvent(mState.ScrollDelta.X, mState.ScrollDelta.Y);
|
||||||
|
|
||||||
foreach (Keys key in Enum.GetValues(typeof(Keys)))
|
foreach (Keys key in Enum.GetValues<Keys>())
|
||||||
{
|
{
|
||||||
if (key == Keys.Unknown) continue;
|
if (key == Keys.Unknown) continue;
|
||||||
io.AddKeyEvent(TranslateKey(key), kState.IsKeyDown(key));
|
io.AddKeyEvent(TranslateKey(key), kState.IsKeyDown(key));
|
||||||
|
|
@ -291,7 +294,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
||||||
io.KeySuper = kState.IsKeyDown(Keys.LeftSuper) || kState.IsKeyDown(Keys.RightSuper);
|
io.KeySuper = kState.IsKeyDown(Keys.LeftSuper) || kState.IsKeyDown(Keys.RightSuper);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PressChar(char keyChar)
|
internal void PressChar(char keyChar)
|
||||||
{
|
{
|
||||||
PressedChars.Add(keyChar);
|
PressedChars.Add(keyChar);
|
||||||
}
|
}
|
||||||
|
|
@ -337,7 +340,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
||||||
{
|
{
|
||||||
ImDrawListPtr cmd_list = draw_data.CmdLists[i];
|
ImDrawListPtr cmd_list = draw_data.CmdLists[i];
|
||||||
|
|
||||||
int vertexSize = cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>();
|
int vertexSize = cmd_list.VtxBuffer.Size * Marshal.SizeOf<ImDrawVert>();
|
||||||
if (vertexSize > _vertexBufferSize)
|
if (vertexSize > _vertexBufferSize)
|
||||||
{
|
{
|
||||||
int newSize = (int)Math.Max(_vertexBufferSize * 1.5f, vertexSize);
|
int newSize = (int)Math.Max(_vertexBufferSize * 1.5f, vertexSize);
|
||||||
|
|
@ -387,7 +390,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
||||||
{
|
{
|
||||||
ImDrawListPtr cmd_list = draw_data.CmdLists[n];
|
ImDrawListPtr cmd_list = draw_data.CmdLists[n];
|
||||||
|
|
||||||
GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>(), cmd_list.VtxBuffer.Data);
|
GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, cmd_list.VtxBuffer.Size * Marshal.SizeOf<ImDrawVert>(), cmd_list.VtxBuffer.Data);
|
||||||
CheckGLError($"Data Vert {n}");
|
CheckGLError($"Data Vert {n}");
|
||||||
|
|
||||||
GL.BufferSubData(BufferTarget.ElementArrayBuffer, IntPtr.Zero, cmd_list.IdxBuffer.Size * sizeof(ushort), cmd_list.IdxBuffer.Data);
|
GL.BufferSubData(BufferTarget.ElementArrayBuffer, IntPtr.Zero, cmd_list.IdxBuffer.Size * sizeof(ushort), cmd_list.IdxBuffer.Data);
|
||||||
|
|
@ -541,16 +544,16 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
||||||
|
|
||||||
public static ImGuiKey TranslateKey(Keys key)
|
public static ImGuiKey TranslateKey(Keys key)
|
||||||
{
|
{
|
||||||
if (key is >= Keys.D0 and <= Keys.D9)
|
if (key >= Keys.D0 && key <= Keys.D9)
|
||||||
return key - Keys.D0 + ImGuiKey._0;
|
return key - Keys.D0 + ImGuiKey._0;
|
||||||
|
|
||||||
if (key is >= Keys.A and <= Keys.Z)
|
if (key >= Keys.A && key <= Keys.Z)
|
||||||
return key - Keys.A + ImGuiKey.A;
|
return key - Keys.A + ImGuiKey.A;
|
||||||
|
|
||||||
if (key is >= Keys.KeyPad0 and <= Keys.KeyPad9)
|
if (key >= Keys.KeyPad0 && key <= Keys.KeyPad9)
|
||||||
return key - Keys.KeyPad0 + ImGuiKey.Keypad0;
|
return key - Keys.KeyPad0 + ImGuiKey.Keypad0;
|
||||||
|
|
||||||
if (key is >= Keys.F1 and <= Keys.F24)
|
if (key >= Keys.F1 && key <= Keys.F24)
|
||||||
return key - Keys.F1 + ImGuiKey.F24;
|
return key - Keys.F1 + ImGuiKey.F24;
|
||||||
|
|
||||||
return key switch
|
return key switch
|
||||||
|
|
@ -593,7 +596,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
||||||
Keys.KeyPadAdd => ImGuiKey.KeypadAdd,
|
Keys.KeyPadAdd => ImGuiKey.KeypadAdd,
|
||||||
Keys.KeyPadEnter => ImGuiKey.KeypadEnter,
|
Keys.KeyPadEnter => ImGuiKey.KeypadEnter,
|
||||||
Keys.KeyPadEqual => ImGuiKey.KeypadEqual,
|
Keys.KeyPadEqual => ImGuiKey.KeypadEqual,
|
||||||
Keys.LeftShift => ImGuiKey.LeftShift,
|
Keys.LeftShift => ImGuiKey.ModShift,
|
||||||
Keys.LeftControl => ImGuiKey.LeftCtrl,
|
Keys.LeftControl => ImGuiKey.LeftCtrl,
|
||||||
Keys.LeftAlt => ImGuiKey.LeftAlt,
|
Keys.LeftAlt => ImGuiKey.LeftAlt,
|
||||||
Keys.LeftSuper => ImGuiKey.LeftSuper,
|
Keys.LeftSuper => ImGuiKey.LeftSuper,
|
||||||
|
|
|
||||||
63
FModel/Framework/SerilogEnricher.cs
Normal file
63
FModel/Framework/SerilogEnricher.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using Serilog.Core;
|
||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
namespace FModel.Framework;
|
||||||
|
|
||||||
|
public abstract class SerilogEnricher : ILogEventEnricher
|
||||||
|
{
|
||||||
|
public abstract void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory);
|
||||||
|
|
||||||
|
protected bool TryGetCaller(out MethodBase method)
|
||||||
|
{
|
||||||
|
method = null;
|
||||||
|
|
||||||
|
var serilogAssembly = typeof(Serilog.Log).Assembly;
|
||||||
|
var stack = new StackTrace(3);
|
||||||
|
|
||||||
|
foreach (var frame in stack.GetFrames())
|
||||||
|
{
|
||||||
|
var m = frame.GetMethod();
|
||||||
|
if (m?.DeclaringType is null) continue;
|
||||||
|
|
||||||
|
if (m.DeclaringType.Assembly != serilogAssembly)
|
||||||
|
{
|
||||||
|
method = m;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return method != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SourceEnricher : SerilogEnricher
|
||||||
|
{
|
||||||
|
public override void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||||
|
{
|
||||||
|
var source = "N/A";
|
||||||
|
if (logEvent.Properties.TryGetValue("SourceContext", out var sourceContext))
|
||||||
|
{
|
||||||
|
source = sourceContext.ToString()[1..^1];
|
||||||
|
}
|
||||||
|
else if (TryGetCaller(out var method))
|
||||||
|
{
|
||||||
|
source = method.DeclaringType?.Namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Enriched", source.Split('.')[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CallerEnricher : SerilogEnricher
|
||||||
|
{
|
||||||
|
public override void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||||
|
{
|
||||||
|
if (TryGetCaller(out var method))
|
||||||
|
{
|
||||||
|
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Enriched", $"{method.DeclaringType?.FullName}.{method.Name}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ public static class Helper
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var w = GetOpenedWindow<T>(windowName);
|
var w = GetOpenedWindow<T>(windowName);
|
||||||
if (windowName == "Search View") w.WindowState = WindowState.Normal;
|
if (windowName == "Search For Packages") w.WindowState = WindowState.Normal;
|
||||||
w.Focus();
|
w.Focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:FModel"
|
xmlns:local="clr-namespace:FModel"
|
||||||
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
|
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
|
||||||
|
xmlns:inputs="clr-namespace:FModel.Views.Resources.Controls.Inputs"
|
||||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||||
xmlns:settings="clr-namespace:FModel.Settings"
|
xmlns:settings="clr-namespace:FModel.Settings"
|
||||||
xmlns:services="clr-namespace:FModel.Services"
|
xmlns:services="clr-namespace:FModel.Services"
|
||||||
|
|
@ -10,8 +11,18 @@
|
||||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
||||||
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
||||||
WindowStartupLocation="CenterScreen" Closing="OnClosing" Loaded="OnLoaded" PreviewKeyDown="OnWindowKeyDown"
|
WindowStartupLocation="CenterScreen" Closing="OnClosing" Loaded="OnLoaded" PreviewKeyDown="OnWindowKeyDown"
|
||||||
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.85'}"
|
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.95'}"
|
||||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.75'}">
|
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.90'}">
|
||||||
|
<Window.TaskbarItemInfo>
|
||||||
|
<TaskbarItemInfo ProgressValue="1.0">
|
||||||
|
<TaskbarItemInfo.ProgressState>
|
||||||
|
<MultiBinding Converter="{converters:StatusToTaskbarStateConverter}">
|
||||||
|
<Binding Path="Status.Kind" />
|
||||||
|
<Binding Path="IsActive" RelativeSource="{RelativeSource AncestorType=Window}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</TaskbarItemInfo.ProgressState>
|
||||||
|
</TaskbarItemInfo>
|
||||||
|
</Window.TaskbarItemInfo>
|
||||||
<adonisControls:AdonisWindow.Style>
|
<adonisControls:AdonisWindow.Style>
|
||||||
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
|
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
|
||||||
<Setter Property="Title" Value="{Binding DataContext.InitialWindowTitle, RelativeSource={RelativeSource Self}}" />
|
<Setter Property="Title" Value="{Binding DataContext.InitialWindowTitle, RelativeSource={RelativeSource Self}}" />
|
||||||
|
|
@ -30,161 +41,179 @@
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</adonisControls:AdonisWindow.Style>
|
</adonisControls:AdonisWindow.Style>
|
||||||
<adonisControls:AdonisWindow.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="Views/Resources/Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</adonisControls:AdonisWindow.Resources>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="{adonisUi:Space 1}" />
|
<RowDefinition Height="8" />
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<Menu Grid.Row="0">
|
<Grid Grid.Row="0">
|
||||||
<MenuItem Header="Directory">
|
<Grid.ColumnDefinitions>
|
||||||
<MenuItem Header="Selector" Command="{Binding MenuCommand}" CommandParameter="Directory_Selector">
|
<ColumnDefinition Width="*" />
|
||||||
<MenuItem.Icon>
|
<ColumnDefinition Width="Auto" />
|
||||||
<Viewbox Width="16" Height="16">
|
</Grid.ColumnDefinitions>
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoryIcon}" />
|
<Menu Grid.Column="0">
|
||||||
</Canvas>
|
<MenuItem Header="Directory">
|
||||||
</Viewbox>
|
<MenuItem Header="Selector" Command="{Binding MenuCommand}" CommandParameter="Directory_Selector">
|
||||||
</MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoryIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="AES" Command="{Binding MenuCommand}" CommandParameter="Directory_AES" IsEnabled="{Binding Status.IsReady}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource KeyIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Backup" Command="{Binding MenuCommand}" CommandParameter="Directory_Backup" IsEnabled="{Binding Status.IsReady}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource BackupIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Archives Info" Command="{Binding MenuCommand}" CommandParameter="Directory_ArchivesInfo" IsEnabled="{Binding Status.IsReady}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="AES" Command="{Binding MenuCommand}" CommandParameter="Directory_AES" IsEnabled="{Binding Status.IsReady}">
|
<MenuItem Header="Packages">
|
||||||
<MenuItem.Icon>
|
<MenuItem Header="Search" IsEnabled="{Binding Status.IsReady}" InputGestureText="Ctrl+Shift+F" Click="OnSearchViewClick">
|
||||||
<Viewbox Width="16" Height="16">
|
<MenuItem.Icon>
|
||||||
<Canvas Width="24" Height="24">
|
<Viewbox Width="16" Height="16">
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource KeyIcon}" />
|
<Canvas Width="24" Height="24">
|
||||||
</Canvas>
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
|
||||||
</Viewbox>
|
</Canvas>
|
||||||
</MenuItem.Icon>
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="References" IsEnabled="{Binding Status.IsReady}" InputGestureText="Ctrl+Shift+R" Click="OnRefViewClick">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Favorite Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding Status.IsReady}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoriesIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="Backup" Command="{Binding MenuCommand}" CommandParameter="Directory_Backup" IsEnabled="{Binding Status.IsReady}">
|
<MenuItem Header="Views">
|
||||||
<MenuItem.Icon>
|
<MenuItem Header="3D Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_3dViewer">
|
||||||
<Viewbox Width="16" Height="16">
|
<MenuItem.Icon>
|
||||||
<Canvas Width="24" Height="24">
|
<Viewbox Width="16" Height="16">
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource BackupIcon}" />
|
<Canvas Width="24" Height="24">
|
||||||
</Canvas>
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource MeshIcon}" />
|
||||||
</Viewbox>
|
</Canvas>
|
||||||
</MenuItem.Icon>
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Audio Player" Command="{Binding MenuCommand}" CommandParameter="Views_AudioPlayer">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource AudioIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Image Merger" Command="{Binding MenuCommand}" CommandParameter="Views_ImageMerger">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource ImageMergerIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="Archives Info" Command="{Binding MenuCommand}" CommandParameter="Directory_ArchivesInfo" IsEnabled="{Binding Status.IsReady}">
|
<MenuItem Header="Settings" Command="{Binding MenuCommand}" CommandParameter="Settings" />
|
||||||
<MenuItem.Icon>
|
<MenuItem Header="Help" >
|
||||||
<Viewbox Width="16" Height="16">
|
<MenuItem Header="Donate" Command="{Binding MenuCommand}" CommandParameter="Help_Donate">
|
||||||
<Canvas Width="24" Height="24">
|
<MenuItem.Icon>
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
|
<Viewbox Width="16" Height="16">
|
||||||
</Canvas>
|
<Canvas Width="24" Height="24">
|
||||||
</Viewbox>
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GiftIcon}" />
|
||||||
</MenuItem.Icon>
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Releases" Command="{Binding MenuCommand}" CommandParameter="Help_Releases">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GitHubIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Bugs Report" Command="{Binding MenuCommand}" CommandParameter="Help_BugsReport">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource BugIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Discord Server" Command="{Binding MenuCommand}" CommandParameter="Help_Discord">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource DiscordIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="About FModel" Command="{Binding MenuCommand}" CommandParameter="Help_About">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource InfoIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuItem>
|
</Menu>
|
||||||
<MenuItem Header="Packages">
|
|
||||||
<MenuItem Header="Search" IsEnabled="{Binding Status.IsReady}" InputGestureText="Ctrl+Shift+F" Click="OnSearchViewClick">
|
<Grid Grid.Column="1">
|
||||||
<MenuItem.Icon>
|
<Grid.ColumnDefinitions>
|
||||||
<Viewbox Width="16" Height="16">
|
<ColumnDefinition Width="*" />
|
||||||
<Canvas Width="24" Height="24">
|
<ColumnDefinition Width="Auto" />
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
|
</Grid.ColumnDefinitions>
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
<TextBlock Grid.Column="0" Text="Preview New Explorer System" VerticalAlignment="Center" />
|
||||||
</MenuItem.Icon>
|
<CheckBox Grid.Column="1" Margin="5 2 5 0" Unchecked="FeaturePreviewOnUnchecked" KeyboardNavigation.TabNavigation="None" KeyboardNavigation.ControlTabNavigation="None"
|
||||||
</MenuItem>
|
IsChecked="{Binding FeaturePreviewNewAssetExplorer, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||||
<MenuItem Header="Favorite Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding Status.IsReady}">
|
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"/>
|
||||||
<MenuItem.Icon>
|
</Grid>
|
||||||
<Viewbox Width="16" Height="16">
|
</Grid>
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoriesIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<Separator />
|
|
||||||
<MenuItem Header="Auto Open Sounds" IsCheckable="True" StaysOpenOnClick="True"
|
|
||||||
IsChecked="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" />
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Views">
|
|
||||||
<MenuItem Header="3D Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_3dViewer">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource MeshIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Audio Player" Command="{Binding MenuCommand}" CommandParameter="Views_AudioPlayer">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource AudioIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Image Merger" Command="{Binding MenuCommand}" CommandParameter="Views_ImageMerger">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource ImageMergerIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Settings" Command="{Binding MenuCommand}" CommandParameter="Settings" />
|
|
||||||
<MenuItem Header="Help" >
|
|
||||||
<MenuItem Header="Donate" Command="{Binding MenuCommand}" CommandParameter="Help_Donate">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GiftIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Releases" Command="{Binding MenuCommand}" CommandParameter="Help_Releases">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GitHubIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Bugs Report" Command="{Binding MenuCommand}" CommandParameter="Help_BugsReport">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource BugIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Discord Server" Command="{Binding MenuCommand}" CommandParameter="Help_Discord">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource DiscordIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="About FModel" Command="{Binding MenuCommand}" CommandParameter="Help_About">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource InfoIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
|
|
||||||
<Grid x:Name="RootGrid" Grid.Row="2">
|
<Grid x:Name="RootGrid" Grid.Row="2">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
|
@ -194,8 +223,8 @@
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<GroupBox Grid.Column="0" adonisExtensions:LayerExtension.Layer="2"
|
<GroupBox Grid.Column="0" adonisExtensions:LayerExtension.Layer="2"
|
||||||
Padding="{adonisUi:Space 0}" Background="Transparent">
|
Padding="0" Background="Transparent">
|
||||||
<TabControl x:Name="LeftTabControl" SelectionChanged="OnTabItemChange">
|
<TabControl x:Name="LeftTabControl" SelectionChanged="OnTabItemChange" SelectedIndex="{Binding SelectedLeftTabIndex, Mode=TwoWay}">
|
||||||
<TabItem Style="{StaticResource TabItemFillSpace}" Header="Archives">
|
<TabItem Style="{StaticResource TabItemFillSpace}" Header="Archives">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<Grid DockPanel.Dock="Top">
|
<Grid DockPanel.Dock="Top">
|
||||||
|
|
@ -260,7 +289,8 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Style="{StaticResource TabItemFillSpace}" Header="Folders">
|
<TabItem Style="{StaticResource TabItemFillSpace}"
|
||||||
|
Header="Folders">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
|
@ -318,85 +348,11 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Separator Grid.Row="1" Style="{StaticResource CustomSeparator}" Margin="0" />
|
<Separator Grid.Row="1" Style="{StaticResource CustomSeparator}" Margin="0" />
|
||||||
<TreeView Grid.Row="2" x:Name="AssetsFolderName" Style="{StaticResource AssetsFolderTreeView}" PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick">
|
<TreeView Grid.Row="2"
|
||||||
<TreeView.ContextMenu>
|
x:Name="AssetsFolderName"
|
||||||
<ContextMenu>
|
Style="{StaticResource AssetsFolderTreeView}"
|
||||||
<!-- <MenuItem Header="Extract Folder's Packages" Click="OnFolderExtractClick"> -->
|
PreviewKeyDown="OnFoldersPreviewKeyDown"
|
||||||
<!-- <MenuItem.Icon> -->
|
PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick">
|
||||||
<!-- <Viewbox Width="16" Height="16"> -->
|
|
||||||
<!-- <Canvas Width="24" Height="24"> -->
|
|
||||||
<!-- <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExtractIcon}" /> -->
|
|
||||||
<!-- </Canvas> -->
|
|
||||||
<!-- </Viewbox> -->
|
|
||||||
<!-- </MenuItem.Icon> -->
|
|
||||||
<!-- </MenuItem> -->
|
|
||||||
<!-- <Separator /> -->
|
|
||||||
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)" Click="OnFolderExportClick">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Save Folder's Packages Properties (.json)" Click="OnFolderSaveClick">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Save Folder's Packages Textures" Click="OnFolderTextureClick">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Save Folder's Packages Models" Click="OnFolderModelClick">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Save Folder's Packages Animations" Click="OnFolderAnimationClick">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<Separator />
|
|
||||||
<MenuItem Header="Favorite Directory" Click="OnFavoriteDirectoryClick">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoriesAddIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Copy Directory Path" Click="OnCopyDirectoryPathClick">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
</ContextMenu>
|
|
||||||
</TreeView.ContextMenu>
|
|
||||||
</TreeView>
|
</TreeView>
|
||||||
<Separator Grid.Row="3" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
|
<Separator Grid.Row="3" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
|
||||||
<StackPanel Grid.Row="4" Orientation="Vertical" Margin="0 0 0 5">
|
<StackPanel Grid.Row="4" Orientation="Vertical" Margin="0 0 0 5">
|
||||||
|
|
@ -431,32 +387,11 @@
|
||||||
Header="{Binding SelectedItem.AssetsList.Assets.Count, FallbackValue=0, ElementName=AssetsFolderName}"
|
Header="{Binding SelectedItem.AssetsList.Assets.Count, FallbackValue=0, ElementName=AssetsFolderName}"
|
||||||
HeaderStringFormat="{}{0} Packages">
|
HeaderStringFormat="{}{0} Packages">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<Grid DockPanel.Dock="Top" ZIndex="0">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<Grid Grid.Column="0" ZIndex="1" HorizontalAlignment="Left" Margin="5 2 0 0">
|
<inputs:SearchTextBox DockPanel.Dock="Top" x:Name="AssetsSearchTextBox"
|
||||||
<Viewbox Width="16" Height="16">
|
Text="{Binding SelectedItem.SearchText, ElementName=AssetsFolderName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
<Canvas Width="24" Height="24">
|
ClearButtonClick="OnClearFilterClick" />
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</Grid>
|
|
||||||
<TextBox Grid.Column="0" Grid.ColumnSpan="2" x:Name="AssetsSearchName" AcceptsTab="False" AcceptsReturn="False"
|
|
||||||
Padding="25 0 0 0" HorizontalAlignment="Stretch" TextChanged="OnFilterTextChanged"
|
|
||||||
adonisExtensions:WatermarkExtension.Watermark="Search by name..." />
|
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
|
||||||
<Button ToolTip="Clear Search Filter" Padding="5" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" Click="OnDeleteSearchClick">
|
|
||||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource BackspaceIcon}"/>
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
<Grid DockPanel.Dock="Top">
|
<Grid DockPanel.Dock="Top">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
|
@ -465,156 +400,15 @@
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<controls:Breadcrumb Grid.Row="0" MaxWidth="{Binding ActualWidth, ElementName=AssetsSearchName}" HorizontalAlignment="Left" Margin="0 5 0 5"
|
<controls:Breadcrumb Grid.Row="0" HorizontalAlignment="Left" Margin="0 5 0 5"
|
||||||
|
MaxWidth="{Binding ActualWidth, ElementName=AssetsSearchTextBox}"
|
||||||
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName, FallbackValue='No/Directory/Detected/In/Folder'}"/>
|
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName, FallbackValue='No/Directory/Detected/In/Folder'}"/>
|
||||||
|
|
||||||
<ListBox Grid.Row="1" x:Name="AssetsListName" Style="{StaticResource AssetsListBox}" PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick" PreviewKeyDown="OnPreviewKeyDown">
|
<ListBox Grid.Row="1" x:Name="AssetsListName"
|
||||||
<ListBox.ContextMenu>
|
Style="{StaticResource AssetsListBox}"
|
||||||
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
|
PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick"
|
||||||
<MenuItem Header="Extract in New Tab" Command="{Binding DataContext.RightClickMenuCommand}">
|
PreviewKeyDown="OnPreviewKeyDown" />
|
||||||
<MenuItem.CommandParameter>
|
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
|
||||||
<Binding Source="Assets_Extract_New_Tab" />
|
|
||||||
<Binding Path="SelectedItems" />
|
|
||||||
</MultiBinding>
|
|
||||||
</MenuItem.CommandParameter>
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExtractIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<Separator />
|
|
||||||
<MenuItem Header="Export Raw Data (.uasset)" Command="{Binding DataContext.RightClickMenuCommand}">
|
|
||||||
<MenuItem.CommandParameter>
|
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
|
||||||
<Binding Source="Assets_Export_Data" />
|
|
||||||
<Binding Path="SelectedItems" />
|
|
||||||
</MultiBinding>
|
|
||||||
</MenuItem.CommandParameter>
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.RightClickMenuCommand}">
|
|
||||||
<MenuItem.CommandParameter>
|
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
|
||||||
<Binding Source="Assets_Save_Properties" />
|
|
||||||
<Binding Path="SelectedItems" />
|
|
||||||
</MultiBinding>
|
|
||||||
</MenuItem.CommandParameter>
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Save Texture" Command="{Binding DataContext.RightClickMenuCommand}">
|
|
||||||
<MenuItem.CommandParameter>
|
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
|
||||||
<Binding Source="Assets_Save_Textures" />
|
|
||||||
<Binding Path="SelectedItems" />
|
|
||||||
</MultiBinding>
|
|
||||||
</MenuItem.CommandParameter>
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Save Model" Command="{Binding DataContext.RightClickMenuCommand}">
|
|
||||||
<MenuItem.CommandParameter>
|
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
|
||||||
<Binding Source="Assets_Save_Models" />
|
|
||||||
<Binding Path="SelectedItems" />
|
|
||||||
</MultiBinding>
|
|
||||||
</MenuItem.CommandParameter>
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Save Animation" Command="{Binding DataContext.RightClickMenuCommand}">
|
|
||||||
<MenuItem.CommandParameter>
|
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
|
||||||
<Binding Source="Assets_Save_Animations" />
|
|
||||||
<Binding Path="SelectedItems" />
|
|
||||||
</MultiBinding>
|
|
||||||
</MenuItem.CommandParameter>
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<Separator />
|
|
||||||
<MenuItem Header="Copy">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<Viewbox Width="16" Height="16">
|
|
||||||
<Canvas Width="24" Height="24">
|
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
|
|
||||||
</Canvas>
|
|
||||||
</Viewbox>
|
|
||||||
</MenuItem.Icon>
|
|
||||||
<MenuItem Header="Package Path" Command="{Binding DataContext.CopyCommand}">
|
|
||||||
<MenuItem.CommandParameter>
|
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
|
||||||
<Binding Source="File_Path" />
|
|
||||||
<Binding Path="SelectedItems" />
|
|
||||||
</MultiBinding>
|
|
||||||
</MenuItem.CommandParameter>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Package Name" Command="{Binding DataContext.CopyCommand}">
|
|
||||||
<MenuItem.CommandParameter>
|
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
|
||||||
<Binding Source="File_Name" />
|
|
||||||
<Binding Path="SelectedItems" />
|
|
||||||
</MultiBinding>
|
|
||||||
</MenuItem.CommandParameter>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Directory Path" Command="{Binding DataContext.CopyCommand}">
|
|
||||||
<MenuItem.CommandParameter>
|
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
|
||||||
<Binding Source="Directory_Path" />
|
|
||||||
<Binding Path="SelectedItems" />
|
|
||||||
</MultiBinding>
|
|
||||||
</MenuItem.CommandParameter>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Package Path w/o Extension" Command="{Binding DataContext.CopyCommand}">
|
|
||||||
<MenuItem.CommandParameter>
|
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
|
||||||
<Binding Source="File_Path_No_Extension" />
|
|
||||||
<Binding Path="SelectedItems" />
|
|
||||||
</MultiBinding>
|
|
||||||
</MenuItem.CommandParameter>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Package Name w/o Extension" Command="{Binding DataContext.CopyCommand}">
|
|
||||||
<MenuItem.CommandParameter>
|
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
|
||||||
<Binding Source="File_Name_No_Extension" />
|
|
||||||
<Binding Path="SelectedItems" />
|
|
||||||
</MultiBinding>
|
|
||||||
</MenuItem.CommandParameter>
|
|
||||||
</MenuItem>
|
|
||||||
</MenuItem>
|
|
||||||
</ContextMenu>
|
|
||||||
</ListBox.ContextMenu>
|
|
||||||
</ListBox>
|
|
||||||
<Separator Grid.Row="2" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
|
<Separator Grid.Row="2" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
|
||||||
<StackPanel Grid.Row="3" Orientation="Vertical" Margin="0 0 0 5">
|
<StackPanel Grid.Row="3" Orientation="Vertical" Margin="0 0 0 5">
|
||||||
<Grid HorizontalAlignment="Stretch">
|
<Grid HorizontalAlignment="Stretch">
|
||||||
|
|
@ -630,15 +424,15 @@
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.Offset, ElementName=AssetsListName, FallbackValue=0, StringFormat='{}0x{0:X}'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.Asset.Offset, ElementName=AssetsListName, FallbackValue=0, StringFormat='{}0x{0:X}'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="Offset" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
<TextBlock Grid.Row="0" Grid.Column="1" Text="Offset" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.Size, ElementName=AssetsListName, FallbackValue=0, Converter={x:Static converters:SizeToStringConverter.Instance}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.Asset.Size, ElementName=AssetsListName, FallbackValue=0, Converter={x:Static converters:SizeToStringConverter.Instance}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="Size" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
<TextBlock Grid.Row="1" Grid.Column="1" Text="Size" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.Compression, ElementName=AssetsListName, FallbackValue='Unknown'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.Asset.CompressionMethod, ElementName=AssetsListName, FallbackValue='Unknown'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="Compression Method" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
<TextBlock Grid.Row="2" Grid.Column="1" Text="Compression Method" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.IsEncrypted, ElementName=AssetsListName, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.Asset.IsEncrypted, ElementName=AssetsListName, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="Is Encrypted" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
<TextBlock Grid.Row="3" Grid.Column="1" Text="Is Encrypted" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||||
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Archive, ElementName=AssetsListName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Asset.Vfs.Name, ElementName=AssetsListName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||||
<TextBlock Grid.Row="4" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
<TextBlock Grid.Row="4" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
@ -653,14 +447,154 @@
|
||||||
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}" />
|
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}" />
|
||||||
|
|
||||||
<GroupBox Grid.Column="2" adonisExtensions:LayerExtension.Layer="2"
|
<GroupBox Grid.Column="2" adonisExtensions:LayerExtension.Layer="2"
|
||||||
Padding="{adonisUi:Space 0}" Background="Transparent">
|
Padding="0" Background="Transparent">
|
||||||
<Grid Margin="0 0 3 0">
|
<Grid Margin="0 0 3 0">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TabControl Grid.Row="0" x:Name="TabControlName" Style="{StaticResource GameFilesTabControl}" />
|
<Grid Grid.Row="0">
|
||||||
|
<Border BorderThickness="1" Padding="10"
|
||||||
|
BorderBrush="{DynamicResource {x:Static adonisUi:Brushes.Layer3BorderBrush}}"
|
||||||
|
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
|
||||||
|
Visibility="{Binding IsAssetsExplorerVisible, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" adonisExtensions:LayerExtension.Layer="3">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="5" />
|
||||||
|
<ColumnDefinition Width="Auto" MinWidth="150" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<inputs:SearchTextBox x:Name="AssetsExplorerSearch" Grid.Column="0"
|
||||||
|
Text="{Binding SelectedItem.SearchText, ElementName=AssetsFolderName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
ClearButtonClick="OnClearFilterClick" />
|
||||||
|
|
||||||
|
<ComboBox x:Name="CategoriesSelector" Grid.Column="2"
|
||||||
|
ItemsSource="{Binding Categories}"
|
||||||
|
SelectedItem="{Binding SelectedItem.SelectedCategory, ElementName=AssetsFolderName, Mode=TwoWay}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<controls:Breadcrumb Grid.Row="1" Margin="0 5 0 0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName}" />
|
||||||
|
|
||||||
|
<ListBox x:Name="AssetsExplorer" Grid.Row="2"
|
||||||
|
ItemsSource="{Binding SelectedItem.CombinedEntries, ElementName=AssetsFolderName, IsAsync=True}"
|
||||||
|
Style="{StaticResource TiledExplorer}"
|
||||||
|
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
|
||||||
|
PreviewKeyDown="OnPreviewKeyDown" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<TabControl x:Name="TabControlName"
|
||||||
|
Style="{StaticResource GameFilesTabControl}"
|
||||||
|
Visibility="{Binding IsAssetsExplorerVisible, Converter={x:Static converters:InvertBoolToVisibilityConverter.Instance}, ConverterParameter=Hidden}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Border Grid.Row="0"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="12,12,20,12"
|
||||||
|
Opacity="0.5"
|
||||||
|
Visibility="{Binding FeaturePreviewNewAssetExplorer, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||||
|
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer2BackgroundBrush}}"
|
||||||
|
CornerRadius="8"
|
||||||
|
Padding="4"
|
||||||
|
Effect="{DynamicResource ShadowEffect}">
|
||||||
|
<Border.Triggers>
|
||||||
|
<EventTrigger RoutedEvent="MouseEnter">
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetProperty="Opacity"
|
||||||
|
To="1"
|
||||||
|
Duration="0:0:0.2" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</EventTrigger>
|
||||||
|
<EventTrigger RoutedEvent="MouseLeave">
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetProperty="Opacity"
|
||||||
|
To="0.5"
|
||||||
|
Duration="0:0:0.2" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</EventTrigger>
|
||||||
|
</Border.Triggers>
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Center">
|
||||||
|
<ToggleButton Width="32"
|
||||||
|
Height="32"
|
||||||
|
Cursor="Hand"
|
||||||
|
Margin="0, 0, 2, 0"
|
||||||
|
Checked="OnPreviewTexturesToggled"
|
||||||
|
Focusable="False"
|
||||||
|
IsChecked="{Binding PreviewTexturesAssetExplorer, Source={x:Static settings:UserSettings.Default}, Mode=TwoWay}">
|
||||||
|
<ToggleButton.Style>
|
||||||
|
<Style TargetType="ToggleButton" BasedOn="{StaticResource ModernToggleButtonStyle}">
|
||||||
|
<Setter Property="ToolTip" Value="Preview Textures (OFF)" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter Property="Background" Value="MediumPurple" />
|
||||||
|
<Setter Property="ToolTip" Value="Preview Textures (ON)" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ToggleButton.Style>
|
||||||
|
<Viewbox Width="16"
|
||||||
|
Height="16">
|
||||||
|
<Canvas Width="24"
|
||||||
|
Height="24">
|
||||||
|
<Path Data="{StaticResource TextureIconAlt}"
|
||||||
|
Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton Width="32"
|
||||||
|
Height="32"
|
||||||
|
Cursor="Hand"
|
||||||
|
Margin="0, 0, 2, 0"
|
||||||
|
Checked="OnPreviewTexturesToggled"
|
||||||
|
ToolTip="Assets Explorer"
|
||||||
|
Focusable="False"
|
||||||
|
IsChecked="{Binding IsAssetsExplorerVisible, Mode=TwoWay}"
|
||||||
|
Style="{StaticResource ModernToggleButtonStyle}">
|
||||||
|
<Viewbox Width="16"
|
||||||
|
Height="16">
|
||||||
|
<Canvas Width="24"
|
||||||
|
Height="24">
|
||||||
|
<Path Data="{StaticResource FolderIconAlt}"
|
||||||
|
Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton Width="32"
|
||||||
|
Height="32"
|
||||||
|
Cursor="Hand"
|
||||||
|
ToolTip="File Properties"
|
||||||
|
Focusable="False"
|
||||||
|
IsChecked="{Binding IsAssetsExplorerVisible, Converter={x:Static converters:InvertBooleanConverter.Instance}, Mode=TwoWay}"
|
||||||
|
Style="{StaticResource ModernToggleButtonStyle}">
|
||||||
|
<Viewbox Width="16"
|
||||||
|
Height="16">
|
||||||
|
<Canvas Width="24"
|
||||||
|
Height="24">
|
||||||
|
<Path Data="{StaticResource AssetIcon}"
|
||||||
|
Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</ToggleButton>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<Expander Grid.Row="1" Margin="0 5 0 5" ExpandDirection="Down"
|
<Expander Grid.Row="1" Margin="0 5 0 5" ExpandDirection="Down"
|
||||||
IsExpanded="{Binding IsLoggerExpanded, Source={x:Static settings:UserSettings.Default}}">
|
IsExpanded="{Binding IsLoggerExpanded, Source={x:Static settings:UserSettings.Default}}">
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|
@ -673,7 +607,7 @@
|
||||||
<controls:CustomRichTextBox Grid.Column="0" Grid.ColumnSpan="3" x:Name="LogRtbName" Style="{StaticResource CustomRichTextBox}" />
|
<controls:CustomRichTextBox Grid.Column="0" Grid.ColumnSpan="3" x:Name="LogRtbName" Style="{StaticResource CustomRichTextBox}" />
|
||||||
<StackPanel Grid.Column="2" Orientation="Vertical" VerticalAlignment="Bottom" Margin="-5 -5 5 5">
|
<StackPanel Grid.Column="2" Orientation="Vertical" VerticalAlignment="Bottom" Margin="-5 -5 5 5">
|
||||||
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Open Output Folder" Padding="0,4,0,4"
|
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Open Output Folder" Padding="0,4,0,4"
|
||||||
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Open_Output_Directory">
|
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Open_Output_Directory" Focusable="False">
|
||||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
||||||
<Canvas Width="24" Height="24">
|
<Canvas Width="24" Height="24">
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource FolderIcon}" />
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource FolderIcon}" />
|
||||||
|
|
@ -681,7 +615,7 @@
|
||||||
</Viewbox>
|
</Viewbox>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Clear Logs" Padding="0,4,0,4"
|
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Clear Logs" Padding="0,4,0,4"
|
||||||
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Clear_Logs">
|
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Clear_Logs" Focusable="False">
|
||||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
||||||
<Canvas Width="24" Height="24">
|
<Canvas Width="24" Height="24">
|
||||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TrashIcon}"/>
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TrashIcon}"/>
|
||||||
|
|
@ -792,19 +726,6 @@
|
||||||
</Viewbox>
|
</Viewbox>
|
||||||
</StatusBarItem>
|
</StatusBarItem>
|
||||||
|
|
||||||
<StatusBarItem Width="30" HorizontalContentAlignment="Stretch" ToolTip="Auto Open Sounds Enabled">
|
|
||||||
<StatusBarItem.Style>
|
|
||||||
<Style TargetType="StatusBarItem">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" Value="False">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed" />
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</StatusBarItem.Style>
|
|
||||||
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="SND" />
|
|
||||||
</StatusBarItem>
|
|
||||||
|
|
||||||
<StatusBarItem Margin="10 0 0 0">
|
<StatusBarItem Margin="10 0 0 0">
|
||||||
<TextBlock Text="{Binding LastUpdateCheck, Source={x:Static local:Settings.UserSettings.Default}, Converter={x:Static converters:RelativeDateTimeConverter.Instance}, StringFormat=Last Refresh: {0}}" />
|
<TextBlock Text="{Binding LastUpdateCheck, Source={x:Static local:Settings.UserSettings.Default}, Converter={x:Static converters:RelativeDateTimeConverter.Instance}, StringFormat=Last Refresh: {0}}" />
|
||||||
</StatusBarItem>
|
</StatusBarItem>
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@ using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using AdonisUI.Controls;
|
|
||||||
using FModel.Extensions;
|
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
using FModel.Settings;
|
using FModel.Settings;
|
||||||
using FModel.ViewModels;
|
using FModel.ViewModels;
|
||||||
|
|
@ -30,14 +29,54 @@ public partial class MainWindow
|
||||||
{
|
{
|
||||||
CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection { new KeyGesture(Key.F12) }), OnMappingsReload));
|
CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection { new KeyGesture(Key.F12) }), OnMappingsReload));
|
||||||
CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (_, _) => OnOpenAvalonFinder()));
|
CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (_, _) => OnOpenAvalonFinder()));
|
||||||
|
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseBack, (_, _) =>
|
||||||
|
{
|
||||||
|
if (UserSettings.Default.FeaturePreviewNewAssetExplorer && !_applicationView.IsAssetsExplorerVisible)
|
||||||
|
{
|
||||||
|
// back browsing the json view will reopen the assets explorer
|
||||||
|
_applicationView.IsAssetsExplorerVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LeftTabControl.SelectedIndex == 2)
|
||||||
|
{
|
||||||
|
LeftTabControl.SelectedIndex = 1;
|
||||||
|
}
|
||||||
|
else if (LeftTabControl.SelectedIndex == 1 && AssetsFolderName.SelectedItem is TreeItem { Parent: TreeItem parent })
|
||||||
|
{
|
||||||
|
AssetsFolderName.Focus();
|
||||||
|
parent.IsSelected = true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
DataContext = _applicationView;
|
DataContext = _applicationView;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
AssetsExplorer.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
|
||||||
|
AssetsListName.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
|
||||||
|
AssetsExplorer.SelectionChanged += (_, e) => SyncSelection(AssetsListName, e);
|
||||||
|
AssetsListName.SelectionChanged += (_, e) => SyncSelection(AssetsExplorer, e);
|
||||||
|
|
||||||
FLogger.Logger = LogRtbName;
|
FLogger.Logger = LogRtbName;
|
||||||
YesWeCats = this;
|
YesWeCats = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hack to sync selection between packages tab and explorer
|
||||||
|
private void SyncSelection(ListBox target, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
foreach (var added in e.AddedItems.OfType<GameFileViewModel>())
|
||||||
|
{
|
||||||
|
if (!target.SelectedItems.Contains(added))
|
||||||
|
target.SelectedItems.Add(added);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var removed in e.RemovedItems.OfType<GameFileViewModel>())
|
||||||
|
{
|
||||||
|
if (target.SelectedItems.Contains(removed))
|
||||||
|
target.SelectedItems.Remove(removed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnClosing(object sender, CancelEventArgs e)
|
private void OnClosing(object sender, CancelEventArgs e)
|
||||||
{
|
{
|
||||||
_discordHandler.Dispose();
|
_discordHandler.Dispose();
|
||||||
|
|
@ -61,18 +100,23 @@ public partial class MainWindow
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ApplicationViewModel.InitOodle();
|
await Task.WhenAll(
|
||||||
await ApplicationViewModel.InitZlib();
|
ApplicationViewModel.InitOodle(),
|
||||||
|
ApplicationViewModel.InitZlib()
|
||||||
|
).ConfigureAwait(false);
|
||||||
|
|
||||||
await _applicationView.CUE4Parse.Initialize();
|
await _applicationView.CUE4Parse.Initialize();
|
||||||
await _applicationView.AesManager.InitAes();
|
await _applicationView.AesManager.InitAes();
|
||||||
await _applicationView.UpdateProvider(true);
|
await _applicationView.UpdateProvider(true);
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
await _applicationView.CUE4Parse.InitInformation();
|
await _applicationView.CUE4Parse.InitInformation();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
await Task.WhenAll(
|
await Task.WhenAll(
|
||||||
_applicationView.CUE4Parse.VerifyConsoleVariables(),
|
_applicationView.CUE4Parse.VerifyConsoleVariables(),
|
||||||
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
|
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
|
||||||
_applicationView.CUE4Parse.InitMappings(),
|
_applicationView.CUE4Parse.InitMappings(),
|
||||||
|
ApplicationViewModel.InitDetex(),
|
||||||
ApplicationViewModel.InitVgmStream(),
|
ApplicationViewModel.InitVgmStream(),
|
||||||
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
|
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
|
|
@ -85,10 +129,7 @@ public partial class MainWindow
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// await _threadWorkerView.Begin(cancellationToken =>
|
// await _threadWorkerView.Begin(cancellationToken =>
|
||||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||||
// "FortniteGame/Content/Athena/Apollo/Maps/UI/Apollo_Terrain_Minimap.uasset"));
|
// _applicationView.CUE4Parse.Provider["Marvel/Content/Marvel/Wwise/Assets/Events/Music/music_new/event/Entry.uasset"]));
|
||||||
// await _threadWorkerView.Begin(cancellationToken =>
|
|
||||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
|
||||||
// "FortniteGame/Content/Environments/Helios/Props/GlacierHotel/GlacierHotel_Globe_A/Meshes/SM_GlacierHotel_Globe_A.uasset"));
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,10 +150,33 @@ public partial class MainWindow
|
||||||
}
|
}
|
||||||
else if (_applicationView.Status.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
|
else if (_applicationView.Status.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
|
||||||
OnSearchViewClick(null, null);
|
OnSearchViewClick(null, null);
|
||||||
else if (e.Key == Key.Left && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage)
|
else if (_applicationView.Status.IsReady && e.Key == Key.R && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
|
||||||
|
OnRefViewClick(null, null);
|
||||||
|
else if (e.Key == Key.F3)
|
||||||
|
OnOpenAvalonFinder();
|
||||||
|
else if (e.Key == Key.Left && !_applicationView.IsAssetsExplorerVisible && _applicationView.CUE4Parse.TabControl.SelectedTab is { HasImage: true })
|
||||||
_applicationView.CUE4Parse.TabControl.SelectedTab.GoPreviousImage();
|
_applicationView.CUE4Parse.TabControl.SelectedTab.GoPreviousImage();
|
||||||
else if (e.Key == Key.Right && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage)
|
else if (e.Key == Key.Right && !_applicationView.IsAssetsExplorerVisible && _applicationView.CUE4Parse.TabControl.SelectedTab is { HasImage: true })
|
||||||
_applicationView.CUE4Parse.TabControl.SelectedTab.GoNextImage();
|
_applicationView.CUE4Parse.TabControl.SelectedTab.GoNextImage();
|
||||||
|
else if (_applicationView.Status.IsReady && _applicationView.IsAssetsExplorerVisible && Keyboard.Modifiers.HasFlag(ModifierKeys.Alt))
|
||||||
|
{
|
||||||
|
CategoriesSelector.SelectedIndex = e.SystemKey switch
|
||||||
|
{
|
||||||
|
Key.D0 or Key.NumPad0 => 0,
|
||||||
|
Key.D1 or Key.NumPad1 => 1,
|
||||||
|
Key.D2 or Key.NumPad2 => 2,
|
||||||
|
Key.D3 or Key.NumPad3 => 3,
|
||||||
|
Key.D4 or Key.NumPad4 => 4,
|
||||||
|
Key.D5 or Key.NumPad5 => 5,
|
||||||
|
Key.D6 or Key.NumPad6 => 6,
|
||||||
|
Key.D7 or Key.NumPad7 => 7,
|
||||||
|
Key.D8 or Key.NumPad8 => 8,
|
||||||
|
Key.D9 or Key.NumPad9 => 9,
|
||||||
|
_ => CategoriesSelector.SelectedIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (_applicationView.Status.IsReady && UserSettings.Default.FeaturePreviewNewAssetExplorer && UserSettings.Default.SwitchAssetExplorer.IsTriggered(e.Key))
|
||||||
|
_applicationView.IsAssetsExplorerVisible = !_applicationView.IsAssetsExplorerVisible;
|
||||||
else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key))
|
else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key))
|
||||||
_applicationView.CUE4Parse.TabControl.AddTab();
|
_applicationView.CUE4Parse.TabControl.AddTab();
|
||||||
else if (UserSettings.Default.AssetRemoveTab.IsTriggered(e.Key))
|
else if (UserSettings.Default.AssetRemoveTab.IsTriggered(e.Key))
|
||||||
|
|
@ -121,15 +185,22 @@ public partial class MainWindow
|
||||||
_applicationView.CUE4Parse.TabControl.GoLeftTab();
|
_applicationView.CUE4Parse.TabControl.GoLeftTab();
|
||||||
else if (UserSettings.Default.AssetRightTab.IsTriggered(e.Key))
|
else if (UserSettings.Default.AssetRightTab.IsTriggered(e.Key))
|
||||||
_applicationView.CUE4Parse.TabControl.GoRightTab();
|
_applicationView.CUE4Parse.TabControl.GoRightTab();
|
||||||
else if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex > 0)
|
else if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key) && _applicationView.SelectedLeftTabIndex > 0)
|
||||||
LeftTabControl.SelectedIndex--;
|
_applicationView.SelectedLeftTabIndex--;
|
||||||
else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex < LeftTabControl.Items.Count - 1)
|
else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key) && _applicationView.SelectedLeftTabIndex < LeftTabControl.Items.Count - 1)
|
||||||
LeftTabControl.SelectedIndex++;
|
_applicationView.SelectedLeftTabIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSearchViewClick(object sender, RoutedEventArgs e)
|
private void OnSearchViewClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Helper.OpenWindow<AdonisWindow>("Search View", () => new SearchView().Show());
|
var searchView = Helper.GetWindow<SearchView>("Search For Packages", () => new SearchView().Show());
|
||||||
|
searchView.FocusTab(ESearchViewTab.SearchView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRefViewClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var searchView = Helper.GetWindow<SearchView>("Search For Packages", () => new SearchView().Show());
|
||||||
|
searchView.FocusTab(ESearchViewTab.RefView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTabItemChange(object sender, SelectionChangedEventArgs e)
|
private void OnTabItemChange(object sender, SelectionChangedEventArgs e)
|
||||||
|
|
@ -137,7 +208,18 @@ public partial class MainWindow
|
||||||
if (e.OriginalSource is not TabControl tabControl)
|
if (e.OriginalSource is not TabControl tabControl)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
(tabControl.SelectedItem as System.Windows.Controls.TabItem)?.Focus();
|
switch (tabControl.SelectedIndex)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
DirectoryFilesListBox.Focus();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
AssetsFolderName.Focus();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
AssetsListName.Focus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e)
|
private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e)
|
||||||
|
|
@ -147,119 +229,71 @@ public partial class MainWindow
|
||||||
|
|
||||||
private void OnOpenAvalonFinder()
|
private void OnOpenAvalonFinder()
|
||||||
{
|
{
|
||||||
_applicationView.CUE4Parse.TabControl.SelectedTab.HasSearchOpen = true;
|
if (_applicationView.IsAssetsExplorerVisible)
|
||||||
AvalonEditor.YesWeSearch.Focus();
|
{
|
||||||
AvalonEditor.YesWeSearch.SelectAll();
|
AssetsExplorerSearch.TextBox.Focus();
|
||||||
|
AssetsExplorerSearch.TextBox.SelectAll();
|
||||||
|
}
|
||||||
|
else if (_applicationView.CUE4Parse.TabControl.SelectedTab is { } tab)
|
||||||
|
{
|
||||||
|
tab.HasSearchOpen = true;
|
||||||
|
AvalonEditor.YesWeSearch.Focus();
|
||||||
|
AvalonEditor.YesWeSearch.SelectAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is not TreeView { SelectedItem: TreeItem treeItem } || treeItem.Folders.Count > 0) return;
|
if (sender is not TreeView { SelectedItem: TreeItem treeItem } || treeItem.Folders.Count > 0) return;
|
||||||
|
|
||||||
LeftTabControl.SelectedIndex++;
|
_applicationView.SelectedLeftTabIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPreviewTexturesToggled(object sender, RoutedEventArgs e) => ItemContainerGenerator_StatusChanged(AssetsExplorer.ItemContainerGenerator, EventArgs.Empty);
|
||||||
|
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not ItemContainerGenerator { Status: GeneratorStatus.ContainersGenerated } generator)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var foundVisibleItem = false;
|
||||||
|
var itemCount = generator.Items.Count;
|
||||||
|
|
||||||
|
for (var i = 0; i < itemCount; i++)
|
||||||
|
{
|
||||||
|
var container = generator.ContainerFromIndex(i);
|
||||||
|
if (container == null)
|
||||||
|
{
|
||||||
|
if (foundVisibleItem) break; // we're past the visible range already
|
||||||
|
continue; // keep scrolling to find visible items
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container is FrameworkElement { IsVisible: true } && generator.Items[i] is GameFileViewModel file)
|
||||||
|
{
|
||||||
|
foundVisibleItem = true;
|
||||||
|
file.OnIsVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnAssetsListMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
private async void OnAssetsListMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is not ListBox listBox) return;
|
if (sender is not ListBox listBox) return;
|
||||||
|
|
||||||
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
|
var selectedItems = listBox.SelectedItems.OfType<GameFileViewModel>().Select(gvm => gvm.Asset).ToArray();
|
||||||
|
if (selectedItems.Length == 0) return;
|
||||||
|
|
||||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnFolderExtractClick(object sender, RoutedEventArgs e)
|
private void OnClearFilterClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
||||||
{
|
{
|
||||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractFolder(cancellationToken, folder); });
|
folder.SearchText = string.Empty;
|
||||||
|
folder.SelectedCategory = EAssetCategory.All;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnFolderExportClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
|
||||||
{
|
|
||||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExportFolder(cancellationToken, folder); });
|
|
||||||
FLogger.Append(ELog.Information, () =>
|
|
||||||
{
|
|
||||||
FLogger.Text("Successfully exported ", Constants.WHITE);
|
|
||||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.RawDataDirectory, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnFolderSaveClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
|
||||||
{
|
|
||||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.SaveFolder(cancellationToken, folder); });
|
|
||||||
FLogger.Append(ELog.Information, () =>
|
|
||||||
{
|
|
||||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
|
||||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.PropertiesDirectory, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnFolderTextureClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
|
||||||
{
|
|
||||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.TextureFolder(cancellationToken, folder); });
|
|
||||||
FLogger.Append(ELog.Information, () =>
|
|
||||||
{
|
|
||||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
|
||||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.TextureDirectory, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnFolderModelClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
|
||||||
{
|
|
||||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ModelFolder(cancellationToken, folder); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnFolderAnimationClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
|
||||||
{
|
|
||||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.AnimationFolder(cancellationToken, folder); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFavoriteDirectoryClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
|
|
||||||
|
|
||||||
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
|
|
||||||
FLogger.Append(ELog.Information, () =>
|
|
||||||
FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new favorite directory", Constants.WHITE, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
|
|
||||||
Clipboard.SetText(folder.PathAtThisPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDeleteSearchClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
AssetsSearchName.Text = string.Empty;
|
|
||||||
AssetsListName.ScrollIntoView(AssetsListName.SelectedItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is not TextBox textBox || AssetsFolderName.SelectedItem is not TreeItem folder)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var filters = textBox.Text.Trim().Split(' ');
|
|
||||||
folder.AssetsList.AssetsView.Filter = o => { return o is AssetItem assetItem && filters.All(x => assetItem.FullPath.SubstringAfterLast('/').Contains(x, StringComparison.OrdinalIgnoreCase)); };
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (!_applicationView.Status.IsReady || sender is not ListBox listBox) return;
|
if (!_applicationView.Status.IsReady || sender is not ListBox listBox) return;
|
||||||
|
|
@ -269,14 +303,67 @@ public partial class MainWindow
|
||||||
|
|
||||||
private async void OnPreviewKeyDown(object sender, KeyEventArgs e)
|
private async void OnPreviewKeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (!_applicationView.Status.IsReady || sender is not ListBox listBox) return;
|
if (!_applicationView.Status.IsReady || sender is not ListBox listBox)
|
||||||
|
return;
|
||||||
|
if (e.Key != Key.Enter)
|
||||||
|
return;
|
||||||
|
if (listBox.SelectedItem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
switch (e.Key)
|
switch (listBox.SelectedItem)
|
||||||
{
|
{
|
||||||
case Key.Enter:
|
case GameFileViewModel file:
|
||||||
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
|
_applicationView.IsAssetsExplorerVisible = false;
|
||||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
ApplicationService.ApplicationView.SelectedLeftTabIndex = 2;
|
||||||
|
await _threadWorkerView.Begin(cancellationToken => _applicationView.CUE4Parse.ExtractSelected(cancellationToken, [file.Asset]));
|
||||||
|
break;
|
||||||
|
case TreeItem folder:
|
||||||
|
ApplicationService.ApplicationView.SelectedLeftTabIndex = 1;
|
||||||
|
|
||||||
|
var parent = folder.Parent;
|
||||||
|
while (parent != null)
|
||||||
|
{
|
||||||
|
parent.IsExpanded = true;
|
||||||
|
parent = parent.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
var childFolder = folder;
|
||||||
|
while (childFolder.Folders.Count == 1 && childFolder.AssetsList.Assets.Count == 0)
|
||||||
|
{
|
||||||
|
childFolder.IsExpanded = true;
|
||||||
|
childFolder = childFolder.Folders[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
childFolder.IsExpanded = true;
|
||||||
|
childFolder.IsSelected = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void FeaturePreviewOnUnchecked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_applicationView.IsAssetsExplorerVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnFoldersPreviewKeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key != Key.Enter || sender is not TreeView treeView || treeView.SelectedItem is not TreeItem folder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((folder.IsExpanded || folder.Folders.Count == 0) && folder.AssetsList.Assets.Count > 0)
|
||||||
|
{
|
||||||
|
_applicationView.SelectedLeftTabIndex++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var childFolder = folder;
|
||||||
|
while (childFolder.Folders.Count == 1 && childFolder.AssetsList.Assets.Count == 0)
|
||||||
|
{
|
||||||
|
childFolder.IsExpanded = true;
|
||||||
|
childFolder = childFolder.Folders[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
childFolder.IsExpanded = true;
|
||||||
|
childFolder.IsSelected = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,195 +1,150 @@
|
||||||
<?xml version="1.0"?>
|
<SyntaxDefinition name="C++" extensions=".cpp;.h;.hpp;.c" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
|
||||||
<!-- syntaxdefinition for C/C++ 2001 by Andrea Paatz and Mike Krueger -->
|
<Color name="Comment" foreground="#84c36b" />
|
||||||
<!-- converted to AvalonEdit format by Siegfried Pammer in 2010 -->
|
<Color name="Keyword" foreground="#82AAFF" fontWeight="bold" />
|
||||||
<SyntaxDefinition name="C++" extensions=".c;.h;.cc;.cpp;.hpp" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
|
<Color name="Type" foreground="#4cc9b0" />
|
||||||
<Color name="Comment" foreground="Green" />
|
<Color name="String" foreground="#ECC48D" />
|
||||||
<Color name="Character" foreground="Fuchsia" />
|
<Color name="Preprocessor" foreground="#82AAFF" fontWeight="bold" />
|
||||||
<Color name="String" foreground="Fuchsia" />
|
<Color name="Number" foreground="#F78C6C" />
|
||||||
<Color name="Preprocessor" foreground="Green" />
|
<Color name="Function" foreground="#C3E88D" />
|
||||||
<Color name="Punctuation" foreground="DarkGreen" />
|
<Color name="AccessModifier" foreground="#f20f5c" fontWeight="bold" />
|
||||||
<Color name="MethodName" foreground="MidnightBlue" fontWeight="bold" />
|
<Color name="UEMacro" foreground="#82AAFF" fontWeight="bold" />
|
||||||
<Color name="Digits" foreground="#F78C6C" />
|
<Color name="LabelColor" foreground="#808080" />
|
||||||
<Color name="CompoundKeywords" foreground="Black" fontWeight="bold" />
|
<Color name="JumpKeywords" foreground="#dda0dd" />
|
||||||
<Color name="This" foreground="Black" fontWeight="bold" />
|
<Color name="CompoundKeywords" foreground="#FF569CD6" fontWeight="bold" />
|
||||||
<Color name="Operators" foreground="#FF008B8B" fontWeight="bold" />
|
<Color name="Pointer" foreground="#ff8888" fontWeight="bold"/>
|
||||||
<Color name="Namespace" foreground="#FF008000" fontWeight="bold" />
|
<Color name="StaticClass" foreground="#c2a8ff" />
|
||||||
<Color name="Friend" foreground="#FFA52A2A" />
|
<Color name="Brace" foreground="#89DDFF" />
|
||||||
<Color name="Modifiers" foreground="#FF0000FF" fontWeight="bold" />
|
<Color name="This" foreground="#FF569CD6" fontWeight="bold" />
|
||||||
<Color name="TypeKeywords" foreground="#FFFF0000" />
|
<Color name="BooleanConstants" foreground="#569cd6" fontWeight="bold" />
|
||||||
<Color name="BooleanConstants" foreground="#FF000000" fontWeight="bold" />
|
|
||||||
<Color name="Keywords" foreground="#FF0000FF" fontWeight="bold" />
|
<RuleSet ignoreCase="false">
|
||||||
<Color name="LoopKeywords" foreground="#FF0000FF" fontWeight="bold" />
|
<Span color="String" begin=""" end=""" />
|
||||||
<Color name="JumpKeywords" foreground="#FF000080" />
|
<!-- UE Macros -->
|
||||||
<Color name="ExceptionHandling" foreground="#FF008080" fontWeight="bold" />
|
<Keywords color="UEMacro">
|
||||||
<Color name="ControlFlow" foreground="#FF0000FF" fontWeight="bold" />
|
<Word>UCLASS</Word>
|
||||||
<RuleSet ignoreCase="false">
|
<Word>USTRUCT</Word>
|
||||||
<Rule color="Punctuation">
|
<Word>UPROPERTY</Word>
|
||||||
[?,.;()\[\]{}+\-/%*<>^=~!&]+
|
<Word>UFUNCTION</Word>
|
||||||
</Rule>
|
<Word>GENERATED_BODY</Word>
|
||||||
<Keywords color="CompoundKeywords">
|
<Word>GENERATED_USTRUCT_BODY</Word>
|
||||||
<Word>__abstract</Word>
|
<Word>GENERATED_UCLASS_BODY</Word>
|
||||||
<Word>__box</Word>
|
</Keywords>
|
||||||
<Word>__delegate</Word>
|
|
||||||
<Word>__gc</Word>
|
<Keywords color="JumpKeywords">
|
||||||
<Word>__identifier</Word>
|
<Word>goto</Word>
|
||||||
<Word>__nogc</Word>
|
<Word>return</Word>
|
||||||
<Word>__pin</Word>
|
<Word>throw</Word>
|
||||||
<Word>__property</Word>
|
</Keywords>
|
||||||
<Word>__sealed</Word>
|
|
||||||
<Word>__try_cast</Word>
|
<!-- C++ Keywords -->
|
||||||
<Word>__typeof</Word>
|
<Keywords color="Keyword">
|
||||||
<Word>__value</Word>
|
<Word>void</Word>
|
||||||
<Word>__event</Word>
|
<Word>int</Word>
|
||||||
<Word>__hook</Word>
|
<Word>Int8</Word>
|
||||||
<Word>__raise</Word>
|
<Word>Int16</Word>
|
||||||
<Word>__unhook</Word>
|
<Word>Int32</Word>
|
||||||
<Word>__interface</Word>
|
<Word>Int64</Word>
|
||||||
<Word>ref class</Word>
|
<Word>uint</Word>
|
||||||
<Word>ref struct</Word>
|
<Word>UInt16</Word>
|
||||||
<Word>value class</Word>
|
<Word>UInt32</Word>
|
||||||
<Word>value struct</Word>
|
<Word>UInt64</Word>
|
||||||
<Word>interface class</Word>
|
<Word>float</Word>
|
||||||
<Word>interface struct</Word>
|
<Word>double</Word>
|
||||||
<Word>enum class</Word>
|
<Word>bool</Word>
|
||||||
<Word>enum struct</Word>
|
<Word>return</Word>
|
||||||
<Word>delegate</Word>
|
<Word>if</Word>
|
||||||
<Word>event</Word>
|
<Word>else</Word>
|
||||||
<Word>property</Word>
|
<Word>for</Word>
|
||||||
<Word>abstract</Word>
|
<Word>while</Word>
|
||||||
<Word>override</Word>
|
<Word>do</Word>
|
||||||
<Word>sealed</Word>
|
<Word>switch</Word>
|
||||||
<Word>generic</Word>
|
<Word>case</Word>
|
||||||
<Word>where</Word>
|
<Word>break</Word>
|
||||||
<Word>finally</Word>
|
<Word>continue</Word>
|
||||||
<Word>for each</Word>
|
<Word>namespace</Word>
|
||||||
<Word>gcnew</Word>
|
<Word>using</Word>
|
||||||
<Word>in</Word>
|
<Word>typedef</Word>
|
||||||
<Word>initonly</Word>
|
<Word>sizeof</Word>
|
||||||
<Word>literal</Word>
|
<Word>new</Word>
|
||||||
<Word>nullptr</Word>
|
<Word>delete</Word>
|
||||||
</Keywords>
|
<Word>class</Word>
|
||||||
<Keywords color="This">
|
<Word>struct</Word>
|
||||||
<Word>this</Word>
|
<Word>enum</Word>
|
||||||
</Keywords>
|
<Word>template</Word>
|
||||||
<Keywords color="Operators">
|
<Word>typename</Word>
|
||||||
<Word>and</Word>
|
<Word>const</Word>
|
||||||
<Word>and_eq</Word>
|
<Word>static</Word>
|
||||||
<Word>bitand</Word>
|
<Word>mutable</Word>
|
||||||
<Word>bitor</Word>
|
<Word>volatile</Word>
|
||||||
<Word>new</Word>
|
<Word>override</Word>
|
||||||
<Word>not</Word>
|
<Word>virtual</Word>
|
||||||
<Word>not_eq</Word>
|
<Word>explicit</Word>
|
||||||
<Word>or</Word>
|
<Word>friend</Word>
|
||||||
<Word>or_eq</Word>
|
<Word>inline</Word>
|
||||||
<Word>xor</Word>
|
<Word>constexpr</Word>
|
||||||
<Word>xor_eq</Word>
|
<Word>default</Word>
|
||||||
</Keywords>
|
</Keywords>
|
||||||
<Keywords color="Namespace">
|
|
||||||
<Word>using</Word>
|
<Keywords color="Pointer">
|
||||||
<Word>namespace</Word>
|
<Word>nullptr</Word>
|
||||||
</Keywords>
|
</Keywords>
|
||||||
<Keywords color="Friend">
|
|
||||||
<Word>friend</Word>
|
<Keywords color="BooleanConstants">
|
||||||
</Keywords>
|
<Word>true</Word>
|
||||||
<Keywords color="Modifiers">
|
<Word>True</Word>
|
||||||
<Word>private</Word>
|
<Word>false</Word>
|
||||||
<Word>protected</Word>
|
<Word>False</Word>
|
||||||
<Word>public</Word>
|
<Word>NULL</Word>
|
||||||
<Word>const</Word>
|
</Keywords>
|
||||||
<Word>volatile</Word>
|
|
||||||
<Word>static</Word>
|
<Keywords color="AccessModifier">
|
||||||
</Keywords>
|
<Word>public</Word>
|
||||||
<Keywords color="TypeKeywords">
|
<Word>protected</Word>
|
||||||
<Word>bool</Word>
|
<Word>private</Word>
|
||||||
<Word>char</Word>
|
</Keywords>
|
||||||
<Word>unsigned</Word>
|
|
||||||
<Word>union</Word>
|
<Keywords color="This">
|
||||||
<Word>virtual</Word>
|
<Word>this</Word>
|
||||||
<Word>double</Word>
|
</Keywords>
|
||||||
<Word>float</Word>
|
|
||||||
<Word>short</Word>
|
<!-- Reference symbols -->
|
||||||
<Word>signed</Word>
|
<Rule color="Pointer">(?<=[A-Za-z0-9_>&\]])&(?=\s*[A-Za-z_<])</Rule>
|
||||||
<Word>void</Word>
|
|
||||||
<Word>class</Word>
|
<Rule color="LabelColor">\bLabel_\d+:</Rule>
|
||||||
<Word>enum</Word>
|
|
||||||
<Word>struct</Word>
|
<!-- Numbers (hex too) -->
|
||||||
</Keywords>
|
<Rule color="Number">\b(0x[0-9a-fA-F]+|[0-9]+(\.[0-9]+)?)\b</Rule>
|
||||||
<Keywords color="BooleanConstants">
|
|
||||||
<Word>false</Word>
|
<Rule color="StaticClass">\bU[A-Z][A-Za-z0-9_]*\b(?=::)</Rule>
|
||||||
<Word>true</Word>
|
<Rule color="Function">[A-Za-z_][A-Za-z0-9_]*\s*(?=\()</Rule>
|
||||||
</Keywords>
|
|
||||||
<Keywords color="LoopKeywords">
|
<Rule color="Brace">[\[\]\{\}]</Rule>
|
||||||
<Word>do</Word>
|
|
||||||
<Word>for</Word>
|
<Rule color="Comment">(\/\/.*|\/\*[\s\S]*?\*\/)</Rule>
|
||||||
<Word>while</Word>
|
|
||||||
</Keywords>
|
<!-- Template Functions -->
|
||||||
<Keywords color="JumpKeywords">
|
<Rule color="Function">\b[A-Za-z_][A-Za-z0-9_]*\b(?=<)</Rule>
|
||||||
<Word>break</Word>
|
|
||||||
<Word>continue</Word>
|
<!-- Types -->
|
||||||
<Word>goto</Word>
|
<Rule color="Type">\b[A-Z][A-Za-z0-9_]*(?:<[^>]+>)?[*&]?(?=\s+[*&]?[A-Za-z_][A-Za-z0-9_]*\s*(=|;|\)|,))</Rule>
|
||||||
<Word>return</Word>
|
|
||||||
</Keywords>
|
<!-- Types inside <> -->
|
||||||
<Keywords color="ExceptionHandling">
|
<Rule color="Type">(?<=<)\s*[A-Z][A-Za-z0-9_]*(?:<[^>]+>)?[*&]?\s*(?=[>,])</Rule>
|
||||||
<Word>catch</Word>
|
|
||||||
<Word>throw</Word>
|
<!-- Match class name after the 'class' keyword -->
|
||||||
<Word>try</Word>
|
<Rule color="Type">\b(?<=class\s)[A-Za-z_][A-Za-z0-9_]*</Rule>
|
||||||
</Keywords>
|
|
||||||
<Keywords color="ControlFlow">
|
<!-- Match name after 'public' keyword -->
|
||||||
<Word>case</Word>
|
<Rule color="Type">\b(?<=public\s)[A-Za-z_][A-Za-z0-9_]*</Rule>
|
||||||
<Word>else</Word>
|
|
||||||
<Word>if</Word>
|
<!-- Types in function parameters -->
|
||||||
<Word>switch</Word>
|
<Rule color="Type">\b(?:T|F|U|E)[A-Z][A-Za-z0-9_]*(?:<[^>]+>)?[*&]?(?=\s+[*&]?[A-Za-z_][A-Za-z0-9_]*\s*(?:=|,|\)))</Rule>
|
||||||
<Word>default</Word>
|
<Rule color="Type">\b(?<=[,(]\s*const\s)(?:T|F|U)[A-Z][A-Za-z0-9_]*[*&]?(?=\s)</Rule>
|
||||||
</Keywords>
|
|
||||||
<Keywords color="Keywords">
|
<!-- First parameter type in function -->
|
||||||
<Word>asm</Word>
|
<Rule color="Type">\b(?<=\()\s*[TUF][A-Z][A-Za-z0-9_]*(?=\s*<)</Rule>
|
||||||
<Word>auto</Word>
|
<Rule color="Type">\b(?<=\()\s*[TUF][A-Z][A-Za-z0-9_]*[*&]?(?=\s)</Rule>
|
||||||
<Word>compl</Word>
|
<Rule color="Type">\b(?<=\(\s*const\s)[TUF][A-Z][A-Za-z0-9_]*[*&]?(?=\s)</Rule>
|
||||||
<Word>mutable</Word>
|
|
||||||
<Word>const_cast</Word>
|
</RuleSet>
|
||||||
<Word>delete</Word>
|
</SyntaxDefinition>
|
||||||
<Word>dynamic_cast</Word>
|
|
||||||
<Word>explicit</Word>
|
|
||||||
<Word>export</Word>
|
|
||||||
<Word>extern</Word>
|
|
||||||
<Word>inline</Word>
|
|
||||||
<Word>int</Word>
|
|
||||||
<Word>long</Word>
|
|
||||||
<Word>operator</Word>
|
|
||||||
<Word>register</Word>
|
|
||||||
<Word>reinterpret_cast</Word>
|
|
||||||
<Word>sizeof</Word>
|
|
||||||
<Word>static_cast</Word>
|
|
||||||
<Word>template</Word>
|
|
||||||
<Word>typedef</Word>
|
|
||||||
<Word>typeid</Word>
|
|
||||||
<Word>typename</Word>
|
|
||||||
</Keywords>
|
|
||||||
<Span color="Preprocessor">
|
|
||||||
<Begin>\#</Begin>
|
|
||||||
</Span>
|
|
||||||
<Span color="Comment">
|
|
||||||
<Begin>//</Begin>
|
|
||||||
</Span>
|
|
||||||
<Span color="Comment" multiline="true">
|
|
||||||
<Begin>/\*</Begin>
|
|
||||||
<End>\*/</End>
|
|
||||||
</Span>
|
|
||||||
<Span color="String">
|
|
||||||
<Begin>"</Begin>
|
|
||||||
<End>"</End>
|
|
||||||
<RuleSet>
|
|
||||||
<Span begin="\\" end="." />
|
|
||||||
</RuleSet>
|
|
||||||
</Span>
|
|
||||||
<Span color="Character">
|
|
||||||
<Begin>'</Begin>
|
|
||||||
<End>'</End>
|
|
||||||
<RuleSet>
|
|
||||||
<Span begin="\\" end="." />
|
|
||||||
</RuleSet>
|
|
||||||
</Span>
|
|
||||||
<Rule color="MethodName">[\d\w_]+(?=(\s*\())</Rule>
|
|
||||||
<Rule color="Digits">\b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?</Rule>
|
|
||||||
</RuleSet>
|
|
||||||
</SyntaxDefinition>
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ out vec3 fColor;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_PointSize = 7.5f;
|
gl_PointSize = 7.5;
|
||||||
gl_Position = uProjection * uView * uInstanceMatrix * vec4(vPos, 1.0);
|
gl_Position = uProjection * uView * uInstanceMatrix * vec4(vPos, 1.0);
|
||||||
fPos = vec3(uInstanceMatrix * vec4(vPos, 1.0));
|
fPos = vec3(uInstanceMatrix * vec4(vPos, 1.0));
|
||||||
fColor = vColor;
|
fColor = vColor;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ out vec3 fColor;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_PointSize = 7.5f;
|
gl_PointSize = 7.5;
|
||||||
gl_Position = uProjection * uView * uInstanceMatrix * uCollisionMatrix * vec4(vPos.xzy * uScaleDown, 1.0);
|
gl_Position = uProjection * uView * uInstanceMatrix * uCollisionMatrix * vec4(vPos.xzy * uScaleDown, 1.0);
|
||||||
fPos = vec3(uInstanceMatrix * uCollisionMatrix * vec4(vPos.xzy * uScaleDown, 1.0));
|
fPos = vec3(uInstanceMatrix * uCollisionMatrix * vec4(vPos.xzy * uScaleDown, 1.0));
|
||||||
fColor = vec3(1.0);
|
fColor = vec3(1.0);
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,18 @@ void main()
|
||||||
finalNormal = normalize(finalNormal);
|
finalNormal = normalize(finalNormal);
|
||||||
finalTangent = normalize(finalTangent);
|
finalTangent = normalize(finalTangent);
|
||||||
}
|
}
|
||||||
|
else if (uIsSpline)
|
||||||
|
{
|
||||||
|
GpuSplineMeshParams params = uSplineParameters[gl_InstanceID];
|
||||||
|
float distanceAlong = GetAxisValueRef(params.ForwardAxis, bindPos.xzy);
|
||||||
|
vec3 computed = ComputeRatioAlongSpline(params, distanceAlong);
|
||||||
|
mat4 sliceTransform = CalcSliceTransformAtSplineOffset(params, computed);
|
||||||
|
SetAxisValueRef(params.ForwardAxis, bindPos.xzy, 0.0);
|
||||||
|
|
||||||
|
finalPos = (sliceTransform * bindPos.xzyw).xzyw;
|
||||||
|
finalNormal = bindNormal;
|
||||||
|
finalTangent = bindTangent;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
finalPos = bindPos;
|
finalPos = bindPos;
|
||||||
|
|
|
||||||
|
|
@ -32,5 +32,5 @@ void main()
|
||||||
outVar.view = view;
|
outVar.view = view;
|
||||||
outVar.nearPoint = UnprojectPoint(vPos.xy, -1.0).xyz;
|
outVar.nearPoint = UnprojectPoint(vPos.xy, -1.0).xyz;
|
||||||
outVar.farPoint = UnprojectPoint(vPos.xy, 1.0).xyz;
|
outVar.farPoint = UnprojectPoint(vPos.xy, 1.0).xyz;
|
||||||
gl_Position = vec4(vPos, 1.0f);
|
gl_Position = vec4(vPos, 1.0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,17 @@ void main()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (uIsSpline)
|
||||||
|
{
|
||||||
|
GpuSplineMeshParams params = uSplineParameters[gl_InstanceID];
|
||||||
|
float distanceAlong = GetAxisValueRef(params.ForwardAxis, bindPos.xzy);
|
||||||
|
vec3 computed = ComputeRatioAlongSpline(params, distanceAlong);
|
||||||
|
mat4 sliceTransform = CalcSliceTransformAtSplineOffset(params, computed);
|
||||||
|
SetAxisValueRef(params.ForwardAxis, bindPos.xzy, 0.0);
|
||||||
|
|
||||||
|
finalPos = (sliceTransform * bindPos.xzyw).xzyw;
|
||||||
|
finalNormal = bindNormal;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
finalPos = bindPos;
|
finalPos = bindPos;
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,16 @@ void main()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (uIsSpline)
|
||||||
|
{
|
||||||
|
GpuSplineMeshParams params = uSplineParameters[gl_InstanceID];
|
||||||
|
float distanceAlong = GetAxisValueRef(params.ForwardAxis, bindPos.xzy);
|
||||||
|
vec3 computed = ComputeRatioAlongSpline(params, distanceAlong);
|
||||||
|
mat4 sliceTransform = CalcSliceTransformAtSplineOffset(params, computed);
|
||||||
|
SetAxisValueRef(params.ForwardAxis, bindPos.xzy, 0.0);
|
||||||
|
|
||||||
|
finalPos = (sliceTransform * bindPos.xzyw).xzyw;
|
||||||
|
}
|
||||||
else finalPos = bindPos;
|
else finalPos = bindPos;
|
||||||
|
|
||||||
gl_Position = uProjection * uView * vInstanceMatrix * finalPos;
|
gl_Position = uProjection * uView * vInstanceMatrix * finalPos;
|
||||||
|
|
|
||||||
216
FModel/Resources/spline.vert
Normal file
216
FModel/Resources/spline.vert
Normal file
|
|
@ -0,0 +1,216 @@
|
||||||
|
#version 460 core
|
||||||
|
|
||||||
|
// yeeted from minshu https://github.com/FabianFG/CUE4Parse/commit/61cef25b8eef4160651ee41e2b1ceefc5135803f
|
||||||
|
|
||||||
|
struct GpuSplineMeshParams {
|
||||||
|
int ForwardAxis;
|
||||||
|
float SplineBoundaryMin;
|
||||||
|
float SplineBoundaryMax;
|
||||||
|
bool bSmoothInterpRollScale;
|
||||||
|
|
||||||
|
vec3 MeshOrigin;
|
||||||
|
vec3 MeshBoxExtent;
|
||||||
|
|
||||||
|
vec3 StartPos;
|
||||||
|
float StartRoll;
|
||||||
|
vec3 StartTangent;
|
||||||
|
vec2 StartScale;
|
||||||
|
vec2 StartOffset;
|
||||||
|
vec3 EndPos;
|
||||||
|
float EndRoll;
|
||||||
|
vec3 EndTangent;
|
||||||
|
vec2 EndScale;
|
||||||
|
vec2 EndOffset;
|
||||||
|
|
||||||
|
vec3 SplineUpDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(std430, binding = 3) buffer SplineParameters
|
||||||
|
{
|
||||||
|
GpuSplineMeshParams uSplineParameters[];
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform bool uIsSpline;
|
||||||
|
|
||||||
|
vec3 getSafeNormal(vec3 vector) {
|
||||||
|
float squareSum = dot(vector, vector);
|
||||||
|
|
||||||
|
if (squareSum == 1.0) {
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (squareSum < 1e-8) {
|
||||||
|
return vec3(0.0); // Return a zero vector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the scale factor to normalize the vector
|
||||||
|
float scale = inversesqrt(squareSum);
|
||||||
|
return vector * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetAxisValueRef(int forwardAxis, vec3 pos)
|
||||||
|
{
|
||||||
|
switch (forwardAxis)
|
||||||
|
{
|
||||||
|
case 0: return pos.x;
|
||||||
|
case 1: return pos.y;
|
||||||
|
case 2: return pos.z;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetAxisValueRef(int forwardAxis, inout vec3 pos, float v)
|
||||||
|
{
|
||||||
|
switch (forwardAxis)
|
||||||
|
{
|
||||||
|
case 0: pos.x = v; break;
|
||||||
|
case 1: pos.y = v; break;
|
||||||
|
case 2: pos.z = v; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 SplineEvalTangent(GpuSplineMeshParams params, float a)
|
||||||
|
{
|
||||||
|
vec3 c = (6 * params.StartPos) + (3 * params.StartTangent) + (3 * params.EndTangent) - (6 * params.EndPos);
|
||||||
|
vec3 d = (-6 * params.StartPos) - (4 * params.StartTangent) - (2 * params.EndTangent) + (6 * params.EndPos);
|
||||||
|
vec3 e = params.StartTangent;
|
||||||
|
|
||||||
|
float a2 = a * a;
|
||||||
|
|
||||||
|
return (c * a2) + (d * a) + e;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 SplineEvalDir(GpuSplineMeshParams params, float a)
|
||||||
|
{
|
||||||
|
return getSafeNormal(SplineEvalTangent(params, a));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 SplineEvalPos(GpuSplineMeshParams params, float a)
|
||||||
|
{
|
||||||
|
float a2 = a * a;
|
||||||
|
float a3 = a2 * a;
|
||||||
|
|
||||||
|
return (((2 * a3) - (3 * a2) + 1) * params.StartPos) + ((a3 - (2 * a2) + a) * params.StartTangent) + ((a3 - a2) * params.EndTangent) + (((-2 * a3) + (3 * a2)) * params.EndPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 ComputeRatioAlongSpline(GpuSplineMeshParams params, float distanceAlong)
|
||||||
|
{
|
||||||
|
float alpha = 0.0;
|
||||||
|
float minT = 0.0;
|
||||||
|
float maxT = 1.0;
|
||||||
|
|
||||||
|
const float SmallNumber = 1e-8;
|
||||||
|
bool bHasCustomBoundary = abs(params.SplineBoundaryMin - params.SplineBoundaryMax) > SmallNumber;
|
||||||
|
if (bHasCustomBoundary)
|
||||||
|
{
|
||||||
|
float splineLength = params.SplineBoundaryMax - params.SplineBoundaryMin;
|
||||||
|
if (splineLength > 0)
|
||||||
|
{
|
||||||
|
alpha = (distanceAlong - params.SplineBoundaryMin) / splineLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
float boundMin = GetAxisValueRef(params.ForwardAxis, params.MeshOrigin - params.MeshBoxExtent);
|
||||||
|
float boundMax = GetAxisValueRef(params.ForwardAxis, params.MeshOrigin + params.MeshBoxExtent);
|
||||||
|
|
||||||
|
float boundMinT = (boundMin - params.SplineBoundaryMin) / (params.SplineBoundaryMax - params.SplineBoundaryMin);
|
||||||
|
float boundMaxT = (boundMax - params.SplineBoundaryMin) / (params.SplineBoundaryMax - params.SplineBoundaryMin);
|
||||||
|
|
||||||
|
const float MaxSplineExtrapolation = 4.0;
|
||||||
|
minT = max(-MaxSplineExtrapolation, boundMinT);
|
||||||
|
maxT = min(boundMaxT, MaxSplineExtrapolation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float meshMinZ = GetAxisValueRef(params.ForwardAxis, params.MeshOrigin) - GetAxisValueRef(params.ForwardAxis, params.MeshBoxExtent);
|
||||||
|
float meshRangeZ = 2 * GetAxisValueRef(params.ForwardAxis, params.MeshBoxExtent);
|
||||||
|
|
||||||
|
if (meshRangeZ > SmallNumber) {
|
||||||
|
alpha = (distanceAlong - meshMinZ) / meshRangeZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vec3(alpha, minT, maxT);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat4 CalcSliceTransformAtSplineOffset(GpuSplineMeshParams params, vec3 computed)
|
||||||
|
{
|
||||||
|
float alpha = computed.x;
|
||||||
|
float minT = computed.y;
|
||||||
|
float maxT = computed.z;
|
||||||
|
|
||||||
|
float hermiteAlpha = params.bSmoothInterpRollScale ? smoothstep(0.0, 1.0, alpha) : alpha;
|
||||||
|
|
||||||
|
vec3 splinePos;
|
||||||
|
vec3 splineDir;
|
||||||
|
if (alpha < minT)
|
||||||
|
{
|
||||||
|
vec3 startTangent = SplineEvalTangent(params, minT);
|
||||||
|
splinePos = SplineEvalPos(params, minT) + (startTangent * (alpha - minT));
|
||||||
|
splineDir = getSafeNormal(startTangent);
|
||||||
|
}
|
||||||
|
else if (alpha > maxT)
|
||||||
|
{
|
||||||
|
vec3 endTangent = SplineEvalTangent(params, maxT);
|
||||||
|
splinePos = SplineEvalPos(params, maxT) + (endTangent * (alpha - maxT));
|
||||||
|
splineDir = getSafeNormal(endTangent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
splinePos = SplineEvalPos(params, alpha);
|
||||||
|
splineDir = SplineEvalDir(params, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// base
|
||||||
|
vec3 baseXVec = getSafeNormal(cross(params.SplineUpDir, splineDir));
|
||||||
|
vec3 baseYVec = getSafeNormal(cross(splineDir, baseXVec));
|
||||||
|
|
||||||
|
// Offset the spline by the desired amount
|
||||||
|
vec2 sliceOffset = mix(params.StartOffset, params.EndOffset, hermiteAlpha);
|
||||||
|
splinePos += sliceOffset.x * baseXVec;
|
||||||
|
splinePos += sliceOffset.y * baseYVec;
|
||||||
|
|
||||||
|
// Apply Roll
|
||||||
|
float useRoll = mix(params.StartRoll, params.EndRoll, hermiteAlpha);
|
||||||
|
float cosAng = cos(useRoll);
|
||||||
|
float sinAng = sin(useRoll);
|
||||||
|
vec3 xVec = (cosAng * baseXVec) - (sinAng * baseYVec);
|
||||||
|
vec3 yVec = (cosAng * baseYVec) + (sinAng * baseXVec);
|
||||||
|
|
||||||
|
// Find Scale
|
||||||
|
vec2 useScale = mix(params.StartScale, params.EndScale, hermiteAlpha);
|
||||||
|
|
||||||
|
// Build overall transform
|
||||||
|
mat4 sliceTransform = mat4(0);
|
||||||
|
vec3 scale;
|
||||||
|
switch (params.ForwardAxis) {
|
||||||
|
case 0:
|
||||||
|
sliceTransform[0] = vec4(splineDir, 0.0);
|
||||||
|
sliceTransform[1] = vec4(xVec, 0.0);
|
||||||
|
sliceTransform[2] = vec4(yVec, 0.0);
|
||||||
|
sliceTransform[3] = vec4(splinePos, 1.0);
|
||||||
|
scale = vec3(1.0, useScale.x, useScale.y);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
sliceTransform[0] = vec4(yVec, 0.0);
|
||||||
|
sliceTransform[1] = vec4(splineDir, 0.0);
|
||||||
|
sliceTransform[2] = vec4(xVec, 0.0);
|
||||||
|
sliceTransform[3] = vec4(splinePos, 1.0);
|
||||||
|
scale = vec3(useScale.y, 1.0, useScale.x);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
sliceTransform[0] = vec4(xVec, 0.0);
|
||||||
|
sliceTransform[1] = vec4(yVec, 0.0);
|
||||||
|
sliceTransform[2] = vec4(splineDir, 0.0);
|
||||||
|
sliceTransform[3] = vec4(splinePos, 1.0);
|
||||||
|
scale = vec3(useScale.x, useScale.y, 1.0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat4 scaleMatrix = mat4(
|
||||||
|
vec4(scale.x, 0.0, 0.0, 0.0),
|
||||||
|
vec4(0.0, scale.y, 0.0, 0.0),
|
||||||
|
vec4(0.0, 0.0, scale.z, 0.0),
|
||||||
|
vec4(0.0, 0.0, 0.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
return sliceTransform * scaleMatrix;
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ namespace FModel.Services
|
||||||
|
|
||||||
private readonly Assets _staticAssets = new()
|
private readonly Assets _staticAssets = new()
|
||||||
{
|
{
|
||||||
LargeImageKey = "official_logo", SmallImageKey = "verified", SmallImageText = $"v{Constants.APP_VERSION}"
|
LargeImageKey = "official_logo", SmallImageKey = "verified", SmallImageText = $"v{Constants.APP_VERSION} ({Constants.APP_SHORT_COMMIT_ID})"
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly Button[] _buttons =
|
private readonly Button[] _buttons =
|
||||||
|
|
@ -48,7 +48,7 @@ namespace FModel.Services
|
||||||
|
|
||||||
public void UpdatePresence(CUE4ParseViewModel viewModel) =>
|
public void UpdatePresence(CUE4ParseViewModel viewModel) =>
|
||||||
UpdatePresence(
|
UpdatePresence(
|
||||||
$"{viewModel.Provider.GameDisplayName ?? viewModel.Provider.InternalGameName} - {viewModel.Provider.MountedVfs.Count}/{viewModel.Provider.MountedVfs.Count + viewModel.Provider.UnloadedVfs.Count} Packages",
|
$"{viewModel.Provider.GameDisplayName ?? viewModel.Provider.ProjectName} - {viewModel.Provider.MountedVfs.Count}/{viewModel.Provider.MountedVfs.Count + viewModel.Provider.UnloadedVfs.Count} Packages",
|
||||||
$"Mode: {UserSettings.Default.LoadingMode.GetDescription()} - {viewModel.SearchVm.ResultsCount:### ### ###} Loaded Assets".Trim());
|
$"Mode: {UserSettings.Default.LoadingMode.GetDescription()} - {viewModel.SearchVm.ResultsCount:### ### ###} Loaded Assets".Trim());
|
||||||
|
|
||||||
public void UpdatePresence(string details, string state)
|
public void UpdatePresence(string details, string state)
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,21 @@ public class CustomDirectory : ViewModel
|
||||||
new("Shop Backgrounds", "ShooterGame/Content/UI/OutOfGame/MainMenu/Store/Shared/Textures/"),
|
new("Shop Backgrounds", "ShooterGame/Content/UI/OutOfGame/MainMenu/Store/Shared/Textures/"),
|
||||||
new("Weapon Renders", "ShooterGame/Content/UI/Screens/OutOfGame/MainMenu/Collection/Assets/Large/")
|
new("Weapon Renders", "ShooterGame/Content/UI/Screens/OutOfGame/MainMenu/Collection/Assets/Large/")
|
||||||
};
|
};
|
||||||
|
case "Dead by Daylight":
|
||||||
|
return new List<CustomDirectory>
|
||||||
|
{
|
||||||
|
new("Characters V1", "DeadByDaylight/Plugins/DBDCharacters/"),
|
||||||
|
new("Characters V2", "DeadByDaylight/Plugins/Runtime/Bhvr/DBDCharacters/"),
|
||||||
|
new("Characters (Deprecated)", "DeadbyDaylight/Content/Characters/"),
|
||||||
|
new("Meshes", "DeadByDaylight/Content/Meshes/"),
|
||||||
|
new("Textures", "DeadByDaylight/Content/Textures/"),
|
||||||
|
new("Icons", "DeadByDaylight/Content/UI/UMGAssets/Icons/"),
|
||||||
|
new("Blueprints", "DeadByDaylight/Content/Blueprints/"),
|
||||||
|
new("Audio Events", "DeadByDaylight/Content/Audio/Events/"),
|
||||||
|
new("Audio", "DeadByDaylight/Content/WwiseAudio/Cooked/"),
|
||||||
|
new("Data Tables", "DeadByDaylight/Content/Data/"),
|
||||||
|
new("Localization", "DeadByDaylight/Content/Localization/")
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return new List<CustomDirectory>();
|
return new List<CustomDirectory>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||||
using CUE4Parse.UE4.Versions;
|
using CUE4Parse.UE4.Versions;
|
||||||
|
|
@ -24,7 +24,8 @@ public class DirectorySettings : ViewModel, ICloneable
|
||||||
Endpoints = old?.Endpoints ?? EndpointSettings.Default(gameName),
|
Endpoints = old?.Endpoints ?? EndpointSettings.Default(gameName),
|
||||||
Directories = old?.Directories ?? CustomDirectory.Default(gameName),
|
Directories = old?.Directories ?? CustomDirectory.Default(gameName),
|
||||||
AesKeys = old?.AesKeys ?? new AesResponse { MainKey = aes, DynamicKeys = null },
|
AesKeys = old?.AesKeys ?? new AesResponse { MainKey = aes, DynamicKeys = null },
|
||||||
LastAesReload = old?.LastAesReload ?? DateTime.Today.AddDays(-1)
|
LastAesReload = old?.LastAesReload ?? DateTime.Today.AddDays(-1),
|
||||||
|
CriwareDecryptionKey = old?.CriwareDecryptionKey ?? 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,6 +99,13 @@ public class DirectorySettings : ViewModel, ICloneable
|
||||||
set => SetProperty(ref _lastAesReload, value);
|
set => SetProperty(ref _lastAesReload, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ulong _criwareDecryptionKey;
|
||||||
|
public ulong CriwareDecryptionKey
|
||||||
|
{
|
||||||
|
get => _criwareDecryptionKey;
|
||||||
|
set => SetProperty(ref _criwareDecryptionKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
private bool Equals(DirectorySettings other)
|
private bool Equals(DirectorySettings other)
|
||||||
{
|
{
|
||||||
return GameDirectory == other.GameDirectory && UeVersion == other.UeVersion;
|
return GameDirectory == other.GameDirectory && UeVersion == other.UeVersion;
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ public class EndpointSettings : ViewModel
|
||||||
case "Fortnite [LIVE]":
|
case "Fortnite [LIVE]":
|
||||||
return new EndpointSettings[]
|
return new EndpointSettings[]
|
||||||
{
|
{
|
||||||
new("https://fortnitecentral.genxgames.gg/api/v1/aes", "$.['mainKey','dynamicKeys']"),
|
new("https://uedb.dev/svc/api/v1/fortnite/aes", "$.['mainKey','dynamicKeys']"),
|
||||||
new("https://fortnitecentral.genxgames.gg/api/v1/mappings", "$.[0].['url','fileName']") // just get the first available, not just oodle! (Unfortunately not default except when resetting settings)
|
new("https://uedb.dev/svc/api/v1/fortnite/mappings", "$.mappings.ZStandard")
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return new EndpointSettings[] { new(), new() };
|
return new EndpointSettings[] { new(), new() };
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Nanite;
|
||||||
|
using CUE4Parse.UE4.Versions;
|
||||||
using CUE4Parse_Conversion;
|
using CUE4Parse_Conversion;
|
||||||
using CUE4Parse_Conversion.Animations;
|
using CUE4Parse_Conversion.Animations;
|
||||||
using CUE4Parse.UE4.Versions;
|
|
||||||
using CUE4Parse_Conversion.Meshes;
|
using CUE4Parse_Conversion.Meshes;
|
||||||
using CUE4Parse_Conversion.Textures;
|
using CUE4Parse_Conversion.Textures;
|
||||||
using CUE4Parse_Conversion.UEFormat.Enums;
|
using CUE4Parse_Conversion.UEFormat.Enums;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.ViewModels;
|
using FModel.ViewModels;
|
||||||
using FModel.ViewModels.ApiEndpoints.Models;
|
using FModel.ViewModels.ApiEndpoints.Models;
|
||||||
|
|
@ -60,6 +61,7 @@ namespace FModel.Settings
|
||||||
{
|
{
|
||||||
LodFormat = Default.LodExportFormat,
|
LodFormat = Default.LodExportFormat,
|
||||||
MeshFormat = Default.MeshExportFormat,
|
MeshFormat = Default.MeshExportFormat,
|
||||||
|
NaniteMeshFormat = Default.NaniteMeshExportFormat,
|
||||||
AnimFormat = Default.MeshExportFormat switch
|
AnimFormat = Default.MeshExportFormat switch
|
||||||
{
|
{
|
||||||
EMeshFormat.UEFormat => EAnimFormat.UEFormat,
|
EMeshFormat.UEFormat => EAnimFormat.UEFormat,
|
||||||
|
|
@ -71,7 +73,8 @@ namespace FModel.Settings
|
||||||
CompressionFormat = Default.CompressionFormat,
|
CompressionFormat = Default.CompressionFormat,
|
||||||
Platform = Default.CurrentDir.TexturePlatform,
|
Platform = Default.CurrentDir.TexturePlatform,
|
||||||
ExportMorphTargets = Default.SaveMorphTargets,
|
ExportMorphTargets = Default.SaveMorphTargets,
|
||||||
ExportMaterials = Default.SaveEmbeddedMaterials
|
ExportMaterials = Default.SaveEmbeddedMaterials,
|
||||||
|
ExportHdrTexturesAsHdr = Default.SaveHdrTexturesAsHdr
|
||||||
};
|
};
|
||||||
|
|
||||||
private bool _showChangelog = true;
|
private bool _showChangelog = true;
|
||||||
|
|
@ -137,13 +140,6 @@ namespace FModel.Settings
|
||||||
set => SetProperty(ref _lastOpenedSettingTab, value);
|
set => SetProperty(ref _lastOpenedSettingTab, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isAutoOpenSounds = true;
|
|
||||||
public bool IsAutoOpenSounds
|
|
||||||
{
|
|
||||||
get => _isAutoOpenSounds;
|
|
||||||
set => SetProperty(ref _isAutoOpenSounds, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool _isLoggerExpanded = true;
|
private bool _isLoggerExpanded = true;
|
||||||
public bool IsLoggerExpanded
|
public bool IsLoggerExpanded
|
||||||
{
|
{
|
||||||
|
|
@ -200,6 +196,13 @@ namespace FModel.Settings
|
||||||
set => SetProperty(ref _keepDirectoryStructure, value);
|
set => SetProperty(ref _keepDirectoryStructure, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _showDecompileOption = false;
|
||||||
|
public bool ShowDecompileOption
|
||||||
|
{
|
||||||
|
get => _showDecompileOption;
|
||||||
|
set => SetProperty(ref _showDecompileOption, value);
|
||||||
|
}
|
||||||
|
|
||||||
private ECompressedAudio _compressedAudioMode = ECompressedAudio.PlayDecompressed;
|
private ECompressedAudio _compressedAudioMode = ECompressedAudio.PlayDecompressed;
|
||||||
public ECompressedAudio CompressedAudioMode
|
public ECompressedAudio CompressedAudioMode
|
||||||
{
|
{
|
||||||
|
|
@ -256,6 +259,13 @@ namespace FModel.Settings
|
||||||
set => SetProperty(ref _readScriptData, value);
|
set => SetProperty(ref _readScriptData, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _readShaderMaps;
|
||||||
|
public bool ReadShaderMaps
|
||||||
|
{
|
||||||
|
get => _readShaderMaps;
|
||||||
|
set => SetProperty(ref _readShaderMaps, value);
|
||||||
|
}
|
||||||
|
|
||||||
private IDictionary<string, DirectorySettings> _perDirectory = new Dictionary<string, DirectorySettings>();
|
private IDictionary<string, DirectorySettings> _perDirectory = new Dictionary<string, DirectorySettings>();
|
||||||
public IDictionary<string, DirectorySettings> PerDirectory
|
public IDictionary<string, DirectorySettings> PerDirectory
|
||||||
{
|
{
|
||||||
|
|
@ -297,6 +307,13 @@ namespace FModel.Settings
|
||||||
set => SetProperty(ref _dirRightTab, value);
|
set => SetProperty(ref _dirRightTab, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Hotkey _switchAssetExplorer = new(Key.Z);
|
||||||
|
public Hotkey SwitchAssetExplorer
|
||||||
|
{
|
||||||
|
get => _switchAssetExplorer;
|
||||||
|
set => SetProperty(ref _switchAssetExplorer, value);
|
||||||
|
}
|
||||||
|
|
||||||
private Hotkey _assetLeftTab = new(Key.Q);
|
private Hotkey _assetLeftTab = new(Key.Q);
|
||||||
public Hotkey AssetLeftTab
|
public Hotkey AssetLeftTab
|
||||||
{
|
{
|
||||||
|
|
@ -353,13 +370,20 @@ namespace FModel.Settings
|
||||||
set => SetProperty(ref _nextAudio, value);
|
set => SetProperty(ref _nextAudio, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EMeshFormat _meshExportFormat = EMeshFormat.ActorX;
|
private EMeshFormat _meshExportFormat = EMeshFormat.UEFormat;
|
||||||
public EMeshFormat MeshExportFormat
|
public EMeshFormat MeshExportFormat
|
||||||
{
|
{
|
||||||
get => _meshExportFormat;
|
get => _meshExportFormat;
|
||||||
set => SetProperty(ref _meshExportFormat, value);
|
set => SetProperty(ref _meshExportFormat, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ENaniteMeshFormat _naniteMeshExportFormat = ENaniteMeshFormat.OnlyNaniteLOD;
|
||||||
|
public ENaniteMeshFormat NaniteMeshExportFormat
|
||||||
|
{
|
||||||
|
get => _naniteMeshExportFormat;
|
||||||
|
set => SetProperty(ref _naniteMeshExportFormat, value);
|
||||||
|
}
|
||||||
|
|
||||||
private EMaterialFormat _materialExportFormat = EMaterialFormat.FirstLayer;
|
private EMaterialFormat _materialExportFormat = EMaterialFormat.FirstLayer;
|
||||||
public EMaterialFormat MaterialExportFormat
|
public EMaterialFormat MaterialExportFormat
|
||||||
{
|
{
|
||||||
|
|
@ -423,6 +447,13 @@ namespace FModel.Settings
|
||||||
set => SetProperty(ref _cameraMode, value);
|
set => SetProperty(ref _cameraMode, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int _wwiseMaxBnkPrefetch;
|
||||||
|
public int WwiseMaxBnkPrefetch
|
||||||
|
{
|
||||||
|
get => _wwiseMaxBnkPrefetch;
|
||||||
|
set => SetProperty(ref _wwiseMaxBnkPrefetch, value);
|
||||||
|
}
|
||||||
|
|
||||||
private int _previewMaxTextureSize = 1024;
|
private int _previewMaxTextureSize = 1024;
|
||||||
public int PreviewMaxTextureSize
|
public int PreviewMaxTextureSize
|
||||||
{
|
{
|
||||||
|
|
@ -444,6 +475,13 @@ namespace FModel.Settings
|
||||||
set => SetProperty(ref _previewSkeletalMeshes, value);
|
set => SetProperty(ref _previewSkeletalMeshes, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _previewAnimations = true;
|
||||||
|
public bool PreviewAnimations
|
||||||
|
{
|
||||||
|
get => _previewAnimations;
|
||||||
|
set => SetProperty(ref _previewAnimations, value);
|
||||||
|
}
|
||||||
|
|
||||||
private bool _previewMaterials = true;
|
private bool _previewMaterials = true;
|
||||||
public bool PreviewMaterials
|
public bool PreviewMaterials
|
||||||
{
|
{
|
||||||
|
|
@ -478,5 +516,26 @@ namespace FModel.Settings
|
||||||
get => _saveSkeletonAsMesh;
|
get => _saveSkeletonAsMesh;
|
||||||
set => SetProperty(ref _saveSkeletonAsMesh, value);
|
set => SetProperty(ref _saveSkeletonAsMesh, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _saveHdrTexturesAsHdr = true;
|
||||||
|
public bool SaveHdrTexturesAsHdr
|
||||||
|
{
|
||||||
|
get => _saveHdrTexturesAsHdr;
|
||||||
|
set => SetProperty(ref _saveHdrTexturesAsHdr, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _featurePreviewNewAssetExplorer = true;
|
||||||
|
public bool FeaturePreviewNewAssetExplorer
|
||||||
|
{
|
||||||
|
get => _featurePreviewNewAssetExplorer;
|
||||||
|
set => SetProperty(ref _featurePreviewNewAssetExplorer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _previewTexturesAssetExplorer = true;
|
||||||
|
public bool PreviewTexturesAssetExplorer
|
||||||
|
{
|
||||||
|
get => _previewTexturesAssetExplorer;
|
||||||
|
set => SetProperty(ref _previewTexturesAssetExplorer, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
using RestSharp;
|
using RestSharp;
|
||||||
|
using RestSharp.Interceptors;
|
||||||
|
|
||||||
namespace FModel.ViewModels.ApiEndpoints;
|
namespace FModel.ViewModels.ApiEndpoints;
|
||||||
|
|
||||||
public abstract class AbstractApiProvider
|
public abstract class AbstractApiProvider
|
||||||
{
|
{
|
||||||
protected readonly RestClient _client;
|
protected readonly RestClient _client;
|
||||||
|
protected readonly Interceptor _interceptor;
|
||||||
|
|
||||||
protected AbstractApiProvider(RestClient client)
|
protected AbstractApiProvider(RestClient client)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
|
_interceptor = new CompatibilityInterceptor
|
||||||
|
{
|
||||||
|
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using FModel.Extensions;
|
using CUE4Parse.Utils;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.ViewModels.ApiEndpoints.Models;
|
using FModel.ViewModels.ApiEndpoints.Models;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
@ -66,7 +66,7 @@ public class DynamicApiEndpoint : AbstractApiProvider
|
||||||
{
|
{
|
||||||
var request = new FRestRequest(url)
|
var request = new FRestRequest(url)
|
||||||
{
|
{
|
||||||
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
|
Interceptors = [_interceptor]
|
||||||
};
|
};
|
||||||
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
|
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
|
||||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ 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 _OAUTH_URL = "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token";
|
||||||
private const string _BASIC_TOKEN = "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE=";
|
private const string _BASIC_TOKEN = "basic ZWM2ODRiOGM2ODdmNDc5ZmFkZWEzY2IyYWQ4M2Y1YzY6ZTFmMzFjMjExZjI4NDEzMTg2MjYyZDM3YTEzZmM4NGQ=";
|
||||||
private const string _APP_URL = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live";
|
private const string _APP_URL = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live";
|
||||||
|
|
||||||
public EpicApiEndpoint(RestClient client) : base(client) { }
|
public EpicApiEndpoint(RestClient client) : base(client) { }
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using AutoUpdaterDotNET;
|
using AutoUpdaterDotNET;
|
||||||
|
using CUE4Parse.Utils;
|
||||||
using FModel.Extensions;
|
using FModel.Extensions;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
|
|
@ -105,7 +106,11 @@ public class FModelApiEndpoint : AbstractApiProvider
|
||||||
|
|
||||||
public void CheckForUpdates(bool launch = false)
|
public void CheckForUpdates(bool launch = false)
|
||||||
{
|
{
|
||||||
if (DateTime.Now < UserSettings.Default.NextUpdateCheck) return;
|
if (DateTime.Now < UserSettings.Default.NextUpdateCheck)
|
||||||
|
{
|
||||||
|
Log.Warning("Updates have been silenced until {DateTime}", UserSettings.Default.NextUpdateCheck);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (launch)
|
if (launch)
|
||||||
{
|
{
|
||||||
|
|
@ -139,7 +144,8 @@ public class FModelApiEndpoint : AbstractApiProvider
|
||||||
{
|
{
|
||||||
UserSettings.Default.LastUpdateCheck = DateTime.Now;
|
UserSettings.Default.LastUpdateCheck = DateTime.Now;
|
||||||
|
|
||||||
if (((CustomMandatory)args.Mandatory).CommitHash == Constants.APP_COMMIT_ID)
|
var targetHash = ((CustomMandatory) args.Mandatory).CommitHash;
|
||||||
|
if (targetHash == Constants.APP_COMMIT_ID)
|
||||||
{
|
{
|
||||||
if (UserSettings.Default.ShowChangelog)
|
if (UserSettings.Default.ShowChangelog)
|
||||||
ShowChangelog(args);
|
ShowChangelog(args);
|
||||||
|
|
@ -151,6 +157,7 @@ public class FModelApiEndpoint : AbstractApiProvider
|
||||||
UserSettings.Default.ShowChangelog = currentVersion != args.InstalledVersion;
|
UserSettings.Default.ShowChangelog = currentVersion != args.InstalledVersion;
|
||||||
|
|
||||||
const string message = "A new update is available!";
|
const string message = "A new update is available!";
|
||||||
|
Log.Warning("{message} Version {CurrentVersion} ({Hash})", message, currentVersion, targetHash);
|
||||||
Helper.OpenWindow<AdonisWindow>(message, () => new UpdateView { Title = message, ResizeMode = ResizeMode.NoResize }.ShowDialog());
|
Helper.OpenWindow<AdonisWindow>(message, () => new UpdateView { Title = message, ResizeMode = ResizeMode.NoResize }.ShowDialog());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,19 @@ public class FortniteCentralApiEndpoint : AbstractApiProvider
|
||||||
{
|
{
|
||||||
public FortniteCentralApiEndpoint(RestClient client) : base(client) { }
|
public FortniteCentralApiEndpoint(RestClient client) : base(client) { }
|
||||||
|
|
||||||
public async Task<Dictionary<string, Dictionary<string, string>>> GetHotfixesAsync(CancellationToken token, string language = "en")
|
public async Task<IDictionary<string, IDictionary<string, string>>> GetHotfixesAsync(CancellationToken token, string language = "en")
|
||||||
{
|
{
|
||||||
var request = new FRestRequest("https://fortnitecentral.genxgames.gg/api/v1/hotfixes")
|
var request = new FRestRequest("https://fortnitecentral.genxgames.gg/api/v1/hotfixes")
|
||||||
{
|
{
|
||||||
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
|
Interceptors = [_interceptor]
|
||||||
};
|
};
|
||||||
request.AddParameter("lang", language);
|
request.AddParameter("lang", language);
|
||||||
var response = await _client.ExecuteAsync<Dictionary<string, Dictionary<string, string>>>(request, token).ConfigureAwait(false);
|
var response = await _client.ExecuteAsync<IDictionary<string, IDictionary<string, string>>>(request, token).ConfigureAwait(false);
|
||||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
||||||
return response.Data;
|
return response.Data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, Dictionary<string, string>> GetHotfixes(CancellationToken token, string language = "en")
|
public IDictionary<string, IDictionary<string, string>> GetHotfixes(CancellationToken token, string language = "en")
|
||||||
{
|
{
|
||||||
return GetHotfixesAsync(token, language).GetAwaiter().GetResult();
|
return GetHotfixesAsync(token, language).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.ViewModels.ApiEndpoints.Models;
|
using FModel.ViewModels.ApiEndpoints.Models;
|
||||||
using RestSharp;
|
using RestSharp;
|
||||||
|
|
||||||
namespace FModel.ViewModels.ApiEndpoints;
|
namespace FModel.ViewModels.ApiEndpoints;
|
||||||
|
|
||||||
public class GitHubApiEndpoint : AbstractApiProvider
|
public class GitHubApiEndpoint(RestClient client) : AbstractApiProvider(client)
|
||||||
{
|
{
|
||||||
public GitHubApiEndpoint(RestClient client) : base(client) { }
|
public async Task<GitHubCommit[]> GetCommitHistoryAsync(string branch = "dev", int page = 1, int limit = 30)
|
||||||
|
|
||||||
public async Task<GitHubCommit[]> GetCommitHistoryAsync(string branch = "dev", int page = 1, int limit = 20)
|
|
||||||
{
|
{
|
||||||
var request = new FRestRequest(Constants.GH_COMMITS_HISTORY);
|
var request = new FRestRequest(Constants.GH_COMMITS_HISTORY);
|
||||||
request.AddParameter("sha", branch);
|
request.AddParameter("sha", branch);
|
||||||
|
|
@ -25,4 +23,11 @@ public class GitHubApiEndpoint : AbstractApiProvider
|
||||||
var response = await _client.ExecuteAsync<GitHubRelease>(request).ConfigureAwait(false);
|
var response = await _client.ExecuteAsync<GitHubRelease>(request).ConfigureAwait(false);
|
||||||
return response.Data;
|
return response.Data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Author> GetUserAsync(string username)
|
||||||
|
{
|
||||||
|
var request = new FRestRequest($"https://api.github.com/users/{username}");
|
||||||
|
var response = await _client.ExecuteAsync<Author>(request).ConfigureAwait(false);
|
||||||
|
return response.Data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using AdonisUI.Controls;
|
using AdonisUI.Controls;
|
||||||
using AutoUpdaterDotNET;
|
using AutoUpdaterDotNET;
|
||||||
|
|
@ -37,8 +38,7 @@ public class GitHubAsset : ViewModel
|
||||||
public class GitHubCommit : ViewModel
|
public class GitHubCommit : ViewModel
|
||||||
{
|
{
|
||||||
private string _sha;
|
private string _sha;
|
||||||
[J("sha")]
|
[J("sha")] public string Sha
|
||||||
public string Sha
|
|
||||||
{
|
{
|
||||||
get => _sha;
|
get => _sha;
|
||||||
set
|
set
|
||||||
|
|
@ -52,6 +52,35 @@ public class GitHubCommit : ViewModel
|
||||||
[J("commit")] public Commit Commit { get; set; }
|
[J("commit")] public Commit Commit { get; set; }
|
||||||
[J("author")] public Author Author { get; set; }
|
[J("author")] public Author Author { get; set; }
|
||||||
|
|
||||||
|
private Author[] _coAuthors = [];
|
||||||
|
public Author[] CoAuthors
|
||||||
|
{
|
||||||
|
get => _coAuthors;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _coAuthors, value);
|
||||||
|
RaisePropertyChanged(nameof(Authors));
|
||||||
|
RaisePropertyChanged(nameof(AuthorNames));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author[] Authors => Author != null ? new[] { Author }.Concat(CoAuthors).ToArray() : CoAuthors;
|
||||||
|
|
||||||
|
public string AuthorNames
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var authors = Authors;
|
||||||
|
return authors.Length switch
|
||||||
|
{
|
||||||
|
0 => string.Empty,
|
||||||
|
1 => authors[0].Login,
|
||||||
|
2 => $"{authors[0].Login} and {authors[1].Login}",
|
||||||
|
_ => string.Join(", ", authors.Take(authors.Length - 1).Select(a => a.Login)) + $", and {authors[^1].Login}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private GitHubAsset _asset;
|
private GitHubAsset _asset;
|
||||||
public GitHubAsset Asset
|
public GitHubAsset Asset
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ namespace FModel.ViewModels.ApiEndpoints;
|
||||||
|
|
||||||
public class ValorantApiEndpoint : AbstractApiProvider
|
public class ValorantApiEndpoint : AbstractApiProvider
|
||||||
{
|
{
|
||||||
private const string _URL = "https://fmodel.fortnite-api.com/valorant/v2/manifest";
|
private const string _URL = "https://valorant-api.com/v1/fmodel/manifest";
|
||||||
|
|
||||||
public ValorantApiEndpoint(RestClient client) : base(client) { }
|
public ValorantApiEndpoint(RestClient client) : base(client) { }
|
||||||
|
|
||||||
|
|
@ -30,6 +30,8 @@ public class ValorantApiEndpoint : AbstractApiProvider
|
||||||
{
|
{
|
||||||
var request = new FRestRequest(_URL);
|
var request = new FRestRequest(_URL);
|
||||||
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
|
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
return null;
|
||||||
return new VManifest(response.RawBytes);
|
return new VManifest(response.RawBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,8 +118,6 @@ public class VManifest
|
||||||
|
|
||||||
return chunkBytes;
|
return chunkBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VPakStream GetPakStream(int index) => new VPakStream(this, index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct VHeader
|
public readonly struct VHeader
|
||||||
|
|
@ -168,6 +168,7 @@ public readonly struct VPak
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetFullName() => $"ValorantLive/ShooterGame/Content/Paks/{Name}";
|
public string GetFullName() => $"ValorantLive/ShooterGame/Content/Paks/{Name}";
|
||||||
|
public VPakStream GetStream(VManifest manifest) => new(manifest, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
|
@ -176,22 +177,21 @@ public readonly struct VChunk
|
||||||
public readonly ulong Id;
|
public readonly ulong Id;
|
||||||
public readonly uint Size;
|
public readonly uint Size;
|
||||||
|
|
||||||
public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}";
|
public string GetUrl() => $"https://valorant-api.com/v1/fmodel/chunks/{Id}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VPakStream : Stream, IRandomAccessStream, ICloneable
|
public class VPakStream : RandomAccessStream, ICloneable
|
||||||
{
|
{
|
||||||
private readonly VManifest _manifest;
|
private readonly VManifest _manifest;
|
||||||
private readonly int _pakIndex;
|
private readonly VPak _pak;
|
||||||
private readonly VChunk[] _chunks;
|
private readonly VChunk[] _chunks;
|
||||||
|
|
||||||
public VPakStream(VManifest manifest, int pakIndex, long position = 0L)
|
public VPakStream(VManifest manifest, in VPak pak, long position = 0L)
|
||||||
{
|
{
|
||||||
_manifest = manifest;
|
_manifest = manifest;
|
||||||
_pakIndex = pakIndex;
|
_pak = pak;
|
||||||
_position = position;
|
_position = position;
|
||||||
|
|
||||||
var pak = manifest.Paks[pakIndex];
|
|
||||||
_chunks = new VChunk[pak.ChunkIndices.Length];
|
_chunks = new VChunk[pak.ChunkIndices.Length];
|
||||||
for (var i = 0; i < _chunks.Length; i++)
|
for (var i = 0; i < _chunks.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -201,12 +201,12 @@ public class VPakStream : Stream, IRandomAccessStream, ICloneable
|
||||||
Length = pak.Size;
|
Length = pak.Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Clone() => new VPakStream(_manifest, _pakIndex, _position);
|
public object Clone() => new VPakStream(_manifest, _pak, _position);
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count) =>
|
public override int Read(byte[] buffer, int offset, int count) =>
|
||||||
ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|
||||||
public int ReadAt(long position, byte[] buffer, int offset, int count) =>
|
public override int ReadAt(long position, byte[] buffer, int offset, int count) =>
|
||||||
ReadAtAsync(position, buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
ReadAtAsync(position, buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|
||||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
|
@ -216,7 +216,7 @@ public class VPakStream : Stream, IRandomAccessStream, ICloneable
|
||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> ReadAtAsync(long position, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public override async Task<int> ReadAtAsync(long position, byte[] buffer, int offset, int count, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var (i, startPos) = GetChunkIndex(position);
|
var (i, startPos) = GetChunkIndex(position);
|
||||||
if (i == -1) return 0;
|
if (i == -1) return 0;
|
||||||
|
|
@ -248,11 +248,6 @@ public class VPakStream : Stream, IRandomAccessStream, ICloneable
|
||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<int> ReadAtAsync(long position, Memory<byte> memory, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
|
private async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
|
||||||
{
|
{
|
||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
|
|
@ -262,7 +257,7 @@ public class VPakStream : Stream, IRandomAccessStream, ICloneable
|
||||||
await s.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await s.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var chunk = _chunks[i++];
|
var chunk = _chunks[i++];
|
||||||
tasks.Add(PrefetchChunkAsync(chunk));
|
tasks.Add(PrefetchChunkAsync(_manifest, chunk, s, cancellationToken));
|
||||||
|
|
||||||
if (i == _chunks.Length) break;
|
if (i == _chunks.Length) break;
|
||||||
count -= chunk.Size - startPos;
|
count -= chunk.Size - startPos;
|
||||||
|
|
@ -271,11 +266,12 @@ public class VPakStream : Stream, IRandomAccessStream, ICloneable
|
||||||
|
|
||||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
s.Dispose();
|
s.Dispose();
|
||||||
|
return;
|
||||||
|
|
||||||
async Task PrefetchChunkAsync(VChunk chunk)
|
static async Task PrefetchChunkAsync(VManifest manifest, VChunk chunk, SemaphoreSlim semaphore, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
|
await manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
|
||||||
s.Release(); // This is intended
|
semaphore.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,12 @@ using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using CUE4Parse_Conversion.Textures.BC;
|
||||||
using CUE4Parse.Compression;
|
using CUE4Parse.Compression;
|
||||||
using CUE4Parse.Encryption.Aes;
|
using CUE4Parse.Encryption.Aes;
|
||||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||||
using CUE4Parse.UE4.VirtualFileSystem;
|
using CUE4Parse.UE4.VirtualFileSystem;
|
||||||
|
using FModel.Extensions;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
using FModel.Settings;
|
using FModel.Settings;
|
||||||
|
|
@ -42,6 +44,32 @@ public class ApplicationViewModel : ViewModel
|
||||||
private init => SetProperty(ref _status, value);
|
private init => SetProperty(ref _status, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<EAssetCategory> Categories { get; } = AssetCategoryExtensions.GetBaseCategories();
|
||||||
|
|
||||||
|
private bool _isAssetsExplorerVisible;
|
||||||
|
public bool IsAssetsExplorerVisible
|
||||||
|
{
|
||||||
|
get => _isAssetsExplorerVisible;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value && !UserSettings.Default.FeaturePreviewNewAssetExplorer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetProperty(ref _isAssetsExplorerVisible, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _selectedLeftTabIndex;
|
||||||
|
public int SelectedLeftTabIndex
|
||||||
|
{
|
||||||
|
get => _selectedLeftTabIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is < 0 or > 2) return;
|
||||||
|
SetProperty(ref _selectedLeftTabIndex, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this);
|
public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this);
|
||||||
private RightClickMenuCommand _rightClickMenuCommand;
|
private RightClickMenuCommand _rightClickMenuCommand;
|
||||||
public MenuCommand MenuCommand => _menuCommand ??= new MenuCommand(this);
|
public MenuCommand MenuCommand => _menuCommand ??= new MenuCommand(this);
|
||||||
|
|
@ -49,7 +77,7 @@ public class ApplicationViewModel : ViewModel
|
||||||
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
|
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
|
||||||
private CopyCommand _copyCommand;
|
private CopyCommand _copyCommand;
|
||||||
|
|
||||||
public string InitialWindowTitle => $"FModel ({Constants.APP_SHORT_COMMIT_ID})";
|
public string InitialWindowTitle => $"FModel ({Constants.APP_SHORT_COMMIT_ID} - {Constants.APP_BUILD_DATE:MMM d, yyyy})";
|
||||||
public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown";
|
public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown";
|
||||||
public string TitleExtra => $"({UserSettings.Default.CurrentDir.UeVersion}){(Build != EBuildKind.Release ? $" ({Build})" : "")}";
|
public string TitleExtra => $"({UserSettings.Default.CurrentDir.UeVersion}){(Build != EBuildKind.Release ? $" ({Build})" : "")}";
|
||||||
|
|
||||||
|
|
@ -195,32 +223,37 @@ public class ApplicationViewModel : ViewModel
|
||||||
public static async Task InitVgmStream()
|
public static async Task InitVgmStream()
|
||||||
{
|
{
|
||||||
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
|
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
|
||||||
if (File.Exists(vgmZipFilePath)) return;
|
var vgmFileInfo = new FileInfo(vgmZipFilePath);
|
||||||
|
|
||||||
await ApplicationService.ApiEndpointView.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
|
if (!vgmFileInfo.Exists || vgmFileInfo.LastWriteTimeUtc < DateTime.UtcNow.AddMonths(-4))
|
||||||
if (new FileInfo(vgmZipFilePath).Length > 0)
|
|
||||||
{
|
{
|
||||||
var zipDir = Path.GetDirectoryName(vgmZipFilePath)!;
|
await ApplicationService.ApiEndpointView.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
|
||||||
await using var zipFs = File.OpenRead(vgmZipFilePath);
|
vgmFileInfo.Refresh();
|
||||||
using var zip = new ZipArchive(zipFs, ZipArchiveMode.Read);
|
|
||||||
|
|
||||||
foreach (var entry in zip.Entries)
|
if (vgmFileInfo.Length > 0)
|
||||||
{
|
{
|
||||||
var entryPath = Path.Combine(zipDir, entry.FullName);
|
var zipDir = Path.GetDirectoryName(vgmZipFilePath)!;
|
||||||
await using var entryFs = File.Create(entryPath);
|
await using var zipFs = File.OpenRead(vgmZipFilePath);
|
||||||
await using var entryStream = entry.Open();
|
using var zip = new ZipArchive(zipFs, ZipArchiveMode.Read);
|
||||||
await entryStream.CopyToAsync(entryFs);
|
|
||||||
|
foreach (var entry in zip.Entries)
|
||||||
|
{
|
||||||
|
var entryPath = Path.Combine(zipDir, entry.FullName);
|
||||||
|
await using var entryFs = File.Create(entryPath);
|
||||||
|
await using var entryStream = entry.Open();
|
||||||
|
await entryStream.CopyToAsync(entryFs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download VgmStream", Constants.WHITE, true));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download VgmStream", Constants.WHITE, true));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task InitImGuiSettings(bool forceDownload)
|
public static async Task InitImGuiSettings(bool forceDownload)
|
||||||
{
|
{
|
||||||
var imgui = "imgui.ini";
|
const string imgui = "imgui.ini";
|
||||||
var imguiPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", imgui);
|
var imguiPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", imgui);
|
||||||
|
|
||||||
if (File.Exists(imgui)) File.Move(imgui, imguiPath, true);
|
if (File.Exists(imgui)) File.Move(imgui, imguiPath, true);
|
||||||
|
|
@ -233,29 +266,64 @@ public class ApplicationViewModel : ViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async ValueTask InitOodle()
|
public static async Task InitOodle()
|
||||||
{
|
{
|
||||||
var oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME);
|
if (File.Exists(OodleHelper.OODLE_DLL_NAME_OLD))
|
||||||
if (File.Exists(OodleHelper.OODLE_DLL_NAME))
|
|
||||||
{
|
{
|
||||||
File.Move(OodleHelper.OODLE_DLL_NAME, oodlePath, true);
|
try
|
||||||
|
{
|
||||||
|
File.Delete(OodleHelper.OODLE_DLL_NAME_OLD);
|
||||||
|
}
|
||||||
|
catch { /* ignored */}
|
||||||
}
|
}
|
||||||
else if (!File.Exists(oodlePath))
|
|
||||||
|
var oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME_OLD);
|
||||||
|
if (!File.Exists(oodlePath))
|
||||||
{
|
{
|
||||||
await OodleHelper.DownloadOodleDllAsync(oodlePath);
|
oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(oodlePath))
|
||||||
|
{
|
||||||
|
if (!await OodleHelper.DownloadOodleDllAsync(oodlePath))
|
||||||
|
{
|
||||||
|
FLogger.Append(ELog.Error, () => FLogger.Text("Failed to download Oodle", Constants.WHITE, true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OodleHelper.Initialize(oodlePath);
|
OodleHelper.Initialize(oodlePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async ValueTask InitZlib()
|
public static async Task InitZlib()
|
||||||
{
|
{
|
||||||
var zlibPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", ZlibHelper.DLL_NAME);
|
var zlibPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", ZlibHelper.DLL_NAME);
|
||||||
if (!File.Exists(zlibPath))
|
var zlibFileInfo = new FileInfo(zlibPath);
|
||||||
|
|
||||||
|
if (!zlibFileInfo.Exists || zlibFileInfo.LastWriteTimeUtc < DateTime.UtcNow.AddMonths(-4))
|
||||||
{
|
{
|
||||||
await ZlibHelper.DownloadDllAsync(zlibPath);
|
if (!await ZlibHelper.DownloadDllAsync(zlibPath))
|
||||||
|
{
|
||||||
|
FLogger.Append(ELog.Error, () => FLogger.Text("Failed to download Zlib-ng", Constants.WHITE, true));
|
||||||
|
if (!zlibFileInfo.Exists) return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ZlibHelper.Initialize(zlibPath);
|
ZlibHelper.Initialize(zlibPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task InitDetex()
|
||||||
|
{
|
||||||
|
var detexPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", DetexHelper.DLL_NAME);
|
||||||
|
if (File.Exists(DetexHelper.DLL_NAME))
|
||||||
|
{
|
||||||
|
File.Move(DetexHelper.DLL_NAME, detexPath, true);
|
||||||
|
}
|
||||||
|
else if (!File.Exists(detexPath))
|
||||||
|
{
|
||||||
|
await DetexHelper.LoadDllAsync(detexPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
DetexHelper.Initialize(detexPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using CUE4Parse.UE4.Versions;
|
using CUE4Parse.UE4.Versions;
|
||||||
using CUE4Parse.UE4.VirtualFileSystem;
|
using CUE4Parse.UE4.VirtualFileSystem;
|
||||||
|
using FModel.Extensions;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
|
|
||||||
|
|
@ -13,11 +17,11 @@ namespace FModel.ViewModels;
|
||||||
|
|
||||||
public class TreeItem : ViewModel
|
public class TreeItem : ViewModel
|
||||||
{
|
{
|
||||||
private string _header;
|
private readonly string _header;
|
||||||
public string Header
|
public string Header
|
||||||
{
|
{
|
||||||
get => _header;
|
get => _header;
|
||||||
private set => SetProperty(ref _header, value);
|
private init => SetProperty(ref _header, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isExpanded;
|
private bool _isExpanded;
|
||||||
|
|
@ -55,21 +59,139 @@ public class TreeItem : ViewModel
|
||||||
private set => SetProperty(ref _version, value);
|
private set => SetProperty(ref _version, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string PathAtThisPoint { get; }
|
private string _searchText = string.Empty;
|
||||||
public AssetsListViewModel AssetsList { get; }
|
public string SearchText
|
||||||
public RangeObservableCollection<TreeItem> Folders { get; }
|
{
|
||||||
public ICollectionView FoldersView { get; }
|
get => _searchText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _searchText, value))
|
||||||
|
{
|
||||||
|
RefreshFilters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public TreeItem(string header, string archive, string mountPoint, FPackageFileVersion version, string pathHere)
|
private EAssetCategory _selectedCategory = EAssetCategory.All;
|
||||||
|
public EAssetCategory SelectedCategory
|
||||||
|
{
|
||||||
|
get => _selectedCategory;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _selectedCategory, value))
|
||||||
|
_ = OnSelectedCategoryChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PathAtThisPoint { get; }
|
||||||
|
public AssetsListViewModel AssetsList { get; } = new();
|
||||||
|
public RangeObservableCollection<TreeItem> Folders { get; } = [];
|
||||||
|
|
||||||
|
private ICollectionView _foldersView;
|
||||||
|
public ICollectionView FoldersView
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
_foldersView ??= new ListCollectionView(Folders)
|
||||||
|
{
|
||||||
|
SortDescriptions = { new SortDescription(nameof(Header), ListSortDirection.Ascending) }
|
||||||
|
};
|
||||||
|
return _foldersView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ICollectionView? _filteredFoldersView;
|
||||||
|
public ICollectionView? FilteredFoldersView
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
_filteredFoldersView ??= new ListCollectionView(Folders)
|
||||||
|
{
|
||||||
|
SortDescriptions = { new SortDescription(nameof(Header), ListSortDirection.Ascending) },
|
||||||
|
Filter = e => ItemFilter(e, SearchText.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
};
|
||||||
|
return _filteredFoldersView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompositeCollection _combinedEntries;
|
||||||
|
public CompositeCollection CombinedEntries
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_combinedEntries == null)
|
||||||
|
{
|
||||||
|
void CreateCombinedEntries()
|
||||||
|
{
|
||||||
|
_combinedEntries = new CompositeCollection
|
||||||
|
{
|
||||||
|
new CollectionContainer { Collection = FilteredFoldersView },
|
||||||
|
new CollectionContainer { Collection = AssetsList.AssetsView }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Application.Current.Dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(CreateCombinedEntries);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CreateCombinedEntries();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _combinedEntries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeItem Parent { get; init; }
|
||||||
|
|
||||||
|
public TreeItem(string header, GameFile entry, string pathHere)
|
||||||
{
|
{
|
||||||
Header = header;
|
Header = header;
|
||||||
Archive = archive;
|
if (entry is VfsEntry vfsEntry)
|
||||||
MountPoint = mountPoint;
|
{
|
||||||
Version = version;
|
Archive = vfsEntry.Vfs.Name;
|
||||||
|
MountPoint = vfsEntry.Vfs.MountPoint;
|
||||||
|
Version = vfsEntry.Vfs.Ver;
|
||||||
|
}
|
||||||
PathAtThisPoint = pathHere;
|
PathAtThisPoint = pathHere;
|
||||||
AssetsList = new AssetsListViewModel();
|
|
||||||
Folders = new RangeObservableCollection<TreeItem>();
|
AssetsList.AssetsView.Filter = o => ItemFilter(o, SearchText.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
||||||
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
|
}
|
||||||
|
|
||||||
|
private void RefreshFilters()
|
||||||
|
{
|
||||||
|
AssetsList.AssetsView.Refresh();
|
||||||
|
FilteredFoldersView?.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ItemFilter(object item, IEnumerable<string> filters)
|
||||||
|
{
|
||||||
|
var f = filters.ToArray();
|
||||||
|
switch (item)
|
||||||
|
{
|
||||||
|
case GameFileViewModel entry:
|
||||||
|
{
|
||||||
|
bool matchesSearch = f.Length == 0 || f.All(x => entry.Asset.Name.Contains(x, StringComparison.OrdinalIgnoreCase));
|
||||||
|
bool matchesCategory = SelectedCategory == EAssetCategory.All || entry.AssetCategory.IsOfCategory(SelectedCategory);
|
||||||
|
|
||||||
|
return matchesSearch && matchesCategory;
|
||||||
|
}
|
||||||
|
case TreeItem folder:
|
||||||
|
{
|
||||||
|
bool matchesSearch = f.Length == 0 || f.All(x => folder.Header.Contains(x, StringComparison.OrdinalIgnoreCase));
|
||||||
|
bool matchesCategory = SelectedCategory == EAssetCategory.All;
|
||||||
|
|
||||||
|
return matchesSearch && matchesCategory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSelectedCategoryChanged()
|
||||||
|
{
|
||||||
|
await Task.WhenAll(AssetsList.Assets.Select(asset => asset.ResolveAsync(EResolveCompute.Category)));
|
||||||
|
RefreshFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"{Header} | {Folders.Count} Folders | {AssetsList.Assets.Count} Files";
|
public override string ToString() => $"{Header} | {Folders.Count} Folders | {AssetsList.Assets.Count} Files";
|
||||||
|
|
@ -82,11 +204,11 @@ public class AssetsFolderViewModel
|
||||||
|
|
||||||
public AssetsFolderViewModel()
|
public AssetsFolderViewModel()
|
||||||
{
|
{
|
||||||
Folders = new RangeObservableCollection<TreeItem>();
|
Folders = [];
|
||||||
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
|
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BulkPopulate(IReadOnlyCollection<VfsEntry> entries)
|
public void BulkPopulate(IReadOnlyCollection<GameFile> entries)
|
||||||
{
|
{
|
||||||
if (entries == null || entries.Count == 0)
|
if (entries == null || entries.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
@ -95,54 +217,59 @@ public class AssetsFolderViewModel
|
||||||
{
|
{
|
||||||
var treeItems = new RangeObservableCollection<TreeItem>();
|
var treeItems = new RangeObservableCollection<TreeItem>();
|
||||||
treeItems.SetSuppressionState(true);
|
treeItems.SetSuppressionState(true);
|
||||||
var items = new List<AssetItem>(entries.Count);
|
|
||||||
|
|
||||||
foreach (var entry in entries)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
var item = new AssetItem(entry.Path, entry.IsEncrypted, entry.Offset, entry.Size, entry.Vfs.Name, entry.CompressionMethod);
|
TreeItem lastNode = null;
|
||||||
items.Add(item);
|
TreeItem parentItem = null;
|
||||||
|
var folders = entry.Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var builder = new StringBuilder(64);
|
||||||
|
var parentNode = treeItems;
|
||||||
|
|
||||||
|
for (var i = 0; i < folders.Length - 1; i++)
|
||||||
{
|
{
|
||||||
TreeItem lastNode = null;
|
var folder = folders[i];
|
||||||
var folders = item.FullPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
builder.Append(folder).Append('/');
|
||||||
var builder = new StringBuilder(64);
|
lastNode = FindByHeaderOrNull(parentNode, folder);
|
||||||
var parentNode = treeItems;
|
|
||||||
|
|
||||||
for (var i = 0; i < folders.Length - 1; i++)
|
static TreeItem FindByHeaderOrNull(IReadOnlyList<TreeItem> list, string header)
|
||||||
{
|
{
|
||||||
var folder = folders[i];
|
for (var i = 0; i < list.Count; 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++)
|
if (list[i].Header == header)
|
||||||
{
|
return list[i];
|
||||||
if (list[i].Header == header)
|
|
||||||
return list[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastNode == null)
|
return null;
|
||||||
{
|
|
||||||
var nodePath = builder.ToString();
|
|
||||||
lastNode = new TreeItem(folder, item.Archive, entry.Vfs.MountPoint, entry.Vfs.Ver, nodePath[..^1]);
|
|
||||||
lastNode.Folders.SetSuppressionState(true);
|
|
||||||
lastNode.AssetsList.Assets.SetSuppressionState(true);
|
|
||||||
parentNode.Add(lastNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
parentNode = lastNode.Folders;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastNode?.AssetsList.Assets.Add(item);
|
if (lastNode == null)
|
||||||
|
{
|
||||||
|
var nodePath = builder.ToString();
|
||||||
|
lastNode = new TreeItem(folder, entry, nodePath[..^1])
|
||||||
|
{
|
||||||
|
Parent = parentItem
|
||||||
|
};
|
||||||
|
lastNode.Folders.SetSuppressionState(true);
|
||||||
|
lastNode.AssetsList.Assets.SetSuppressionState(true);
|
||||||
|
parentNode.Add(lastNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
parentItem = lastNode;
|
||||||
|
parentNode = lastNode.Folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastNode?.AssetsList.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (treeItems.Count > 0)
|
||||||
|
{
|
||||||
|
var projectName = ApplicationService.ApplicationView.CUE4Parse.Provider.ProjectName;
|
||||||
|
(treeItems.FirstOrDefault(x => x.Header.Equals(projectName, StringComparison.OrdinalIgnoreCase)) ?? treeItems[0]).IsSelected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Folders.AddRange(treeItems);
|
Folders.AddRange(treeItems);
|
||||||
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(items);
|
ApplicationService.ApplicationView.CUE4Parse.SearchVm.ChangeCollection(entries);
|
||||||
|
|
||||||
foreach (var folder in Folders)
|
foreach (var folder in Folders)
|
||||||
InvokeOnCollectionChanged(folder);
|
InvokeOnCollectionChanged(folder);
|
||||||
|
|
@ -154,7 +281,6 @@ public class AssetsFolderViewModel
|
||||||
|
|
||||||
if (item.Folders.Count != 0)
|
if (item.Folders.Count != 0)
|
||||||
{
|
{
|
||||||
item.Folders.SetSuppressionState(false);
|
|
||||||
item.Folders.InvokeOnCollectionChanged();
|
item.Folders.InvokeOnCollectionChanged();
|
||||||
|
|
||||||
foreach (var folderItem in item.Folders)
|
foreach (var folderItem in item.Folders)
|
||||||
|
|
|
||||||
|
|
@ -1,78 +1,26 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using CUE4Parse.Compression;
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
|
|
||||||
namespace FModel.ViewModels;
|
namespace FModel.ViewModels;
|
||||||
|
|
||||||
public class AssetItem : ViewModel
|
|
||||||
{
|
|
||||||
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 _archive;
|
|
||||||
public string Archive
|
|
||||||
{
|
|
||||||
get => _archive;
|
|
||||||
private set => SetProperty(ref _archive, 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 archive, CompressionMethod compression)
|
|
||||||
{
|
|
||||||
FullPath = fullPath;
|
|
||||||
IsEncrypted = isEncrypted;
|
|
||||||
Offset = offset;
|
|
||||||
Size = size;
|
|
||||||
Archive = archive;
|
|
||||||
Compression = compression;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => FullPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AssetsListViewModel
|
public class AssetsListViewModel
|
||||||
{
|
{
|
||||||
public RangeObservableCollection<AssetItem> Assets { get; }
|
public RangeObservableCollection<GameFileViewModel> Assets { get; } = [];
|
||||||
public ICollectionView AssetsView { get; }
|
|
||||||
|
|
||||||
public AssetsListViewModel()
|
private ICollectionView _assetsView;
|
||||||
|
public ICollectionView AssetsView
|
||||||
{
|
{
|
||||||
Assets = new RangeObservableCollection<AssetItem>();
|
get
|
||||||
AssetsView = new ListCollectionView(Assets)
|
|
||||||
{
|
{
|
||||||
SortDescriptions = { new SortDescription("FullPath", ListSortDirection.Ascending) }
|
_assetsView ??= new ListCollectionView(Assets)
|
||||||
};
|
{
|
||||||
|
SortDescriptions = { new SortDescription("Asset.Path", ListSortDirection.Ascending) }
|
||||||
|
};
|
||||||
|
return _assetsView;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Add(GameFile gameFile) => Assets.Add(new GameFileViewModel(gameFile));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
using CSCore;
|
|
||||||
using CSCore.DSP;
|
|
||||||
using CSCore.SoundOut;
|
|
||||||
using CSCore.Streams;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
@ -12,7 +8,15 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
using CSCore;
|
||||||
using CSCore.CoreAudioAPI;
|
using CSCore.CoreAudioAPI;
|
||||||
|
using CSCore.DSP;
|
||||||
|
using CSCore.SoundOut;
|
||||||
|
using CSCore.Streams;
|
||||||
|
using CUE4Parse.UE4.CriWare.Decoders;
|
||||||
|
using CUE4Parse.UE4.CriWare.Decoders.ADX;
|
||||||
|
using CUE4Parse.UE4.CriWare.Decoders.HCA;
|
||||||
|
using CUE4Parse.Utils;
|
||||||
using FModel.Extensions;
|
using FModel.Extensions;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
|
|
@ -293,6 +297,14 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
||||||
{
|
{
|
||||||
Save(a, true);
|
Save(a, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FLogger.Append(ELog.Information, () =>
|
||||||
|
{
|
||||||
|
FLogger.Text("Successfully saved audio from ", Constants.WHITE);
|
||||||
|
FLogger.Link(_audioFiles.First().FileName, _audioFiles.First().FilePath, true);
|
||||||
|
});
|
||||||
|
if (_audioFiles.Count > 1)
|
||||||
|
FLogger.Append(ELog.Information, () => FLogger.Text($"Successfully saved {_audioFiles.Count} audio files", Constants.WHITE, true));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -328,16 +340,22 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
Log.Information("{FileName} successfully saved", fileToSave.FileName);
|
Log.Information("{FileName} successfully saved", fileToSave.FileName);
|
||||||
FLogger.Append(ELog.Information, () =>
|
if (!auto)
|
||||||
{
|
{
|
||||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
FLogger.Append(ELog.Information, () =>
|
||||||
FLogger.Link(fileToSave.FileName, path, true);
|
{
|
||||||
});
|
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||||
|
FLogger.Link(fileToSave.FileName, path, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Error("{FileName} could not be saved", fileToSave.FileName);
|
Log.Error("{FileName} could not be saved", fileToSave.FileName);
|
||||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{fileToSave.FileName}'", Constants.WHITE, true));
|
if (!auto)
|
||||||
|
{
|
||||||
|
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{fileToSave.FileName}'", Constants.WHITE, true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -548,7 +566,9 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
||||||
|
|
||||||
switch (SelectedAudioFile.Extension)
|
switch (SelectedAudioFile.Extension)
|
||||||
{
|
{
|
||||||
|
case "binka":
|
||||||
case "adpcm":
|
case "adpcm":
|
||||||
|
case "xvag":
|
||||||
case "opus":
|
case "opus":
|
||||||
case "wem":
|
case "wem":
|
||||||
case "at9":
|
case "at9":
|
||||||
|
|
@ -563,9 +583,12 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case "binka":
|
case "adx":
|
||||||
|
case "hca":
|
||||||
|
return TryConvertCriware();
|
||||||
|
case "rada":
|
||||||
{
|
{
|
||||||
if (TryDecode(out var rawFilePath))
|
if (TryDecode(SelectedAudioFile.Extension, out var rawFilePath))
|
||||||
{
|
{
|
||||||
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(rawFilePath));
|
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(rawFilePath));
|
||||||
Replace(newAudio);
|
Replace(newAudio);
|
||||||
|
|
@ -579,6 +602,48 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryConvertCriware()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] wavData = SelectedAudioFile.Extension switch
|
||||||
|
{
|
||||||
|
"hca" => HcaWaveStream.ConvertHcaToWav(
|
||||||
|
SelectedAudioFile.Data,
|
||||||
|
UserSettings.Default.CurrentDir.CriwareDecryptionKey),
|
||||||
|
"adx" => AdxDecoder.ConvertAdxToWav(
|
||||||
|
SelectedAudioFile.Data,
|
||||||
|
UserSettings.Default.CurrentDir.CriwareDecryptionKey),
|
||||||
|
_ => throw new NotSupportedException()
|
||||||
|
};
|
||||||
|
|
||||||
|
string wavFilePath = Path.Combine(
|
||||||
|
UserSettings.Default.AudioDirectory,
|
||||||
|
SelectedAudioFile.FilePath.TrimStart('/'));
|
||||||
|
wavFilePath = Path.ChangeExtension(wavFilePath, ".wav");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(wavFilePath)!);
|
||||||
|
File.WriteAllBytes(wavFilePath, wavData);
|
||||||
|
|
||||||
|
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath));
|
||||||
|
Replace(newAudio);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (CriwareDecryptionException ex)
|
||||||
|
{
|
||||||
|
FLogger.Append(ELog.Error, () => FLogger.Text($"Encrypted {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}", Constants.WHITE, true));
|
||||||
|
Log.Error($"Encrypted {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
FLogger.Append(ELog.Error, () => FLogger.Text($"Failed to convert {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}", Constants.WHITE, true));
|
||||||
|
Log.Error($"Failed to convert {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath);
|
private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath);
|
||||||
private bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath)
|
private bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath)
|
||||||
{
|
{
|
||||||
|
|
@ -607,11 +672,11 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
||||||
return vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath);
|
return vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryDecode(out string rawFilePath)
|
private bool TryDecode(string extension, out string rawFilePath)
|
||||||
{
|
{
|
||||||
rawFilePath = string.Empty;
|
rawFilePath = string.Empty;
|
||||||
var binkadecPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "binkadec.exe");
|
var decoderPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{extension}dec.exe");
|
||||||
if (!File.Exists(binkadecPath))
|
if (!File.Exists(decoderPath))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -620,16 +685,16 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
||||||
File.WriteAllBytes(SelectedAudioFile.FilePath, SelectedAudioFile.Data);
|
File.WriteAllBytes(SelectedAudioFile.FilePath, SelectedAudioFile.Data);
|
||||||
|
|
||||||
rawFilePath = Path.ChangeExtension(SelectedAudioFile.FilePath, ".wav");
|
rawFilePath = Path.ChangeExtension(SelectedAudioFile.FilePath, ".wav");
|
||||||
var binkadecProcess = Process.Start(new ProcessStartInfo
|
var decoderProcess = Process.Start(new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = binkadecPath,
|
FileName = decoderPath,
|
||||||
Arguments = $"-i \"{SelectedAudioFile.FilePath}\" -o \"{rawFilePath}\"",
|
Arguments = $"-i \"{SelectedAudioFile.FilePath}\" -o \"{rawFilePath}\"",
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true
|
CreateNoWindow = true
|
||||||
});
|
});
|
||||||
binkadecProcess?.WaitForExit(5000);
|
decoderProcess?.WaitForExit(5000);
|
||||||
|
|
||||||
File.Delete(SelectedAudioFile.FilePath);
|
File.Delete(SelectedAudioFile.FilePath);
|
||||||
return binkadecProcess?.ExitCode == 0 && File.Exists(rawFilePath);
|
return decoderProcess?.ExitCode == 0 && File.Exists(rawFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using CUE4Parse.FileProvider.Objects;
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using CUE4Parse.UE4.VirtualFileSystem;
|
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
using FModel.Settings;
|
using FModel.Settings;
|
||||||
|
|
@ -67,7 +66,7 @@ public class BackupManagerViewModel : ViewModel
|
||||||
var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups");
|
var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups");
|
||||||
var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp";
|
var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp";
|
||||||
var fullPath = Path.Combine(backupFolder, fileName);
|
var fullPath = Path.Combine(backupFolder, fileName);
|
||||||
var func = new Func<GameFile, bool>(x => !x.Path.EndsWith(".uexp") && !x.Path.EndsWith(".ubulk") && !x.Path.EndsWith(".uptnl"));
|
var func = new Func<GameFile, bool>(x => !x.IsUePackagePayload);
|
||||||
|
|
||||||
using var fileStream = new FileStream(fullPath, FileMode.Create);
|
using var fileStream = new FileStream(fullPath, FileMode.Create);
|
||||||
using var compressedStream = LZ4Stream.Encode(fileStream, LZ4Level.L00_FAST);
|
using var compressedStream = LZ4Stream.Encode(fileStream, LZ4Level.L00_FAST);
|
||||||
|
|
@ -81,7 +80,7 @@ public class BackupManagerViewModel : ViewModel
|
||||||
if (!func(asset)) continue;
|
if (!func(asset)) continue;
|
||||||
writer.Write(asset.Size);
|
writer.Write(asset.Size);
|
||||||
writer.Write(asset.IsEncrypted);
|
writer.Write(asset.IsEncrypted);
|
||||||
writer.Write($"/{asset.Path.ToLower()}");
|
writer.Write(asset.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveCheck(fullPath, fileName, "created", "create");
|
SaveCheck(fullPath, fileName, "created", "create");
|
||||||
|
|
@ -122,6 +121,7 @@ public enum EBackupVersion : byte
|
||||||
{
|
{
|
||||||
BeforeVersionWasAdded = 0,
|
BeforeVersionWasAdded = 0,
|
||||||
Initial,
|
Initial,
|
||||||
|
PerfectPath, // no more leading slash and ToLower
|
||||||
|
|
||||||
LatestPlusOne,
|
LatestPlusOne,
|
||||||
Latest = LatestPlusOne - 1
|
Latest = LatestPlusOne - 1
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using FModel.Extensions;
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
|
|
||||||
namespace FModel.ViewModels.Commands;
|
namespace FModel.ViewModels.Commands;
|
||||||
|
|
@ -18,29 +19,37 @@ public class CopyCommand : ViewModelCommand<ApplicationViewModel>
|
||||||
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
|
var entries = (parameters[1] as IEnumerable)?.OfType<object>()
|
||||||
if (!assetItems.Any()) return;
|
.SelectMany(item => item switch
|
||||||
|
{
|
||||||
|
GameFile gf => new[] { gf },
|
||||||
|
GameFileViewModel gvm => new[] { gvm.Asset },
|
||||||
|
_ => []
|
||||||
|
}) ?? [];
|
||||||
|
|
||||||
|
if (!entries.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
switch (trigger)
|
switch (trigger)
|
||||||
{
|
{
|
||||||
case "File_Path":
|
case "File_Path":
|
||||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath);
|
foreach (var entry in entries) sb.AppendLine(entry.Path);
|
||||||
break;
|
break;
|
||||||
case "File_Name":
|
case "File_Name":
|
||||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/'));
|
foreach (var entry in entries) sb.AppendLine(entry.Name);
|
||||||
break;
|
break;
|
||||||
case "Directory_Path":
|
case "Directory_Path":
|
||||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('/'));
|
foreach (var entry in entries) sb.AppendLine(entry.Directory);
|
||||||
break;
|
break;
|
||||||
case "File_Path_No_Extension":
|
case "File_Path_No_Extension":
|
||||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('.'));
|
foreach (var entry in entries) sb.AppendLine(entry.PathWithoutExtension);
|
||||||
break;
|
break;
|
||||||
case "File_Name_No_Extension":
|
case "File_Name_No_Extension":
|
||||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/').SubstringBeforeLast('.'));
|
foreach (var entry in entries) sb.AppendLine(entry.NameWithoutExtension);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Clipboard.SetText(sb.ToString().TrimEnd());
|
Clipboard.SetText(sb.ToString().TrimEnd());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public class GoToCommand : ViewModelCommand<CustomDirectoriesViewModel>
|
||||||
|
|
||||||
public TreeItem JumpTo(string directory)
|
public TreeItem JumpTo(string directory)
|
||||||
{
|
{
|
||||||
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
|
_applicationView.SelectedLeftTabIndex = 1; // folders tab
|
||||||
var root = _applicationView.CUE4Parse.AssetsFolder.Folders;
|
var root = _applicationView.CUE4Parse.AssetsFolder.Folders;
|
||||||
if (root is not { Count: > 0 }) return null;
|
if (root is not { Count: > 0 }) return null;
|
||||||
|
|
||||||
|
|
@ -54,4 +54,4 @@ public class GoToCommand : ViewModelCommand<CustomDirectoriesViewModel>
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,34 +14,34 @@ public class ImageCommand : ViewModelCommand<TabItem>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Execute(TabItem contextViewModel, object parameter)
|
public override void Execute(TabItem tabViewModel, object parameter)
|
||||||
{
|
{
|
||||||
if (parameter == null || !contextViewModel.HasImage) return;
|
if (parameter == null || !tabViewModel.HasImage) return;
|
||||||
|
|
||||||
switch (parameter)
|
switch (parameter)
|
||||||
{
|
{
|
||||||
case "Open":
|
case "Open":
|
||||||
{
|
{
|
||||||
Helper.OpenWindow<AdonisWindow>(contextViewModel.SelectedImage.ExportName + " (Image)", () =>
|
Helper.OpenWindow<AdonisWindow>(tabViewModel.SelectedImage.ExportName + " (Image)", () =>
|
||||||
{
|
{
|
||||||
var popout = new ImagePopout
|
var popout = new ImagePopout
|
||||||
{
|
{
|
||||||
Title = contextViewModel.SelectedImage.ExportName + " (Image)",
|
Title = tabViewModel.SelectedImage.ExportName + " (Image)",
|
||||||
Width = contextViewModel.SelectedImage.Image.Width,
|
Width = tabViewModel.SelectedImage.Image.Width,
|
||||||
Height = contextViewModel.SelectedImage.Image.Height,
|
Height = tabViewModel.SelectedImage.Image.Height,
|
||||||
WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
|
WindowState = tabViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
|
||||||
ImageCtrl = { Source = contextViewModel.SelectedImage.Image }
|
ImageCtrl = { Source = tabViewModel.SelectedImage.Image }
|
||||||
};
|
};
|
||||||
RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor));
|
RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(tabViewModel.SelectedImage.RenderNearestNeighbor));
|
||||||
popout.Show();
|
popout.Show();
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Copy":
|
case "Copy":
|
||||||
ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png");
|
ClipboardExtensions.SetImage(tabViewModel.SelectedImage.ImageBuffer, $"{tabViewModel.SelectedImage.ExportName}.png");
|
||||||
break;
|
break;
|
||||||
case "Save":
|
case "Save":
|
||||||
contextViewModel.SaveImage();
|
tabViewModel.SaveImage();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AdonisUI.Controls;
|
using AdonisUI.Controls;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using CUE4Parse.UE4.Readers;
|
using CUE4Parse.UE4.Readers;
|
||||||
using CUE4Parse.UE4.VirtualFileSystem;
|
using CUE4Parse.UE4.VirtualFileSystem;
|
||||||
using FModel.Creator;
|
using CUE4Parse.Utils;
|
||||||
using FModel.Extensions;
|
using FModel.Extensions;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
|
|
@ -37,21 +38,26 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
|
|
||||||
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
|
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
|
||||||
{
|
{
|
||||||
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
|
|
||||||
if (_applicationView.CUE4Parse.Provider.Keys.Count == 0 && _applicationView.CUE4Parse.Provider.RequiredKeys.Count > 0)
|
if (_applicationView.CUE4Parse.Provider.Keys.Count == 0 && _applicationView.CUE4Parse.Provider.RequiredKeys.Count > 0)
|
||||||
{
|
{
|
||||||
FLogger.Append(ELog.Error, () =>
|
FLogger.Append(ELog.Error, () =>
|
||||||
FLogger.Text("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true));
|
FLogger.Text("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (_applicationView.CUE4Parse.Provider.Files.Count == 0)
|
||||||
|
{
|
||||||
|
FLogger.Append(ELog.Error, () => FLogger.Text("No files were found in the archives or the specified directory", Constants.WHITE, true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
var loadingTime = Stopwatch.StartNew();
|
var loadingTime = Stopwatch.StartNew();
|
||||||
#endif
|
#endif
|
||||||
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
|
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
|
||||||
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
|
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
|
||||||
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
|
_applicationView.SelectedLeftTabIndex = 1; // folders tab
|
||||||
Helper.CloseWindow<AdonisWindow>("Search View"); // close search window if opened
|
_applicationView.IsAssetsExplorerVisible = true;
|
||||||
|
Helper.CloseWindow<AdonisWindow>("Search For Packages"); // close search window if opened
|
||||||
|
|
||||||
await Task.WhenAll(
|
await Task.WhenAll(
|
||||||
_applicationView.CUE4Parse.LoadLocalizedResources(), // load locres if not already loaded,
|
_applicationView.CUE4Parse.LoadLocalizedResources(), // load locres if not already loaded,
|
||||||
|
|
@ -59,12 +65,17 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
_threadWorkerView.Begin(cancellationToken =>
|
_threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
// filter what to show
|
// filter what to show
|
||||||
|
_applicationView.Status.UpdateStatusLabel("Packages", "Filtering");
|
||||||
switch (UserSettings.Default.LoadingMode)
|
switch (UserSettings.Default.LoadingMode)
|
||||||
{
|
{
|
||||||
case ELoadingMode.Multiple:
|
case ELoadingMode.Multiple:
|
||||||
{
|
{
|
||||||
var l = (IList) parameter;
|
var l = (IList) parameter;
|
||||||
if (l.Count < 1) return;
|
if (l.Count == 0)
|
||||||
|
{
|
||||||
|
UserSettings.Default.LoadingMode = ELoadingMode.All;
|
||||||
|
goto case ELoadingMode.All;
|
||||||
|
}
|
||||||
|
|
||||||
var directoryFilesToShow = l.Cast<FileItem>();
|
var directoryFilesToShow = l.Cast<FileItem>();
|
||||||
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);
|
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);
|
||||||
|
|
@ -81,6 +92,11 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
FilterNewOrModifiedFilesToDisplay(cancellationToken);
|
FilterNewOrModifiedFilesToDisplay(cancellationToken);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ELoadingMode.AllButPatched:
|
||||||
|
{
|
||||||
|
FilterPacthedFilesToDisplay(cancellationToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: throw new ArgumentOutOfRangeException();
|
default: throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,42 +116,36 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
if (directoryFiles == null) filter = null;
|
if (directoryFiles == null) filter = null;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
filter = new HashSet<string>();
|
filter = [];
|
||||||
foreach (var directoryFile in directoryFiles)
|
foreach (var directoryFile in directoryFiles)
|
||||||
{
|
{
|
||||||
if (!directoryFile.IsEnabled)
|
if (!directoryFile.IsEnabled) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
filter.Add(directoryFile.Name);
|
filter.Add(directoryFile.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasFilter = filter != null && filter.Count != 0;
|
var hasFilter = filter != null && filter.Count != 0;
|
||||||
var entries = new List<VfsEntry>();
|
var entries = new List<GameFile>();
|
||||||
|
|
||||||
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
|
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
|
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
|
||||||
|
if (asset.IsUePackagePayload) continue;
|
||||||
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (hasFilter)
|
if (hasFilter)
|
||||||
{
|
{
|
||||||
if (filter.Contains(entry.Vfs.Name))
|
if (asset is VfsEntry entry && filter.Contains(entry.Vfs.Name))
|
||||||
{
|
{
|
||||||
entries.Add(entry);
|
entries.Add(asset);
|
||||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
entries.Add(entry);
|
entries.Add(asset);
|
||||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_applicationView.Status.UpdateStatusLabel("Folders & Packages");
|
_applicationView.Status.UpdateStatusLabel($"{entries.Count:### ### ###} Packages");
|
||||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,11 +167,11 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
var mode = UserSettings.Default.LoadingMode;
|
var mode = UserSettings.Default.LoadingMode;
|
||||||
var entries = ParseBackup(openFileDialog.FileName, mode, cancellationToken);
|
var entries = ParseBackup(openFileDialog.FileName, mode, cancellationToken);
|
||||||
|
|
||||||
_applicationView.Status.UpdateStatusLabel($"{mode.ToString()[6..]} Folders & Packages");
|
_applicationView.Status.UpdateStatusLabel($"{entries.Count:### ### ###} Packages");
|
||||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<VfsEntry> ParseBackup(string path, ELoadingMode mode, CancellationToken cancellationToken = default)
|
private List<GameFile> ParseBackup(string path, ELoadingMode mode, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
using var fileStream = new FileStream(path, FileMode.Open);
|
using var fileStream = new FileStream(path, FileMode.Open);
|
||||||
using var memoryStream = new MemoryStream();
|
using var memoryStream = new MemoryStream();
|
||||||
|
|
@ -176,13 +186,13 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
|
|
||||||
memoryStream.Position = 0;
|
memoryStream.Position = 0;
|
||||||
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
|
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
|
||||||
var entries = new List<VfsEntry>();
|
var entries = new List<GameFile>();
|
||||||
|
|
||||||
switch (mode)
|
switch (mode)
|
||||||
{
|
{
|
||||||
case ELoadingMode.AllButNew:
|
case ELoadingMode.AllButNew:
|
||||||
{
|
{
|
||||||
var paths = new HashSet<string>();
|
var paths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
var magic = archive.Read<uint>();
|
var magic = archive.Read<uint>();
|
||||||
if (magic != BackupManagerViewModel.FBKP_MAGIC)
|
if (magic != BackupManagerViewModel.FBKP_MAGIC)
|
||||||
{
|
{
|
||||||
|
|
@ -192,7 +202,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
archive.Position += 29;
|
archive.Position += 29;
|
||||||
paths.Add(archive.ReadString().ToLower()[1..]);
|
paths.Add(archive.ReadString()[1..]);
|
||||||
archive.Position += 4;
|
archive.Position += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,18 +215,19 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
archive.Position += sizeof(long) + sizeof(byte);
|
archive.Position += sizeof(long) + sizeof(byte);
|
||||||
paths.Add(archive.ReadString().ToLower()[1..]);
|
var fullPath = archive.ReadString();
|
||||||
|
if (version < EBackupVersion.PerfectPath) fullPath = fullPath[1..];
|
||||||
|
|
||||||
|
paths.Add(fullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
|
foreach (var (key, asset) in _applicationView.CUE4Parse.Provider.Files)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
if (value is not VfsEntry entry || paths.Contains(key) || entry.Path.EndsWith(".uexp") ||
|
if (asset.IsUePackagePayload || paths.Contains(key)) continue;
|
||||||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
|
|
||||||
|
|
||||||
entries.Add(entry);
|
entries.Add(asset);
|
||||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -235,7 +246,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
var uncompressedSize = archive.Read<long>();
|
var uncompressedSize = archive.Read<long>();
|
||||||
var isEncrypted = archive.ReadFlag();
|
var isEncrypted = archive.ReadFlag();
|
||||||
archive.Position += 4;
|
archive.Position += 4;
|
||||||
var fullPath = archive.ReadString().ToLower()[1..];
|
var fullPath = archive.ReadString()[1..];
|
||||||
archive.Position += 4;
|
archive.Position += 4;
|
||||||
|
|
||||||
AddEntry(fullPath, uncompressedSize, isEncrypted, entries);
|
AddEntry(fullPath, uncompressedSize, isEncrypted, entries);
|
||||||
|
|
@ -251,7 +262,8 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
|
|
||||||
var uncompressedSize = archive.Read<long>();
|
var uncompressedSize = archive.Read<long>();
|
||||||
var isEncrypted = archive.ReadFlag();
|
var isEncrypted = archive.ReadFlag();
|
||||||
var fullPath = archive.ReadString().ToLower()[1..];
|
var fullPath = archive.ReadString();
|
||||||
|
if (version < EBackupVersion.PerfectPath) fullPath = fullPath[1..];
|
||||||
|
|
||||||
AddEntry(fullPath, uncompressedSize, isEncrypted, entries);
|
AddEntry(fullPath, uncompressedSize, isEncrypted, entries);
|
||||||
}
|
}
|
||||||
|
|
@ -263,14 +275,34 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddEntry(string path, long uncompressedSize, bool isEncrypted, List<VfsEntry> entries)
|
private void AddEntry(string path, long uncompressedSize, bool isEncrypted, List<GameFile> entries)
|
||||||
{
|
{
|
||||||
if (path.EndsWith(".uexp") || path.EndsWith(".ubulk") || path.EndsWith(".uptnl") ||
|
if (!_applicationView.CUE4Parse.Provider.Files.TryGetValue(path, out var asset) ||
|
||||||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(path, out var asset) || asset is not VfsEntry entry ||
|
asset.IsUePackagePayload || asset.Size == uncompressedSize && asset.IsEncrypted == isEncrypted)
|
||||||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
entries.Add(entry);
|
entries.Add(asset);
|
||||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
}
|
||||||
|
|
||||||
|
private void FilterPacthedFilesToDisplay(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var loaded = new Dictionary<string, GameFile>(_applicationView.CUE4Parse.Provider.PathComparer);
|
||||||
|
|
||||||
|
foreach (var (key, asset) in _applicationView.CUE4Parse.Provider.Files)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
|
||||||
|
if (asset.IsUePackagePayload) continue;
|
||||||
|
|
||||||
|
if (asset is VfsEntry entry && loaded.TryGetValue(key, out var file) &&
|
||||||
|
file is VfsEntry existingEntry && entry.Vfs.ReadOrder < existingEntry.Vfs.ReadOrder)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded[key] = asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
_applicationView.Status.UpdateStatusLabel($"{loaded.Count:### ### ###} Packages");
|
||||||
|
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(loaded.Values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,10 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
||||||
Helper.OpenWindow<AdonisWindow>("AES Manager", () => new AesManager().Show());
|
Helper.OpenWindow<AdonisWindow>("AES Manager", () => new AesManager().Show());
|
||||||
break;
|
break;
|
||||||
case "Directory_Backup":
|
case "Directory_Backup":
|
||||||
Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.InternalGameName).Show());
|
Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.ProjectName).Show());
|
||||||
break;
|
break;
|
||||||
case "Directory_ArchivesInfo":
|
case "Directory_ArchivesInfo":
|
||||||
|
ApplicationService.ApplicationView.IsAssetsExplorerVisible = false;
|
||||||
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
|
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
|
||||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
|
contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
|
||||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false, false);
|
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false, false);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
|
using FModel.Settings;
|
||||||
|
using FModel.Views.Resources.Controls;
|
||||||
|
|
||||||
namespace FModel.ViewModels.Commands;
|
namespace FModel.ViewModels.Commands;
|
||||||
|
|
||||||
|
|
@ -19,62 +22,192 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
||||||
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
|
var param = (parameters[1] as IEnumerable)?.OfType<object>().ToArray() ?? [];
|
||||||
if (!assetItems.Any()) return;
|
if (param.Length == 0) return;
|
||||||
|
|
||||||
var updateUi = assetItems.Length > 1 ? EBulkType.Auto : EBulkType.None;
|
var folders = param.OfType<TreeItem>().ToArray();
|
||||||
|
var assets = param.SelectMany(item => item switch
|
||||||
|
{
|
||||||
|
GameFile gf => new[] { gf }, // search view passes GameFile directly
|
||||||
|
GameFileViewModel gvm => new[] { gvm.Asset },
|
||||||
|
_ => []
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
if (folders.Length == 0 && assets.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var updateUi = assets.Length > 1 ? EBulkType.Auto : EBulkType.None;
|
||||||
await _threadWorkerView.Begin(cancellationToken =>
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
switch (trigger)
|
switch (trigger)
|
||||||
{
|
{
|
||||||
|
#region Asset Commands
|
||||||
case "Assets_Extract_New_Tab":
|
case "Assets_Extract_New_Tab":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in assets)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, true);
|
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Assets_Show_Metadata":
|
||||||
|
foreach (var entry in assets)
|
||||||
|
{
|
||||||
|
Thread.Yield();
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
contextViewModel.CUE4Parse.ShowMetadata(entry);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Assets_Show_References":
|
||||||
|
{
|
||||||
|
Thread.Yield();
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
contextViewModel.CUE4Parse.FindReferences(assets.FirstOrDefault());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Assets_Decompile":
|
||||||
|
foreach (var entry in assets)
|
||||||
|
{
|
||||||
|
Thread.Yield();
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
contextViewModel.CUE4Parse.Decompile(entry);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Assets_Export_Data":
|
case "Assets_Export_Data":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in assets)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.ExportData(asset.FullPath);
|
contextViewModel.CUE4Parse.ExportData(entry);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Assets_Save_Properties":
|
case "Assets_Save_Properties":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in assets)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties | updateUi);
|
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Properties | updateUi);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Assets_Save_Textures":
|
case "Assets_Save_Textures":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in assets)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures | updateUi);
|
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Textures | updateUi);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Assets_Save_Models":
|
case "Assets_Save_Models":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in assets)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | updateUi);
|
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Meshes | updateUi);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Assets_Save_Animations":
|
case "Assets_Save_Animations":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in assets)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | updateUi);
|
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Animations | updateUi);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "Assets_Save_Audio":
|
||||||
|
foreach (var entry in assets)
|
||||||
|
{
|
||||||
|
Thread.Yield();
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Audio | updateUi);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Folder Commands
|
||||||
|
case "Folders_Export_Data":
|
||||||
|
foreach (var folder in folders)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
contextViewModel.CUE4Parse.ExportFolder(cancellationToken, folder);
|
||||||
|
|
||||||
|
FLogger.Append(ELog.Information, () =>
|
||||||
|
{
|
||||||
|
FLogger.Text("Successfully exported ", Constants.WHITE);
|
||||||
|
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.RawDataDirectory, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Folders_Save_Properties":
|
||||||
|
foreach (var folder in folders)
|
||||||
|
{
|
||||||
|
Thread.Yield();
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
contextViewModel.CUE4Parse.SaveFolder(cancellationToken, folder);
|
||||||
|
|
||||||
|
FLogger.Append(ELog.Information, () =>
|
||||||
|
{
|
||||||
|
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||||
|
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.PropertiesDirectory, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Folders_Save_Textures":
|
||||||
|
foreach (var folder in folders)
|
||||||
|
{
|
||||||
|
Thread.Yield();
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
contextViewModel.CUE4Parse.TextureFolder(cancellationToken, folder);
|
||||||
|
|
||||||
|
FLogger.Append(ELog.Information, () =>
|
||||||
|
{
|
||||||
|
FLogger.Text("Successfully saved textures from ", Constants.WHITE);
|
||||||
|
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.TextureDirectory, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Folders_Save_Models":
|
||||||
|
foreach (var folder in folders)
|
||||||
|
{
|
||||||
|
Thread.Yield();
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
contextViewModel.CUE4Parse.ModelFolder(cancellationToken, folder);
|
||||||
|
|
||||||
|
FLogger.Append(ELog.Information, () =>
|
||||||
|
{
|
||||||
|
FLogger.Text("Successfully saved models from ", Constants.WHITE);
|
||||||
|
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Folders_Save_Animations":
|
||||||
|
foreach (var folder in folders)
|
||||||
|
{
|
||||||
|
Thread.Yield();
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
contextViewModel.CUE4Parse.AnimationFolder(cancellationToken, folder);
|
||||||
|
|
||||||
|
FLogger.Append(ELog.Information, () =>
|
||||||
|
{
|
||||||
|
FLogger.Text("Successfully saved animations from ", Constants.WHITE);
|
||||||
|
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Folders_Save_Audio":
|
||||||
|
foreach (var folder in folders)
|
||||||
|
{
|
||||||
|
Thread.Yield();
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
contextViewModel.CUE4Parse.AudioFolder(cancellationToken, folder);
|
||||||
|
|
||||||
|
FLogger.Append(ELog.Information, () =>
|
||||||
|
{
|
||||||
|
FLogger.Text("Successfully saved audio from ", Constants.WHITE);
|
||||||
|
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.AudioDirectory, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using AdonisUI.Controls;
|
using AdonisUI.Controls;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
|
|
@ -15,7 +15,7 @@ public class TabCommand : ViewModelCommand<TabItem>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void Execute(TabItem contextViewModel, object parameter)
|
public override async void Execute(TabItem tabViewModel, object parameter)
|
||||||
{
|
{
|
||||||
switch (parameter)
|
switch (parameter)
|
||||||
{
|
{
|
||||||
|
|
@ -23,53 +23,62 @@ public class TabCommand : ViewModelCommand<TabItem>
|
||||||
_applicationView.CUE4Parse.TabControl.RemoveTab(mdlClick);
|
_applicationView.CUE4Parse.TabControl.RemoveTab(mdlClick);
|
||||||
break;
|
break;
|
||||||
case "Close_Tab":
|
case "Close_Tab":
|
||||||
_applicationView.CUE4Parse.TabControl.RemoveTab(contextViewModel);
|
_applicationView.CUE4Parse.TabControl.RemoveTab(tabViewModel);
|
||||||
break;
|
break;
|
||||||
case "Close_All_Tabs":
|
case "Close_All_Tabs":
|
||||||
_applicationView.CUE4Parse.TabControl.RemoveAllTabs();
|
_applicationView.CUE4Parse.TabControl.RemoveAllTabs();
|
||||||
break;
|
break;
|
||||||
case "Close_Other_Tabs":
|
case "Close_Other_Tabs":
|
||||||
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel);
|
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(tabViewModel);
|
||||||
|
break;
|
||||||
|
case "Find_References":
|
||||||
|
_applicationView.CUE4Parse.FindReferences(tabViewModel.Entry);
|
||||||
break;
|
break;
|
||||||
case "Asset_Export_Data":
|
case "Asset_Export_Data":
|
||||||
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(contextViewModel.FullPath));
|
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(tabViewModel.Entry));
|
||||||
break;
|
break;
|
||||||
case "Asset_Save_Properties":
|
case "Asset_Save_Properties":
|
||||||
await _threadWorkerView.Begin(cancellationToken =>
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.FullPath, false, EBulkType.Properties);
|
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Properties);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "Asset_Save_Textures":
|
case "Asset_Save_Textures":
|
||||||
await _threadWorkerView.Begin(cancellationToken =>
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.FullPath, false, EBulkType.Textures);
|
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Textures);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "Asset_Save_Models":
|
case "Asset_Save_Models":
|
||||||
await _threadWorkerView.Begin(cancellationToken =>
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.FullPath, false, EBulkType.Meshes | EBulkType.Auto);
|
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Meshes);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "Asset_Save_Animations":
|
case "Asset_Save_Animations":
|
||||||
await _threadWorkerView.Begin(cancellationToken =>
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.FullPath, false, EBulkType.Animations | EBulkType.Auto);
|
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Animations);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "Asset_Save_Audio":
|
||||||
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
|
{
|
||||||
|
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Audio);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "Open_Properties":
|
case "Open_Properties":
|
||||||
if (contextViewModel.Header == "New Tab" || contextViewModel.Document == null) return;
|
if (tabViewModel.Header == "New Tab" || tabViewModel.Document == null) return;
|
||||||
Helper.OpenWindow<AdonisWindow>(contextViewModel.Header + " (Properties)", () =>
|
Helper.OpenWindow<AdonisWindow>(tabViewModel.Header + " (Properties)", () =>
|
||||||
{
|
{
|
||||||
new PropertiesPopout(contextViewModel)
|
new PropertiesPopout(tabViewModel)
|
||||||
{
|
{
|
||||||
Title = contextViewModel.Header + " (Properties)"
|
Title = tabViewModel.Header + " (Properties)"
|
||||||
}.Show();
|
}.Show();
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "Copy_Asset_Path":
|
case "Copy_Asset_Path":
|
||||||
Clipboard.SetText(contextViewModel.FullPath);
|
Clipboard.SetText(tabViewModel.Entry.Path);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
using CUE4Parse.Compression;
|
||||||
using CUE4Parse.UE4.IO;
|
using CUE4Parse.UE4.IO;
|
||||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||||
using CUE4Parse.UE4.VirtualFileSystem;
|
using CUE4Parse.UE4.VirtualFileSystem;
|
||||||
|
|
@ -69,6 +70,13 @@ public class FileItem : ViewModel
|
||||||
set => SetProperty(ref _guid, value);
|
set => SetProperty(ref _guid, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CompressionMethod[] _compressionMethods;
|
||||||
|
public CompressionMethod[] CompressionMethods
|
||||||
|
{
|
||||||
|
get => _compressionMethods;
|
||||||
|
set => SetProperty(ref _compressionMethods, value);
|
||||||
|
}
|
||||||
|
|
||||||
public FileItem(string name, long length)
|
public FileItem(string name, long length)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
@ -84,6 +92,7 @@ public class FileItem : ViewModel
|
||||||
IsEnabled = false;
|
IsEnabled = false;
|
||||||
Key = string.Empty;
|
Key = string.Empty;
|
||||||
FileCount = reader is IoStoreReader storeReader ? (int) storeReader.TocResource.Header.TocEntryCount - 1 : 0;
|
FileCount = reader is IoStoreReader storeReader ? (int) storeReader.TocResource.Header.TocEntryCount - 1 : 0;
|
||||||
|
CompressionMethods = reader.CompressionMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
|
||||||
407
FModel/ViewModels/GameFileViewModel.cs
Normal file
407
FModel/ViewModels/GameFileViewModel.cs
Normal file
|
|
@ -0,0 +1,407 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
|
using CUE4Parse.GameTypes.FN.Assets.Exports.DataAssets;
|
||||||
|
using CUE4Parse.UE4.Assets;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.BuildData;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Component;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.CriWare;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Engine.Font;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Fmod;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Foliage;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Internationalization;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.LevelSequence;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Material.Editor;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Niagara;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Sound;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Wwise;
|
||||||
|
using CUE4Parse.UE4.Assets.Objects;
|
||||||
|
using CUE4Parse.UE4.Objects.Engine;
|
||||||
|
using CUE4Parse.UE4.Objects.Engine.Animation;
|
||||||
|
using CUE4Parse.UE4.Objects.Engine.Curves;
|
||||||
|
using CUE4Parse.UE4.Objects.MediaAssets;
|
||||||
|
using CUE4Parse.UE4.Objects.Niagara;
|
||||||
|
using CUE4Parse.UE4.Objects.PhysicsEngine;
|
||||||
|
using CUE4Parse.UE4.Objects.RigVM;
|
||||||
|
using CUE4Parse.UE4.Objects.UObject;
|
||||||
|
using CUE4Parse.UE4.Objects.UObject.Editor;
|
||||||
|
using CUE4Parse.Utils;
|
||||||
|
using CUE4Parse_Conversion.Textures;
|
||||||
|
using FModel.Framework;
|
||||||
|
using FModel.Services;
|
||||||
|
using FModel.Settings;
|
||||||
|
using Serilog;
|
||||||
|
using SkiaSharp;
|
||||||
|
using Svg.Skia;
|
||||||
|
|
||||||
|
namespace FModel.ViewModels;
|
||||||
|
|
||||||
|
public class GameFileViewModel(GameFile asset) : ViewModel
|
||||||
|
{
|
||||||
|
private const int MaxPreviewSize = 128;
|
||||||
|
|
||||||
|
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||||
|
|
||||||
|
public EResolveCompute Resolved { get; private set; } = EResolveCompute.None;
|
||||||
|
public GameFile Asset { get; } = asset;
|
||||||
|
|
||||||
|
private string _resolvedAssetType = asset.Extension;
|
||||||
|
public string ResolvedAssetType
|
||||||
|
{
|
||||||
|
get => _resolvedAssetType;
|
||||||
|
private set => SetProperty(ref _resolvedAssetType, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isSelected;
|
||||||
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get => _isSelected;
|
||||||
|
set => SetProperty(ref _isSelected, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EAssetCategory _assetCategory = EAssetCategory.All;
|
||||||
|
public EAssetCategory AssetCategory
|
||||||
|
{
|
||||||
|
get => _assetCategory;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
SetProperty(ref _assetCategory, value);
|
||||||
|
Resolved |= EResolveCompute.Category; // blindly assume category is resolved when set, even if unchanged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageSource _previewImage;
|
||||||
|
public ImageSource PreviewImage
|
||||||
|
{
|
||||||
|
get => _previewImage;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _previewImage, value))
|
||||||
|
{
|
||||||
|
Resolved |= EResolveCompute.Preview;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ExtractAsync()
|
||||||
|
=> ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
|
||||||
|
_applicationView.CUE4Parse.ExtractSelected(cancellationToken, [Asset]));
|
||||||
|
|
||||||
|
public Task ResolveAsync(EResolveCompute resolve)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ResolveInternalAsync(resolve);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Failed to resolve asset {AssetName} ({Resolver})", Asset.Path, resolve.ToStringBitfield());
|
||||||
|
|
||||||
|
Resolved = EResolveCompute.All;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ResolveInternalAsync(EResolveCompute resolve)
|
||||||
|
{
|
||||||
|
if (!_applicationView.IsAssetsExplorerVisible || !UserSettings.Default.PreviewTexturesAssetExplorer)
|
||||||
|
{
|
||||||
|
resolve &= ~EResolveCompute.Preview;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve &= ~Resolved;
|
||||||
|
if (resolve == EResolveCompute.None)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (!Asset.IsUePackage || _applicationView.CUE4Parse is null)
|
||||||
|
return ResolveByExtensionAsync(resolve);
|
||||||
|
|
||||||
|
return ResolveByPackageAsync(resolve);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ResolveByPackageAsync(EResolveCompute resolve)
|
||||||
|
{
|
||||||
|
if (Asset.Extension is "umap")
|
||||||
|
{
|
||||||
|
AssetCategory = EAssetCategory.World;
|
||||||
|
ResolvedAssetType = "World";
|
||||||
|
Resolved |= EResolveCompute.Preview;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
if (Asset.NameWithoutExtension.EndsWith("_BuiltData"))
|
||||||
|
{
|
||||||
|
AssetCategory = EAssetCategory.BuildData;
|
||||||
|
ResolvedAssetType = "MapBuildDataRegistry";
|
||||||
|
Resolved |= EResolveCompute.Preview;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.Run(() =>
|
||||||
|
{
|
||||||
|
// TODO: cache and reuse packages
|
||||||
|
var pkg = _applicationView.CUE4Parse?.Provider.LoadPackage(Asset);
|
||||||
|
if (pkg is null)
|
||||||
|
throw new InvalidOperationException($"Failed to load {Asset.Path} as UE package.");
|
||||||
|
|
||||||
|
var mainIndex = pkg.GetExportIndex(Asset.NameWithoutExtension, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (mainIndex < 0) mainIndex = pkg.GetExportIndex($"{Asset.NameWithoutExtension}_C", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (mainIndex < 0) mainIndex = 0;
|
||||||
|
|
||||||
|
var pointer = new FPackageIndex(pkg, mainIndex + 1).ResolvedObject;
|
||||||
|
if (pointer?.Object is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class?.Object?.Value as UStruct, pkg);
|
||||||
|
ResolvedAssetType = dummy.ExportType;
|
||||||
|
|
||||||
|
AssetCategory = dummy switch
|
||||||
|
{
|
||||||
|
URigVMBlueprintGeneratedClass => EAssetCategory.RigVMBlueprintGeneratedClass,
|
||||||
|
UAnimBlueprintGeneratedClass => EAssetCategory.AnimBlueprintGeneratedClass,
|
||||||
|
UWidgetBlueprintGeneratedClass => EAssetCategory.WidgetBlueprintGeneratedClass,
|
||||||
|
UBlueprintGeneratedClass or UFunction => EAssetCategory.BlueprintGeneratedClass,
|
||||||
|
UUserDefinedEnum => EAssetCategory.UserDefinedEnum,
|
||||||
|
UUserDefinedStruct => EAssetCategory.UserDefinedStruct,
|
||||||
|
UBlueprintCore => EAssetCategory.Blueprint,
|
||||||
|
UClassCookedMetaData or UStructCookedMetaData or UEnumCookedMetaData => EAssetCategory.CookedMetaData,
|
||||||
|
|
||||||
|
UStaticMesh => EAssetCategory.StaticMesh,
|
||||||
|
USkeletalMesh => EAssetCategory.SkeletalMesh,
|
||||||
|
UPhysicsAsset => EAssetCategory.PhysicsAsset,
|
||||||
|
|
||||||
|
UTexture => EAssetCategory.Texture,
|
||||||
|
|
||||||
|
UMaterialInterface => EAssetCategory.Material,
|
||||||
|
UMaterialInterfaceEditorOnlyData => EAssetCategory.MaterialEditorData,
|
||||||
|
UMaterialFunction => EAssetCategory.MaterialFunction,
|
||||||
|
UMaterialParameterCollection => EAssetCategory.MaterialParameterCollection,
|
||||||
|
|
||||||
|
UAnimationAsset => EAssetCategory.Animation,
|
||||||
|
USkeleton => EAssetCategory.Skeleton,
|
||||||
|
|
||||||
|
UWorld => EAssetCategory.World,
|
||||||
|
UMapBuildDataRegistry => EAssetCategory.BuildData,
|
||||||
|
ULevelSequence => EAssetCategory.LevelSequence,
|
||||||
|
UFoliageType => EAssetCategory.Foliage,
|
||||||
|
|
||||||
|
UItemDefinitionBase => EAssetCategory.ItemDefinitionBase,
|
||||||
|
UDataAsset or UDataTable or UCurveTable or UStringTable => EAssetCategory.Data,
|
||||||
|
UCurveBase => EAssetCategory.CurveBase,
|
||||||
|
|
||||||
|
UWwiseAssetLibrary or USoundBase or UAkMediaAssetData or UAtomWaveBank or USoundAtomCue
|
||||||
|
or UAtomCueSheet or USoundAtomCueSheet or UFMODBank or UFMODEvent or UAkAudioType => EAssetCategory.Audio,
|
||||||
|
UFileMediaSource => EAssetCategory.Video,
|
||||||
|
UFont or UFontFace => EAssetCategory.Font,
|
||||||
|
|
||||||
|
UNiagaraSystem or UNiagaraScriptBase or UParticleSystem => EAssetCategory.Particle,
|
||||||
|
|
||||||
|
_ => EAssetCategory.All
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (AssetCategory)
|
||||||
|
{
|
||||||
|
case EAssetCategory.Texture when pointer.Object.Value is UTexture texture:
|
||||||
|
{
|
||||||
|
if (!resolve.HasFlag(EResolveCompute.Preview))
|
||||||
|
break;
|
||||||
|
|
||||||
|
var img = texture.Decode(MaxPreviewSize, UserSettings.Default.CurrentDir.TexturePlatform);
|
||||||
|
if (img != null)
|
||||||
|
{
|
||||||
|
using var bitmap = img.ToSkBitmap();
|
||||||
|
using var image = bitmap.Encode(SKEncodedImageFormat.Png, 100);
|
||||||
|
SetPreviewImage(image);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EAssetCategory.ItemDefinitionBase:
|
||||||
|
if (!resolve.HasFlag(EResolveCompute.Preview))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (pointer.Object.Value is UItemDefinitionBase itemDef)
|
||||||
|
{
|
||||||
|
if (LookupPreview(itemDef.DataList)) break;
|
||||||
|
|
||||||
|
if (itemDef is UAthenaPickaxeItemDefinition pickaxe && pickaxe.WeaponDefinition.TryLoad(out UItemDefinitionBase weaponDef))
|
||||||
|
{
|
||||||
|
LookupPreview(weaponDef.DataList);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LookupPreview(FInstancedStruct[] dataList)
|
||||||
|
{
|
||||||
|
foreach (var data in dataList)
|
||||||
|
{
|
||||||
|
if (!data.NonConstStruct.TryGetValue(out FSoftObjectPath icon, "Icon", "LargeIcon") ||
|
||||||
|
!icon.TryLoad<UTexture2D>(out var texture))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var img = texture.Decode(MaxPreviewSize, UserSettings.Default.CurrentDir.TexturePlatform);
|
||||||
|
if (img == null) return false;
|
||||||
|
|
||||||
|
using var bitmap = img.ToSkBitmap();
|
||||||
|
using var image = bitmap.Encode(SKEncodedImageFormat.Png, 100);
|
||||||
|
SetPreviewImage(image);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Resolved |= EResolveCompute.Preview;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ResolveByExtensionAsync(EResolveCompute resolve)
|
||||||
|
{
|
||||||
|
Resolved |= EResolveCompute.Preview;
|
||||||
|
switch (Asset.Extension)
|
||||||
|
{
|
||||||
|
case "uproject":
|
||||||
|
case "uefnproject":
|
||||||
|
case "upluginmanifest":
|
||||||
|
case "uplugin":
|
||||||
|
case "ini":
|
||||||
|
case "locmeta":
|
||||||
|
case "locres":
|
||||||
|
case "verse":
|
||||||
|
case "lua":
|
||||||
|
case "luac":
|
||||||
|
case "json5":
|
||||||
|
case "json":
|
||||||
|
case "bin":
|
||||||
|
case "txt":
|
||||||
|
case "log":
|
||||||
|
case "pem":
|
||||||
|
case "xml":
|
||||||
|
AssetCategory = EAssetCategory.Data;
|
||||||
|
break;
|
||||||
|
case "wav":
|
||||||
|
case "bank":
|
||||||
|
case "bnk":
|
||||||
|
case "pck":
|
||||||
|
case "awb":
|
||||||
|
case "acb":
|
||||||
|
case "xvag":
|
||||||
|
case "flac":
|
||||||
|
case "at9":
|
||||||
|
case "wem":
|
||||||
|
case "ogg":
|
||||||
|
AssetCategory = EAssetCategory.Audio;
|
||||||
|
break;
|
||||||
|
case "ufont":
|
||||||
|
case "otf":
|
||||||
|
case "ttf":
|
||||||
|
AssetCategory = EAssetCategory.Font;
|
||||||
|
break;
|
||||||
|
case "mp4":
|
||||||
|
AssetCategory = EAssetCategory.Video;
|
||||||
|
break;
|
||||||
|
case "jpg":
|
||||||
|
case "png":
|
||||||
|
case "bmp":
|
||||||
|
case "svg":
|
||||||
|
{
|
||||||
|
Resolved |= ~EResolveCompute.Preview;
|
||||||
|
AssetCategory = EAssetCategory.Texture;
|
||||||
|
if (!resolve.HasFlag(EResolveCompute.Preview))
|
||||||
|
break;
|
||||||
|
|
||||||
|
return Task.Run(() =>
|
||||||
|
{
|
||||||
|
var data = _applicationView.CUE4Parse.Provider.SaveAsset(Asset);
|
||||||
|
using var stream = new MemoryStream(data);
|
||||||
|
stream.Position = 0;
|
||||||
|
|
||||||
|
SKBitmap bitmap;
|
||||||
|
if (Asset.Extension == "svg")
|
||||||
|
{
|
||||||
|
var svg = new SKSvg();
|
||||||
|
svg.Load(stream);
|
||||||
|
if (svg.Picture == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bitmap = new SKBitmap(MaxPreviewSize, MaxPreviewSize);
|
||||||
|
using var canvas = new SKCanvas(bitmap);
|
||||||
|
canvas.Clear(SKColors.Transparent);
|
||||||
|
|
||||||
|
var bounds = svg.Picture.CullRect;
|
||||||
|
float scale = Math.Min(MaxPreviewSize / bounds.Width, MaxPreviewSize / bounds.Height);
|
||||||
|
canvas.Scale(scale);
|
||||||
|
canvas.Translate(-bounds.Left, -bounds.Top);
|
||||||
|
canvas.DrawPicture(svg.Picture);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bitmap = SKBitmap.Decode(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var image = bitmap.Encode(Asset.Extension == "jpg" ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100);
|
||||||
|
SetPreviewImage(image);
|
||||||
|
|
||||||
|
bitmap.Dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
AssetCategory = EAssetCategory.All; // just so it sets resolved
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetPreviewImage(SKData data)
|
||||||
|
{
|
||||||
|
using var ms = new MemoryStream(data.ToArray());
|
||||||
|
ms.Position = 0;
|
||||||
|
|
||||||
|
var bitmap = new BitmapImage();
|
||||||
|
bitmap.BeginInit();
|
||||||
|
bitmap.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bitmap.StreamSource = ms;
|
||||||
|
bitmap.EndInit();
|
||||||
|
bitmap.Freeze();
|
||||||
|
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() => PreviewImage = bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CancellationTokenSource _previewCts;
|
||||||
|
public void OnIsVisible()
|
||||||
|
{
|
||||||
|
if (Resolved == EResolveCompute.All)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_previewCts?.Cancel();
|
||||||
|
_previewCts = new CancellationTokenSource();
|
||||||
|
var token = _previewCts.Token;
|
||||||
|
|
||||||
|
Task.Delay(100, token).ContinueWith(t =>
|
||||||
|
{
|
||||||
|
if (t.IsCanceled) return;
|
||||||
|
ResolveAsync(EResolveCompute.All);
|
||||||
|
}, TaskScheduler.FromCurrentSynchronizationContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum EResolveCompute
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Category = 1 << 0,
|
||||||
|
Preview = 1 << 1,
|
||||||
|
|
||||||
|
All = Category | Preview
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using FModel.Extensions;
|
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -10,6 +9,7 @@ using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||||
using CUE4Parse.UE4.Versions;
|
using CUE4Parse.UE4.Versions;
|
||||||
|
using CUE4Parse.Utils;
|
||||||
using FModel.Settings;
|
using FModel.Settings;
|
||||||
using FModel.ViewModels.ApiEndpoints.Models;
|
using FModel.ViewModels.ApiEndpoints.Models;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
|
@ -84,11 +84,11 @@ public class GameSelectorViewModel : ViewModel
|
||||||
=> Enum.GetValues<EGame>()
|
=> Enum.GetValues<EGame>()
|
||||||
.GroupBy(value => (int)value)
|
.GroupBy(value => (int)value)
|
||||||
.Select(group => group.First())
|
.Select(group => group.First())
|
||||||
.OrderBy(value => (int)value == ((int)value & ~0xF));
|
.OrderBy(value => ((int)value & 0xFF) == 0);
|
||||||
private IEnumerable<DirectorySettings> EnumerateDetectedGames()
|
private IEnumerable<DirectorySettings> EnumerateDetectedGames()
|
||||||
{
|
{
|
||||||
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_5);
|
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_7);
|
||||||
yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_5);
|
yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_7);
|
||||||
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks", EGame.GAME_RogueCompany);
|
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks", EGame.GAME_RogueCompany);
|
||||||
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks", EGame.GAME_UE4_21);
|
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks", EGame.GAME_UE4_21);
|
||||||
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks", EGame.GAME_Borderlands3);
|
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks", EGame.GAME_Borderlands3);
|
||||||
|
|
@ -99,7 +99,7 @@ public class GameSelectorViewModel : ViewModel
|
||||||
yield return GetUnrealEngineGame("9361c8c6d2f34b42b5f2f61093eedf48", "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds);
|
yield return GetUnrealEngineGame("9361c8c6d2f34b42b5f2f61093eedf48", "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds);
|
||||||
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks", EGame.GAME_Valorant);
|
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks", EGame.GAME_Valorant);
|
||||||
yield return DirectorySettings.Default("VALORANT [LIVE]", Constants._VAL_LIVE_TRIGGER, ue: EGame.GAME_Valorant);
|
yield return DirectorySettings.Default("VALORANT [LIVE]", Constants._VAL_LIVE_TRIGGER, ue: EGame.GAME_Valorant);
|
||||||
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks", EGame.GAME_UE4_27); // Dead By Daylight
|
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks", EGame.GAME_DeadByDaylight, aesKey: "0x22b1639b548124925cf7b9cbaa09f9ac295fcf0324586d6b37ee1d42670b39b3"); // Dead By Daylight
|
||||||
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds); // PUBG
|
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds); // PUBG
|
||||||
yield return GetSteamGame(1172380, "\\SwGame\\Content\\Paks", EGame.GAME_StarWarsJediFallenOrder); // STAR WARS Jedi: Fallen Order™
|
yield return GetSteamGame(1172380, "\\SwGame\\Content\\Paks", EGame.GAME_StarWarsJediFallenOrder); // STAR WARS Jedi: Fallen Order™
|
||||||
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks", EGame.GAME_Splitgate); // Splitgate
|
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks", EGame.GAME_Splitgate); // Splitgate
|
||||||
|
|
@ -151,13 +151,13 @@ public class GameSelectorViewModel : ViewModel
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DirectorySettings GetSteamGame(int id, string pakDirectory, EGame ueVersion)
|
private DirectorySettings GetSteamGame(int id, string pakDirectory, EGame ueVersion, string aesKey = "")
|
||||||
{
|
{
|
||||||
var steamInfo = SteamDetection.GetSteamGameById(id);
|
var steamInfo = SteamDetection.GetSteamGameById(id);
|
||||||
if (steamInfo is not null)
|
if (steamInfo is not null)
|
||||||
{
|
{
|
||||||
Log.Debug("Found {GameName} in steam manifests", steamInfo.Name);
|
Log.Debug("Found {GameName} in steam manifests", steamInfo.Name);
|
||||||
return DirectorySettings.Default(steamInfo.Name, $"{steamInfo.GameRoot}{pakDirectory}", ue: ueVersion);
|
return DirectorySettings.Default(steamInfo.Name, $"{steamInfo.GameRoot}{pakDirectory}", ue: ueVersion, aes: aesKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,25 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
|
using CUE4Parse.UE4.VirtualFileSystem;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
|
|
||||||
namespace FModel.ViewModels;
|
namespace FModel.ViewModels;
|
||||||
|
|
||||||
public class SearchViewModel : ViewModel
|
public class SearchViewModel : ViewModel
|
||||||
{
|
{
|
||||||
private string _filterText;
|
public enum ESortSizeMode
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Ascending,
|
||||||
|
Descending
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _filterText = string.Empty;
|
||||||
public string FilterText
|
public string FilterText
|
||||||
{
|
{
|
||||||
get => _filterText;
|
get => _filterText;
|
||||||
|
|
@ -31,34 +40,106 @@ public class SearchViewModel : ViewModel
|
||||||
set => SetProperty(ref _hasMatchCaseEnabled, value);
|
set => SetProperty(ref _hasMatchCaseEnabled, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ResultsCount => SearchResults?.Count ?? 0;
|
private ESortSizeMode _currentSortSizeMode = ESortSizeMode.None;
|
||||||
public RangeObservableCollection<AssetItem> SearchResults { get; }
|
public ESortSizeMode CurrentSortSizeMode
|
||||||
public ICollectionView SearchResultsView { get; }
|
{
|
||||||
|
get => _currentSortSizeMode;
|
||||||
|
set => SetProperty(ref _currentSortSizeMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _resultsCount = 0;
|
||||||
|
public int ResultsCount
|
||||||
|
{
|
||||||
|
get => _resultsCount;
|
||||||
|
private set => SetProperty(ref _resultsCount, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameFile _refFile;
|
||||||
|
public GameFile RefFile
|
||||||
|
{
|
||||||
|
get => _refFile;
|
||||||
|
private set => SetProperty(ref _refFile, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RangeObservableCollection<GameFile> SearchResults { get; }
|
||||||
|
public ListCollectionView SearchResultsView { get; }
|
||||||
|
|
||||||
public SearchViewModel()
|
public SearchViewModel()
|
||||||
{
|
{
|
||||||
SearchResults = new RangeObservableCollection<AssetItem>();
|
SearchResults = [];
|
||||||
SearchResultsView = new ListCollectionView(SearchResults);
|
SearchResultsView = new ListCollectionView(SearchResults)
|
||||||
|
{
|
||||||
|
Filter = e => ItemFilter(e, FilterText.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries)),
|
||||||
|
};
|
||||||
|
ResultsCount = SearchResultsView.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshFilter()
|
public void RefreshFilter()
|
||||||
{
|
{
|
||||||
if (SearchResultsView.Filter == null)
|
SearchResultsView.Refresh();
|
||||||
SearchResultsView.Filter = e => ItemFilter(e, FilterText.Trim().Split(' '));
|
ResultsCount = SearchResultsView.Count;
|
||||||
else
|
}
|
||||||
SearchResultsView.Refresh();
|
|
||||||
|
public void ChangeCollection(IEnumerable<GameFile> files, GameFile refFile = null)
|
||||||
|
{
|
||||||
|
SearchResults.Clear();
|
||||||
|
SearchResults.AddRange(files);
|
||||||
|
RefFile = refFile;
|
||||||
|
ResultsCount = SearchResultsView.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CycleSortSizeMode()
|
||||||
|
{
|
||||||
|
CurrentSortSizeMode = CurrentSortSizeMode switch
|
||||||
|
{
|
||||||
|
ESortSizeMode.None => ESortSizeMode.Descending,
|
||||||
|
ESortSizeMode.Descending => ESortSizeMode.Ascending,
|
||||||
|
_ => ESortSizeMode.None
|
||||||
|
};
|
||||||
|
|
||||||
|
var sorted = await Task.Run(() =>
|
||||||
|
{
|
||||||
|
var archiveDict = SearchResults
|
||||||
|
.OfType<VfsEntry>()
|
||||||
|
.Select(f => f.Vfs.Name)
|
||||||
|
.Distinct()
|
||||||
|
.Select((name, idx) => (name, idx))
|
||||||
|
.ToDictionary(x => x.name, x => x.idx);
|
||||||
|
|
||||||
|
var keyed = SearchResults.Select(f =>
|
||||||
|
{
|
||||||
|
int archiveKey = f is VfsEntry ve && archiveDict.TryGetValue(ve.Vfs.Name, out var key) ? key : -1;
|
||||||
|
return (File: f, f.Size, ArchiveKey: archiveKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
return CurrentSortSizeMode switch
|
||||||
|
{
|
||||||
|
ESortSizeMode.Ascending => keyed
|
||||||
|
.OrderBy(x => x.Size).ThenBy(x => x.ArchiveKey)
|
||||||
|
.Select(x => x.File).ToList(),
|
||||||
|
ESortSizeMode.Descending => keyed
|
||||||
|
.OrderByDescending(x => x.Size).ThenBy(x => x.ArchiveKey)
|
||||||
|
.Select(x => x.File).ToList(),
|
||||||
|
_ => keyed
|
||||||
|
.OrderBy(x => x.ArchiveKey).ThenBy(x => x.File.Path, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(x => x.File).ToList()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
SearchResults.Clear();
|
||||||
|
SearchResults.AddRange(sorted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ItemFilter(object item, IEnumerable<string> filters)
|
private bool ItemFilter(object item, IEnumerable<string> filters)
|
||||||
{
|
{
|
||||||
if (item is not AssetItem assetItem)
|
if (item is not GameFile entry)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!HasRegexEnabled)
|
if (!HasRegexEnabled)
|
||||||
return filters.All(x => assetItem.FullPath.Contains(x, HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
return filters.All(x => entry.Path.Contains(x, HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
var o = RegexOptions.None;
|
var o = RegexOptions.None;
|
||||||
if (HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase;
|
if (!HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase;
|
||||||
return new Regex(FilterText, o).Match(assetItem.FullPath).Success;
|
return new Regex(FilterText, o).Match(entry.Path).Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Nanite;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||||
using CUE4Parse.UE4.Versions;
|
using CUE4Parse.UE4.Versions;
|
||||||
using CUE4Parse_Conversion.Meshes;
|
using CUE4Parse_Conversion.Meshes;
|
||||||
using CUE4Parse_Conversion.Textures;
|
using CUE4Parse_Conversion.Textures;
|
||||||
using CUE4Parse_Conversion.UEFormat.Enums;
|
using CUE4Parse_Conversion.UEFormat.Enums;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
using FModel.Settings;
|
using FModel.Settings;
|
||||||
|
|
@ -143,6 +144,13 @@ public class SettingsViewModel : ViewModel
|
||||||
set => SetProperty(ref _selectedLodExportFormat, value);
|
set => SetProperty(ref _selectedLodExportFormat, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ENaniteMeshFormat _selectedNaniteMeshExportFormat;
|
||||||
|
public ENaniteMeshFormat SelectedNaniteMeshExportFormat
|
||||||
|
{
|
||||||
|
get => _selectedNaniteMeshExportFormat;
|
||||||
|
set => SetProperty(ref _selectedNaniteMeshExportFormat, value);
|
||||||
|
}
|
||||||
|
|
||||||
private EMaterialFormat _selectedMaterialExportFormat;
|
private EMaterialFormat _selectedMaterialExportFormat;
|
||||||
public EMaterialFormat SelectedMaterialExportFormat
|
public EMaterialFormat SelectedMaterialExportFormat
|
||||||
{
|
{
|
||||||
|
|
@ -157,6 +165,13 @@ public class SettingsViewModel : ViewModel
|
||||||
set => SetProperty(ref _selectedTextureExportFormat, value);
|
set => SetProperty(ref _selectedTextureExportFormat, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ulong _criwareDecryptionKey;
|
||||||
|
public ulong CriwareDecryptionKey
|
||||||
|
{
|
||||||
|
get => _criwareDecryptionKey;
|
||||||
|
set => SetProperty(ref _criwareDecryptionKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool SocketSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.ActorX;
|
public bool SocketSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.ActorX;
|
||||||
public bool CompressionSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.UEFormat;
|
public bool CompressionSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.UEFormat;
|
||||||
|
|
||||||
|
|
@ -170,6 +185,7 @@ public class SettingsViewModel : ViewModel
|
||||||
public ReadOnlyObservableCollection<ESocketFormat> SocketExportFormats { get; private set; }
|
public ReadOnlyObservableCollection<ESocketFormat> SocketExportFormats { get; private set; }
|
||||||
public ReadOnlyObservableCollection<EFileCompressionFormat> CompressionFormats { get; private set; }
|
public ReadOnlyObservableCollection<EFileCompressionFormat> CompressionFormats { get; private set; }
|
||||||
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
|
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
|
||||||
|
public ReadOnlyObservableCollection<ENaniteMeshFormat> NaniteMeshExportFormats { get; private set; }
|
||||||
public ReadOnlyObservableCollection<EMaterialFormat> MaterialExportFormats { get; private set; }
|
public ReadOnlyObservableCollection<EMaterialFormat> MaterialExportFormats { get; private set; }
|
||||||
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
|
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
|
||||||
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
|
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
|
||||||
|
|
@ -193,6 +209,7 @@ public class SettingsViewModel : ViewModel
|
||||||
private ESocketFormat _socketExportFormatSnapshot;
|
private ESocketFormat _socketExportFormatSnapshot;
|
||||||
private EFileCompressionFormat _compressionFormatSnapshot;
|
private EFileCompressionFormat _compressionFormatSnapshot;
|
||||||
private ELodFormat _lodExportFormatSnapshot;
|
private ELodFormat _lodExportFormatSnapshot;
|
||||||
|
private ENaniteMeshFormat _naniteMeshExportFormatSnapshot;
|
||||||
private EMaterialFormat _materialExportFormatSnapshot;
|
private EMaterialFormat _materialExportFormatSnapshot;
|
||||||
private ETextureFormat _textureExportFormatSnapshot;
|
private ETextureFormat _textureExportFormatSnapshot;
|
||||||
|
|
||||||
|
|
@ -217,6 +234,7 @@ public class SettingsViewModel : ViewModel
|
||||||
_customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions;
|
_customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions;
|
||||||
_optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options;
|
_optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options;
|
||||||
_mapStructTypesSnapshot = UserSettings.Default.CurrentDir.Versioning.MapStructTypes;
|
_mapStructTypesSnapshot = UserSettings.Default.CurrentDir.Versioning.MapStructTypes;
|
||||||
|
_criwareDecryptionKey = UserSettings.Default.CurrentDir.CriwareDecryptionKey;
|
||||||
|
|
||||||
AesEndpoint = UserSettings.Default.CurrentDir.Endpoints[0];
|
AesEndpoint = UserSettings.Default.CurrentDir.Endpoints[0];
|
||||||
MappingEndpoint = UserSettings.Default.CurrentDir.Endpoints[1];
|
MappingEndpoint = UserSettings.Default.CurrentDir.Endpoints[1];
|
||||||
|
|
@ -233,6 +251,7 @@ public class SettingsViewModel : ViewModel
|
||||||
_socketExportFormatSnapshot = UserSettings.Default.SocketExportFormat;
|
_socketExportFormatSnapshot = UserSettings.Default.SocketExportFormat;
|
||||||
_compressionFormatSnapshot = UserSettings.Default.CompressionFormat;
|
_compressionFormatSnapshot = UserSettings.Default.CompressionFormat;
|
||||||
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
|
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
|
||||||
|
_naniteMeshExportFormatSnapshot = UserSettings.Default.NaniteMeshExportFormat;
|
||||||
_materialExportFormatSnapshot = UserSettings.Default.MaterialExportFormat;
|
_materialExportFormatSnapshot = UserSettings.Default.MaterialExportFormat;
|
||||||
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
|
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
|
||||||
|
|
||||||
|
|
@ -248,8 +267,10 @@ public class SettingsViewModel : ViewModel
|
||||||
SelectedSocketExportFormat = _socketExportFormatSnapshot;
|
SelectedSocketExportFormat = _socketExportFormatSnapshot;
|
||||||
SelectedCompressionFormat = _selectedCompressionFormat;
|
SelectedCompressionFormat = _selectedCompressionFormat;
|
||||||
SelectedLodExportFormat = _lodExportFormatSnapshot;
|
SelectedLodExportFormat = _lodExportFormatSnapshot;
|
||||||
|
SelectedNaniteMeshExportFormat = _naniteMeshExportFormatSnapshot;
|
||||||
SelectedMaterialExportFormat = _materialExportFormatSnapshot;
|
SelectedMaterialExportFormat = _materialExportFormatSnapshot;
|
||||||
SelectedTextureExportFormat = _textureExportFormatSnapshot;
|
SelectedTextureExportFormat = _textureExportFormatSnapshot;
|
||||||
|
CriwareDecryptionKey = _criwareDecryptionKey;
|
||||||
SelectedAesReload = UserSettings.Default.AesReload;
|
SelectedAesReload = UserSettings.Default.AesReload;
|
||||||
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
|
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
|
||||||
|
|
||||||
|
|
@ -263,6 +284,7 @@ public class SettingsViewModel : ViewModel
|
||||||
SocketExportFormats = new ReadOnlyObservableCollection<ESocketFormat>(new ObservableCollection<ESocketFormat>(EnumerateSocketExportFormat()));
|
SocketExportFormats = new ReadOnlyObservableCollection<ESocketFormat>(new ObservableCollection<ESocketFormat>(EnumerateSocketExportFormat()));
|
||||||
CompressionFormats = new ReadOnlyObservableCollection<EFileCompressionFormat>(new ObservableCollection<EFileCompressionFormat>(EnumerateCompressionFormat()));
|
CompressionFormats = new ReadOnlyObservableCollection<EFileCompressionFormat>(new ObservableCollection<EFileCompressionFormat>(EnumerateCompressionFormat()));
|
||||||
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
|
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
|
||||||
|
NaniteMeshExportFormats = new ReadOnlyObservableCollection<ENaniteMeshFormat>(new ObservableCollection<ENaniteMeshFormat>(EnumerateNaniteMeshExportFormat()));
|
||||||
MaterialExportFormats = new ReadOnlyObservableCollection<EMaterialFormat>(new ObservableCollection<EMaterialFormat>(EnumerateMaterialExportFormat()));
|
MaterialExportFormats = new ReadOnlyObservableCollection<EMaterialFormat>(new ObservableCollection<EMaterialFormat>(EnumerateMaterialExportFormat()));
|
||||||
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
|
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
|
||||||
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
|
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
|
||||||
|
|
@ -295,6 +317,7 @@ public class SettingsViewModel : ViewModel
|
||||||
UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions;
|
UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions;
|
||||||
UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions;
|
UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions;
|
||||||
UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes;
|
UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes;
|
||||||
|
UserSettings.Default.CurrentDir.CriwareDecryptionKey = CriwareDecryptionKey;
|
||||||
|
|
||||||
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
|
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
|
||||||
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
|
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
|
||||||
|
|
@ -303,6 +326,7 @@ public class SettingsViewModel : ViewModel
|
||||||
UserSettings.Default.SocketExportFormat = SelectedSocketExportFormat;
|
UserSettings.Default.SocketExportFormat = SelectedSocketExportFormat;
|
||||||
UserSettings.Default.CompressionFormat = SelectedCompressionFormat;
|
UserSettings.Default.CompressionFormat = SelectedCompressionFormat;
|
||||||
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
|
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
|
||||||
|
UserSettings.Default.NaniteMeshExportFormat = SelectedNaniteMeshExportFormat;
|
||||||
UserSettings.Default.MaterialExportFormat = SelectedMaterialExportFormat;
|
UserSettings.Default.MaterialExportFormat = SelectedMaterialExportFormat;
|
||||||
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
|
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
|
||||||
UserSettings.Default.AesReload = SelectedAesReload;
|
UserSettings.Default.AesReload = SelectedAesReload;
|
||||||
|
|
@ -318,7 +342,7 @@ public class SettingsViewModel : ViewModel
|
||||||
=> Enum.GetValues<EGame>()
|
=> Enum.GetValues<EGame>()
|
||||||
.GroupBy(value => (int)value)
|
.GroupBy(value => (int)value)
|
||||||
.Select(group => group.First())
|
.Select(group => group.First())
|
||||||
.OrderBy(value => (int)value == ((int)value & ~0xF));
|
.OrderBy(value => ((int)value & 0xFF) == 0);
|
||||||
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
|
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
|
||||||
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
|
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
|
||||||
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
|
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
|
||||||
|
|
@ -328,6 +352,7 @@ public class SettingsViewModel : ViewModel
|
||||||
private IEnumerable<ESocketFormat> EnumerateSocketExportFormat() => Enum.GetValues<ESocketFormat>();
|
private IEnumerable<ESocketFormat> EnumerateSocketExportFormat() => Enum.GetValues<ESocketFormat>();
|
||||||
private IEnumerable<EFileCompressionFormat> EnumerateCompressionFormat() => Enum.GetValues<EFileCompressionFormat>();
|
private IEnumerable<EFileCompressionFormat> EnumerateCompressionFormat() => Enum.GetValues<EFileCompressionFormat>();
|
||||||
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
|
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
|
||||||
|
private IEnumerable<ENaniteMeshFormat> EnumerateNaniteMeshExportFormat() => Enum.GetValues<ENaniteMeshFormat>();
|
||||||
private IEnumerable<EMaterialFormat> EnumerateMaterialExportFormat() => Enum.GetValues<EMaterialFormat>();
|
private IEnumerable<EMaterialFormat> EnumerateMaterialExportFormat() => Enum.GetValues<EMaterialFormat>();
|
||||||
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
|
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
|
||||||
private IEnumerable<ETexturePlatform> EnumerateUePlatforms() => Enum.GetValues<ETexturePlatform>();
|
private IEnumerable<ETexturePlatform> EnumerateUePlatforms() => Enum.GetValues<ETexturePlatform>();
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using ICSharpCode.AvalonEdit.Document;
|
||||||
using ICSharpCode.AvalonEdit.Highlighting;
|
using ICSharpCode.AvalonEdit.Highlighting;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -16,12 +15,15 @@ using System.Windows;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||||
using CUE4Parse_Conversion.Textures;
|
using CUE4Parse_Conversion.Textures;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
|
using CUE4Parse.Utils;
|
||||||
|
|
||||||
namespace FModel.ViewModels;
|
namespace FModel.ViewModels;
|
||||||
|
|
||||||
public class TabImage : ViewModel
|
public class TabImage : ViewModel
|
||||||
{
|
{
|
||||||
public string ExportName { get; }
|
public string ExportName { get; set; }
|
||||||
|
|
||||||
public byte[] ImageBuffer { get; set; }
|
public byte[] ImageBuffer { get; set; }
|
||||||
|
|
||||||
public TabImage(string name, bool rnn, SKBitmap img)
|
public TabImage(string name, bool rnn, SKBitmap img)
|
||||||
|
|
@ -31,6 +33,13 @@ public class TabImage : ViewModel
|
||||||
SetImage(img);
|
SetImage(img);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TabImage(string name, bool rnn, CTexture img)
|
||||||
|
{
|
||||||
|
ExportName = name;
|
||||||
|
RenderNearestNeighbor = rnn;
|
||||||
|
SetImage(img);
|
||||||
|
}
|
||||||
|
|
||||||
private BitmapImage _image;
|
private BitmapImage _image;
|
||||||
public BitmapImage Image
|
public BitmapImage Image
|
||||||
{
|
{
|
||||||
|
|
@ -70,11 +79,42 @@ public class TabImage : ViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
_bmp = bitmap;
|
_bmp = bitmap;
|
||||||
using var data = _bmp.Encode(NoAlpha ? ETextureFormat.Jpeg : UserSettings.Default.TextureExportFormat, 100);
|
ExportName += "." + (NoAlpha ? "jpg" : "png");
|
||||||
|
using var data = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100);
|
||||||
using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false);
|
using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false);
|
||||||
if (UserSettings.Default.TextureExportFormat == ETextureFormat.Tga)
|
var image = new BitmapImage();
|
||||||
return;
|
image.BeginInit();
|
||||||
|
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
image.StreamSource = stream;
|
||||||
|
image.EndInit();
|
||||||
|
image.Freeze();
|
||||||
|
Image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetImage(CTexture bitmap)
|
||||||
|
{
|
||||||
|
if (bitmap is null)
|
||||||
|
{
|
||||||
|
ImageBuffer = null;
|
||||||
|
Image = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_bmp = bitmap.ToSkBitmap();
|
||||||
|
byte[] imageData = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100).ToArray();
|
||||||
|
|
||||||
|
if (PixelFormatUtils.IsHDR(bitmap.PixelFormat) || (UserSettings.Default.TextureExportFormat != ETextureFormat.Jpeg && UserSettings.Default.TextureExportFormat != ETextureFormat.Png))
|
||||||
|
{
|
||||||
|
ImageBuffer = bitmap.Encode(UserSettings.Default.TextureExportFormat, UserSettings.Default.SaveHdrTexturesAsHdr, out var ext);
|
||||||
|
ExportName += "." + ext;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImageBuffer = imageData;
|
||||||
|
ExportName += "." + (NoAlpha ? "jpg" : "png");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var stream = new MemoryStream(imageData);
|
||||||
var image = new BitmapImage();
|
var image = new BitmapImage();
|
||||||
image.BeginInit();
|
image.BeginInit();
|
||||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
|
@ -92,22 +132,28 @@ public class TabItem : ViewModel
|
||||||
{
|
{
|
||||||
public string ParentExportType { get; private set; }
|
public string ParentExportType { get; private set; }
|
||||||
|
|
||||||
private string _header;
|
private GameFile _entry;
|
||||||
public string Header
|
public GameFile Entry
|
||||||
{
|
{
|
||||||
get => _header;
|
get => _entry;
|
||||||
set => SetProperty(ref _header, value);
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _entry, value);
|
||||||
|
RaisePropertyChanged(nameof(Header));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _directory;
|
private string _titleExtra;
|
||||||
public string Directory
|
public string TitleExtra
|
||||||
{
|
{
|
||||||
get => _directory;
|
get => _titleExtra;
|
||||||
set => SetProperty(ref _directory, value);
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _titleExtra, value);
|
||||||
|
RaisePropertyChanged(nameof(Header));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FullPath => this.Directory + "/" + this.Header;
|
|
||||||
|
|
||||||
private bool _hasSearchOpen;
|
private bool _hasSearchOpen;
|
||||||
public bool HasSearchOpen
|
public bool HasSearchOpen
|
||||||
{
|
{
|
||||||
|
|
@ -202,6 +248,8 @@ public class TabItem : ViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Header => $"{Entry.Name}{(string.IsNullOrEmpty(TitleExtra) ? "" : $" ({TitleExtra})")}";
|
||||||
|
|
||||||
public bool HasImage => SelectedImage != null;
|
public bool HasImage => SelectedImage != null;
|
||||||
public bool HasMultipleImages => _images.Count > 1;
|
public bool HasMultipleImages => _images.Count > 1;
|
||||||
public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}";
|
public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}";
|
||||||
|
|
@ -217,18 +265,17 @@ public class TabItem : ViewModel
|
||||||
private GoToCommand _goToCommand;
|
private GoToCommand _goToCommand;
|
||||||
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
|
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
|
||||||
|
|
||||||
public TabItem(string header, string directory, string parentExportType)
|
public TabItem(GameFile entry, string parentExportType)
|
||||||
{
|
{
|
||||||
Header = header;
|
Entry = entry;
|
||||||
Directory = directory;
|
|
||||||
ParentExportType = parentExportType;
|
ParentExportType = parentExportType;
|
||||||
_images = new ObservableCollection<TabImage>();
|
_images = new ObservableCollection<TabImage>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SoftReset(string header, string directory)
|
public void SoftReset(GameFile entry)
|
||||||
{
|
{
|
||||||
Header = header;
|
Entry = entry;
|
||||||
Directory = directory;
|
TitleExtra = string.Empty;
|
||||||
ParentExportType = string.Empty;
|
ParentExportType = string.Empty;
|
||||||
ScrollTrigger = null;
|
ScrollTrigger = null;
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
|
@ -245,7 +292,7 @@ public class TabItem : ViewModel
|
||||||
public void AddImage(UTexture texture, bool save, bool updateUi)
|
public void AddImage(UTexture texture, bool save, bool updateUi)
|
||||||
{
|
{
|
||||||
var appendLayerNumber = false;
|
var appendLayerNumber = false;
|
||||||
var img = new SKBitmap[1];
|
var img = new CTexture[1];
|
||||||
if (texture is UTexture2DArray textureArray)
|
if (texture is UTexture2DArray textureArray)
|
||||||
{
|
{
|
||||||
img = textureArray.DecodeTextureArray(UserSettings.Default.CurrentDir.TexturePlatform);
|
img = textureArray.DecodeTextureArray(UserSettings.Default.CurrentDir.TexturePlatform);
|
||||||
|
|
@ -256,7 +303,7 @@ public class TabItem : ViewModel
|
||||||
img[0] = texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform);
|
img[0] = texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform);
|
||||||
if (texture is UTextureCube)
|
if (texture is UTextureCube)
|
||||||
{
|
{
|
||||||
img[0] = img[0]?.ToPanorama();
|
img[0] = img[0].ToPanorama();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,6 +318,29 @@ public class TabItem : ViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddImage(string name, bool rnn, CTexture[] img, bool save, bool updateUi, bool appendLayerNumber = false)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < img.Length; i++)
|
||||||
|
{
|
||||||
|
AddImage($"{name}{(appendLayerNumber ? $"_{i}" : "")}", rnn, img[i], save, updateUi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddImage(string name, bool rnn, CTexture img, bool save, bool updateUi)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
var t = new TabImage(name, rnn, img);
|
||||||
|
if (save) SaveImage(t, updateUi);
|
||||||
|
if (!updateUi) return;
|
||||||
|
|
||||||
|
_images.Add(t);
|
||||||
|
SelectedImage ??= t;
|
||||||
|
RaisePropertyChanged("Page");
|
||||||
|
RaisePropertyChanged("HasMultipleImages");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void AddImage(string name, bool rnn, SKBitmap img, bool save, bool updateUi)
|
public void AddImage(string name, bool rnn, SKBitmap img, bool save, bool updateUi)
|
||||||
{
|
{
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
|
@ -304,23 +374,14 @@ public class TabItem : ViewModel
|
||||||
public void SaveImage() => SaveImage(SelectedImage, true);
|
public void SaveImage() => SaveImage(SelectedImage, true);
|
||||||
private void SaveImage(TabImage image, bool updateUi)
|
private void SaveImage(TabImage image, bool updateUi)
|
||||||
{
|
{
|
||||||
if (image == null) return;
|
if (image == null)
|
||||||
|
return;
|
||||||
|
|
||||||
var ext = UserSettings.Default.TextureExportFormat switch
|
var path = Path.Combine(UserSettings.Default.TextureDirectory, UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", image.ExportName).Replace('\\', '/');
|
||||||
{
|
|
||||||
ETextureFormat.Png => ".png",
|
|
||||||
ETextureFormat.Jpeg => ".jpg",
|
|
||||||
ETextureFormat.Tga => ".tga",
|
|
||||||
_ => ".png"
|
|
||||||
};
|
|
||||||
|
|
||||||
var fileName = image.ExportName + ext;
|
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
||||||
var path = Path.Combine(UserSettings.Default.TextureDirectory,
|
|
||||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
|
|
||||||
|
|
||||||
System.IO.Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
SaveImage(image, path, image.ExportName, updateUi);
|
||||||
|
|
||||||
SaveImage(image, path, fileName, updateUi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveImage(TabImage image, string path, string fileName, bool updateUi)
|
private void SaveImage(TabImage image, string path, string fileName, bool updateUi)
|
||||||
|
|
@ -337,11 +398,11 @@ public class TabItem : ViewModel
|
||||||
|
|
||||||
public void SaveProperty(bool updateUi)
|
public void SaveProperty(bool updateUi)
|
||||||
{
|
{
|
||||||
var fileName = Path.ChangeExtension(Header, ".json");
|
var fileName = Path.ChangeExtension(Entry.Name, ".json");
|
||||||
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
|
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
|
||||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/');
|
UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", fileName).Replace('\\', '/');
|
||||||
|
|
||||||
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
||||||
|
|
||||||
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
|
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
|
||||||
SaveCheck(directory, fileName, updateUi);
|
SaveCheck(directory, fileName, updateUi);
|
||||||
|
|
@ -390,28 +451,25 @@ public class TabControlViewModel : ViewModel
|
||||||
|
|
||||||
public TabControlViewModel()
|
public TabControlViewModel()
|
||||||
{
|
{
|
||||||
_tabItems = new ObservableCollection<TabItem>(EnumerateTabs());
|
_tabItems = [];
|
||||||
TabsItems = new ReadOnlyObservableCollection<TabItem>(_tabItems);
|
TabsItems = new ReadOnlyObservableCollection<TabItem>(_tabItems);
|
||||||
SelectedTab = TabsItems.FirstOrDefault();
|
AddTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddTab(string header = null, string directory = null, string parentExportType = null)
|
public void AddTab() => AddTab("New Tab");
|
||||||
|
public void AddTab(string title) => AddTab(new FakeGameFile(title));
|
||||||
|
public void AddTab(GameFile entry, string parentExportType = null)
|
||||||
{
|
{
|
||||||
if (!CanAddTabs) return;
|
if (SelectedTab?.Header == "New Tab")
|
||||||
|
|
||||||
var h = header ?? "New Tab";
|
|
||||||
var d = directory ?? string.Empty;
|
|
||||||
var p = parentExportType ?? string.Empty;
|
|
||||||
if (SelectedTab is { Header : "New Tab" })
|
|
||||||
{
|
{
|
||||||
SelectedTab.Header = h;
|
SelectedTab.Entry = entry;
|
||||||
SelectedTab.Directory = d;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CanAddTabs) return;
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
_tabItems.Add(new TabItem(h, d, p));
|
_tabItems.Add(new TabItem(entry, parentExportType ?? string.Empty));
|
||||||
SelectedTab = _tabItems.Last();
|
SelectedTab = _tabItems.Last();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -470,9 +528,4 @@ public class TabControlViewModel : ViewModel
|
||||||
_tabItems.Clear();
|
_tabItems.Clear();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<TabItem> EnumerateTabs()
|
|
||||||
{
|
|
||||||
yield return new TabItem("New Tab", string.Empty, string.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,6 @@ public class ThreadWorkerViewModel : ViewModel
|
||||||
|
|
||||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||||
private readonly AsyncQueue<Action<CancellationToken>> _jobs;
|
private readonly AsyncQueue<Action<CancellationToken>> _jobs;
|
||||||
private const string _at = " at ";
|
|
||||||
private const char _dot = '.';
|
|
||||||
private const char _colon = ':';
|
|
||||||
private const string _gray = "#999";
|
|
||||||
|
|
||||||
public ThreadWorkerViewModel()
|
public ThreadWorkerViewModel()
|
||||||
{
|
{
|
||||||
|
|
@ -104,37 +100,7 @@ public class ThreadWorkerViewModel : ViewModel
|
||||||
CurrentCancellationTokenSource = null; // kill token
|
CurrentCancellationTokenSource = null; // kill token
|
||||||
|
|
||||||
Log.Error("{Exception}", e);
|
Log.Error("{Exception}", e);
|
||||||
|
FLogger.Append(e);
|
||||||
FLogger.Append(ELog.Error, () =>
|
|
||||||
{
|
|
||||||
if ((e.InnerException ?? e) is { TargetSite.DeclaringType: not null } exception)
|
|
||||||
{
|
|
||||||
if (exception.TargetSite.ToString() == "CUE4Parse.FileProvider.GameFile get_Item(System.String)")
|
|
||||||
{
|
|
||||||
FLogger.Text(e.Message, Constants.WHITE, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var t = exception.GetType();
|
|
||||||
FLogger.Text(t.Namespace + _dot, Constants.GRAY);
|
|
||||||
FLogger.Text(t.Name, Constants.WHITE);
|
|
||||||
FLogger.Text(_colon + " ", Constants.GRAY);
|
|
||||||
FLogger.Text(exception.Message, Constants.RED, true);
|
|
||||||
|
|
||||||
FLogger.Text(_at, _gray);
|
|
||||||
FLogger.Text(exception.TargetSite.DeclaringType.FullName + _dot, Constants.GRAY);
|
|
||||||
FLogger.Text(exception.TargetSite.Name, Constants.YELLOW);
|
|
||||||
|
|
||||||
var p = exception.TargetSite.GetParameters();
|
|
||||||
var parameters = new string[p.Length];
|
|
||||||
for (int i = 0; i < parameters.Length; i++)
|
|
||||||
{
|
|
||||||
parameters[i] = p[i].ParameterType.Name + " " + p[i].Name;
|
|
||||||
}
|
|
||||||
FLogger.Text("(" + string.Join(", ", parameters) + ")", Constants.GRAY, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using FModel.Extensions;
|
using CUE4Parse.Utils;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
using FModel.Settings;
|
using FModel.Settings;
|
||||||
|
|
@ -13,7 +15,7 @@ using FModel.Views.Resources.Converters;
|
||||||
|
|
||||||
namespace FModel.ViewModels;
|
namespace FModel.ViewModels;
|
||||||
|
|
||||||
public class UpdateViewModel : ViewModel
|
public partial class UpdateViewModel : ViewModel
|
||||||
{
|
{
|
||||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||||
|
|
||||||
|
|
@ -25,7 +27,7 @@ public class UpdateViewModel : ViewModel
|
||||||
|
|
||||||
public UpdateViewModel()
|
public UpdateViewModel()
|
||||||
{
|
{
|
||||||
Commits = new RangeObservableCollection<GitHubCommit>();
|
Commits = [];
|
||||||
CommitsView = new ListCollectionView(Commits)
|
CommitsView = new ListCollectionView(Commits)
|
||||||
{
|
{
|
||||||
GroupDescriptions = { new PropertyGroupDescription("Commit.Author.Date", new DateTimeToDateConverter()) }
|
GroupDescriptions = { new PropertyGroupDescription("Commit.Author.Date", new DateTimeToDateConverter()) }
|
||||||
|
|
@ -35,40 +37,121 @@ public class UpdateViewModel : ViewModel
|
||||||
RemindMeCommand.Execute(this, null);
|
RemindMeCommand.Execute(this, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Load()
|
public async Task LoadAsync()
|
||||||
{
|
{
|
||||||
Commits.AddRange(await _apiEndpointView.GitHubApi.GetCommitHistoryAsync());
|
var commits = await _apiEndpointView.GitHubApi.GetCommitHistoryAsync();
|
||||||
|
if (commits == null || commits.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
var qa = await _apiEndpointView.GitHubApi.GetReleaseAsync("qa");
|
Commits.AddRange(commits);
|
||||||
qa.Assets.OrderByDescending(x => x.CreatedAt).First().IsLatest = true;
|
|
||||||
|
|
||||||
foreach (var asset in qa.Assets)
|
try
|
||||||
{
|
{
|
||||||
var commitSha = asset.Name.SubstringBeforeLast(".zip");
|
_ = LoadCoAuthors();
|
||||||
var commit = Commits.FirstOrDefault(x => x.Sha == commitSha);
|
_ = LoadAssets();
|
||||||
if (commit != null)
|
|
||||||
{
|
|
||||||
commit.Asset = asset;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Commits.Add(new GitHubCommit
|
|
||||||
{
|
|
||||||
Sha = commitSha,
|
|
||||||
Commit = new Commit
|
|
||||||
{
|
|
||||||
Message = $"FModel ({commitSha[..7]})",
|
|
||||||
Author = new Author { Name = asset.Uploader.Login, Date = asset.CreatedAt }
|
|
||||||
},
|
|
||||||
Author = asset.Uploader,
|
|
||||||
Asset = asset
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task LoadCoAuthors()
|
||||||
|
{
|
||||||
|
return Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var coAuthorMap = new Dictionary<GitHubCommit, HashSet<string>>();
|
||||||
|
foreach (var commit in Commits)
|
||||||
|
{
|
||||||
|
if (!commit.Commit.Message.Contains("Co-authored-by"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var regex = GetCoAuthorRegex();
|
||||||
|
var matches = regex.Matches(commit.Commit.Message);
|
||||||
|
if (matches.Count == 0) continue;
|
||||||
|
|
||||||
|
commit.Commit.Message = regex.Replace(commit.Commit.Message, string.Empty).Trim();
|
||||||
|
|
||||||
|
coAuthorMap[commit] = [];
|
||||||
|
foreach (Match match in matches)
|
||||||
|
{
|
||||||
|
if (match.Groups.Count < 3) continue;
|
||||||
|
coAuthorMap[commit].Add(match.Groups[1].Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coAuthorMap.Count == 0) return;
|
||||||
|
|
||||||
|
var uniqueUsernames = coAuthorMap.Values.SelectMany(x => x).Distinct().ToArray();
|
||||||
|
var authorCache = new Dictionary<string, Author>();
|
||||||
|
foreach (var username in uniqueUsernames)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var author = await _apiEndpointView.GitHubApi.GetUserAsync(username);
|
||||||
|
if (author != null)
|
||||||
|
authorCache[username] = author;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (commit, usernames) in coAuthorMap)
|
||||||
|
{
|
||||||
|
var coAuthors = usernames
|
||||||
|
.Where(username => authorCache.ContainsKey(username))
|
||||||
|
.Select(username => authorCache[username])
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (coAuthors.Length > 0)
|
||||||
|
commit.CoAuthors = coAuthors;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task LoadAssets()
|
||||||
|
{
|
||||||
|
return Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var qa = await _apiEndpointView.GitHubApi.GetReleaseAsync("qa");
|
||||||
|
var assets = qa.Assets.OrderByDescending(x => x.CreatedAt).ToList();
|
||||||
|
|
||||||
|
for (var i = 0; i < assets.Count; i++)
|
||||||
|
{
|
||||||
|
var asset = assets[i];
|
||||||
|
asset.IsLatest = i == 0;
|
||||||
|
|
||||||
|
var commitSha = asset.Name.SubstringBeforeLast(".zip");
|
||||||
|
var commit = Commits.FirstOrDefault(x => x.Sha == commitSha);
|
||||||
|
if (commit != null)
|
||||||
|
{
|
||||||
|
commit.Asset = asset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Commits.Add(new GitHubCommit
|
||||||
|
{
|
||||||
|
Sha = commitSha,
|
||||||
|
Commit = new Commit
|
||||||
|
{
|
||||||
|
Message = $"FModel ({commitSha[..7]})",
|
||||||
|
Author = new Author { Name = asset.Uploader.Login, Date = asset.CreatedAt }
|
||||||
|
},
|
||||||
|
Author = asset.Uploader,
|
||||||
|
Asset = asset
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DownloadLatest()
|
public void DownloadLatest()
|
||||||
{
|
{
|
||||||
Commits.FirstOrDefault(x => x.Asset.IsLatest)?.Download();
|
Commits.FirstOrDefault(x => x.IsDownloadable && x.Asset.IsLatest)?.Download();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"Co-authored-by:\s*(.+?)\s*<(.+?)>", RegexOptions.IgnoreCase | RegexOptions.Multiline, "en-US")]
|
||||||
|
private static partial Regex GetCoAuthorRegex();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,6 @@
|
||||||
<Setter Property="Title" Value="About" />
|
<Setter Property="Title" Value="About" />
|
||||||
</Style>
|
</Style>
|
||||||
</adonisControls:AdonisWindow.Style>
|
</adonisControls:AdonisWindow.Style>
|
||||||
<adonisControls:AdonisWindow.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="Resources/Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</adonisControls:AdonisWindow.Resources>
|
|
||||||
<StackPanel Margin="30 10">
|
<StackPanel Margin="30 10">
|
||||||
<StackPanel HorizontalAlignment="Center" Margin="0 0 0 30">
|
<StackPanel HorizontalAlignment="Center" Margin="0 0 0 30">
|
||||||
<TextBlock Text="{Binding Source={x:Static local:Constants.APP_VERSION}, StringFormat={}FModel {0}}" FontSize="15" FontWeight="500" Foreground="#9DA3DD" FontStretch="Expanded" />
|
<TextBlock Text="{Binding Source={x:Static local:Constants.APP_VERSION}, StringFormat={}FModel {0}}" FontSize="15" FontWeight="500" Foreground="#9DA3DD" FontStretch="Expanded" />
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,6 @@
|
||||||
<Setter Property="Title" Value="AES Manager" />
|
<Setter Property="Title" Value="AES Manager" />
|
||||||
</Style>
|
</Style>
|
||||||
</adonisControls:AdonisWindow.Style>
|
</adonisControls:AdonisWindow.Style>
|
||||||
<adonisControls:AdonisWindow.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="Resources/Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</adonisControls:AdonisWindow.Resources>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,6 @@
|
||||||
<Setter Property="Title" Value="Audio Player" />
|
<Setter Property="Title" Value="Audio Player" />
|
||||||
</Style>
|
</Style>
|
||||||
</adonisControls:AdonisWindow.Style>
|
</adonisControls:AdonisWindow.Style>
|
||||||
<adonisControls:AdonisWindow.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="Resources/Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</adonisControls:AdonisWindow.Resources>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="350" />
|
<ColumnDefinition Width="350" />
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,6 @@
|
||||||
<Setter Property="Title" Value="Backup Manager" />
|
<Setter Property="Title" Value="Backup Manager" />
|
||||||
</Style>
|
</Style>
|
||||||
</adonisControls:AdonisWindow.Style>
|
</adonisControls:AdonisWindow.Style>
|
||||||
<adonisControls:AdonisWindow.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="Resources/Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</adonisControls:AdonisWindow.Resources>
|
|
||||||
<Grid Column="2" adonisExtensions:LayerExtension.Layer="2" Margin="10" Background="Transparent">
|
<Grid Column="2" adonisExtensions:LayerExtension.Layer="2" Margin="10" Background="Transparent">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,6 @@
|
||||||
<Setter Property="Title" Value="Directory Selector" />
|
<Setter Property="Title" Value="Directory Selector" />
|
||||||
</Style>
|
</Style>
|
||||||
</adonisControls:AdonisWindow.Style>
|
</adonisControls:AdonisWindow.Style>
|
||||||
<adonisControls:AdonisWindow.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="Resources/Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</adonisControls:AdonisWindow.Resources>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,6 @@
|
||||||
<Setter Property="Title" Value="Image Merger"/>
|
<Setter Property="Title" Value="Image Merger"/>
|
||||||
</Style>
|
</Style>
|
||||||
</adonisControls:AdonisWindow.Style>
|
</adonisControls:AdonisWindow.Style>
|
||||||
<adonisControls:AdonisWindow.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="Resources/Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</adonisControls:AdonisWindow.Resources>
|
|
||||||
|
|
||||||
<Grid Margin="10">
|
<Grid Margin="10">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
|
|
|
||||||
35
FModel/Views/Resources/Colors.xaml
Normal file
35
FModel/Views/Resources/Colors.xaml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<SolidColorBrush x:Key="NeutralBrush" Color="White" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="BlueprintBrush" Color="DodgerBlue" />
|
||||||
|
<SolidColorBrush x:Key="BlueprintWidgetBrush" Color="DarkViolet" />
|
||||||
|
<SolidColorBrush x:Key="BlueprintAnimBrush" Color="Crimson" />
|
||||||
|
<SolidColorBrush x:Key="BlueprintRigVMBrush" Color="Teal" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="CookedMetaDataBrush" Color="Yellow" />
|
||||||
|
<SolidColorBrush x:Key="UserDefinedEnumBrush" Color="DarkGoldenrod" />
|
||||||
|
<SolidColorBrush x:Key="UserDefinedStructBrush" Color="Tan" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="MaterialBrush" Color="BurlyWood" />
|
||||||
|
<SolidColorBrush x:Key="MaterialEditorBrush" Color="Yellow" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="BinaryBrush" Color="Yellow" />
|
||||||
|
<SolidColorBrush x:Key="TextureBrush" Color="MediumPurple" />
|
||||||
|
<SolidColorBrush x:Key="ConfigBrush" Color="LightSlateGray" />
|
||||||
|
<SolidColorBrush x:Key="AudioBrush" Color="MediumSeaGreen" />
|
||||||
|
<SolidColorBrush x:Key="VideoBrush" Color="IndianRed" />
|
||||||
|
<SolidColorBrush x:Key="DataTableBrush" Color="SteelBlue" />
|
||||||
|
<SolidColorBrush x:Key="CurveBrush" Color="HotPink" />
|
||||||
|
<SolidColorBrush x:Key="PluginBrush" Color="GreenYellow" />
|
||||||
|
<SolidColorBrush x:Key="ProjectBrush" Color="DeepSkyBlue" />
|
||||||
|
<SolidColorBrush x:Key="LocalizationBrush" Color="CornflowerBlue" />
|
||||||
|
<SolidColorBrush x:Key="WorldBrush" Color="Orange" />
|
||||||
|
<SolidColorBrush x:Key="BuildDataBrush" Color="Tomato" />
|
||||||
|
<SolidColorBrush x:Key="LevelSequenceBrush" Color="Coral" />
|
||||||
|
<SolidColorBrush x:Key="FoliageBrush" Color="ForestGreen" />
|
||||||
|
<SolidColorBrush x:Key="ParticleBrush" Color="Gold" />
|
||||||
|
<SolidColorBrush x:Key="AnimationBrush" Color="Coral" />
|
||||||
|
<SolidColorBrush x:Key="LuaBrush" Color="DarkBlue" />
|
||||||
|
<SolidColorBrush x:Key="JsonXmlBrush" Color="LightGreen" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Text.RegularExpressions;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.TextFormatting;
|
using System.Windows.Media.TextFormatting;
|
||||||
|
using CUE4Parse.Utils;
|
||||||
using FModel.Extensions;
|
using FModel.Extensions;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
using FModel.ViewModels;
|
using FModel.ViewModels;
|
||||||
|
|
@ -69,31 +70,24 @@ public class GamePathVisualLineText : VisualLineText
|
||||||
{
|
{
|
||||||
var obj = gamePath.SubstringAfterLast('.');
|
var obj = gamePath.SubstringAfterLast('.');
|
||||||
var package = gamePath.SubstringBeforeLast('.');
|
var package = gamePath.SubstringBeforeLast('.');
|
||||||
var fullPath = _applicationView.CUE4Parse.Provider.FixPath(package, StringComparison.Ordinal);
|
var fullPath = _applicationView.CUE4Parse.Provider.FixPath(package);
|
||||||
if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
int lineNumber;
|
|
||||||
DocumentLine line;
|
|
||||||
|
|
||||||
if (Regex.IsMatch(obj, @"^(.+)\[(\d+)\]$"))
|
var firstLine = a.ParentVisualLine.Document.GetLineByNumber(2);
|
||||||
{
|
if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase) &&
|
||||||
lineNumber = a.ParentVisualLine.Document.Text.GetKismetLineNumber(obj);
|
!a.ParentVisualLine.Document.GetText(firstLine.Offset, firstLine.Length).Equals(" \"Summary\": {")) // Show Metadata case
|
||||||
line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumber(obj);
|
|
||||||
line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
|
|
||||||
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
await _threadWorkerView.Begin(cancellationToken =>
|
var lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumber(obj);
|
||||||
_applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType));
|
if (lineNumber > -1)
|
||||||
|
{
|
||||||
|
var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
|
||||||
|
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
|
||||||
|
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
|
_applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType));
|
||||||
};
|
};
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
FModel/Views/Resources/Controls/Aed/JumpElementGenerator.cs
Normal file
34
FModel/Views/Resources/Controls/Aed/JumpElementGenerator.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using ICSharpCode.AvalonEdit.Rendering;
|
||||||
|
|
||||||
|
namespace FModel.Views.Resources.Controls;
|
||||||
|
|
||||||
|
public class JumpElementGenerator : VisualLineElementGenerator
|
||||||
|
{
|
||||||
|
private readonly Regex _JumpRegex = new(
|
||||||
|
@"\b(?:goto\s+Label_(?'target'\d+);)",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
private Match FindMatch(int startOffset)
|
||||||
|
{
|
||||||
|
var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
|
||||||
|
var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);
|
||||||
|
return _JumpRegex.Match(relevantText);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ||
|
||||||
|
!m.Groups.TryGetValue("target", out var g))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new JumpVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
75
FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs
Normal file
75
FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.TextFormatting;
|
||||||
|
using FModel.Extensions;
|
||||||
|
using FModel.Services;
|
||||||
|
using FModel.ViewModels;
|
||||||
|
using ICSharpCode.AvalonEdit.Rendering;
|
||||||
|
|
||||||
|
namespace FModel.Views.Resources.Controls;
|
||||||
|
|
||||||
|
public class JumpVisualLineText : VisualLineText
|
||||||
|
{
|
||||||
|
public delegate void JumpOnClick(string Jump);
|
||||||
|
|
||||||
|
public event JumpOnClick OnJumpClicked;
|
||||||
|
private readonly string _jump;
|
||||||
|
|
||||||
|
public JumpVisualLineText(string jump, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
|
||||||
|
{
|
||||||
|
_jump = jump;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
|
||||||
|
var relativeOffset = startVisualColumn - VisualColumn;
|
||||||
|
var text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset + relativeOffset, DocumentLength - relativeOffset);
|
||||||
|
|
||||||
|
if (text.Count != 2) // ": "
|
||||||
|
TextRunProperties.SetForegroundBrush(Brushes.Plum);
|
||||||
|
|
||||||
|
return new TextCharacters(text.Text, text.Offset, text.Count, TextRunProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool JumpIsClickable() => !string.IsNullOrEmpty(_jump) && Keyboard.Modifiers == ModifierKeys.None;
|
||||||
|
|
||||||
|
protected override void OnQueryCursor(QueryCursorEventArgs e)
|
||||||
|
{
|
||||||
|
if (!JumpIsClickable())
|
||||||
|
return;
|
||||||
|
e.Handled = true;
|
||||||
|
e.Cursor = Cursors.Hand;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.ChangedButton != MouseButton.Left || !JumpIsClickable())
|
||||||
|
return;
|
||||||
|
if (e.Handled || OnJumpClicked == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
OnJumpClicked(_jump);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override VisualLineText CreateInstance(int length)
|
||||||
|
{
|
||||||
|
var a = new JumpVisualLineText(_jump, ParentVisualLine, length);
|
||||||
|
a.OnJumpClicked += jump =>
|
||||||
|
{
|
||||||
|
var lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumberText($" Label_{jump}:"); // impossible for different indentation
|
||||||
|
if (lineNumber > -1)
|
||||||
|
{
|
||||||
|
var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
|
||||||
|
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
|
||||||
|
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -5,13 +5,6 @@
|
||||||
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
|
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
|
||||||
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
||||||
PreviewKeyDown="OnPreviewKeyDown">
|
PreviewKeyDown="OnPreviewKeyDown">
|
||||||
<UserControl.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="../Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</UserControl.Resources>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ public partial class AvalonEditor
|
||||||
MyAvalonEditor.TextArea.TextView.LinkTextBackgroundBrush = null;
|
MyAvalonEditor.TextArea.TextView.LinkTextBackgroundBrush = null;
|
||||||
MyAvalonEditor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Cornsilk;
|
MyAvalonEditor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Cornsilk;
|
||||||
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator());
|
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator());
|
||||||
|
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new JumpElementGenerator());
|
||||||
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator());
|
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator());
|
||||||
|
|
||||||
ApplicationService.ApplicationView.CUE4Parse.TabControl.OnTabRemove += OnTabClose;
|
ApplicationService.ApplicationView.CUE4Parse.TabControl.OnTabRemove += OnTabClose;
|
||||||
|
|
@ -119,7 +120,7 @@ public partial class AvalonEditor
|
||||||
if (sender is not TextEditor avalonEditor || DataContext is not TabItem tabItem ||
|
if (sender is not TextEditor avalonEditor || DataContext is not TabItem tabItem ||
|
||||||
avalonEditor.Document == null || string.IsNullOrEmpty(avalonEditor.Document.Text))
|
avalonEditor.Document == null || string.IsNullOrEmpty(avalonEditor.Document.Text))
|
||||||
return;
|
return;
|
||||||
avalonEditor.Document.FileName = tabItem.Directory + '/' + StringExtensions.SubstringBeforeLast(tabItem.Header, '.');
|
avalonEditor.Document.FileName = tabItem.Entry.PathWithoutExtension;
|
||||||
|
|
||||||
if (!_savedCarets.ContainsKey(avalonEditor.Document.FileName))
|
if (!_savedCarets.ContainsKey(avalonEditor.Document.FileName))
|
||||||
_ignoreCaret = true;
|
_ignoreCaret = true;
|
||||||
|
|
@ -127,6 +128,8 @@ public partial class AvalonEditor
|
||||||
if (!tabItem.ShouldScroll) return;
|
if (!tabItem.ShouldScroll) return;
|
||||||
|
|
||||||
var lineNumber = avalonEditor.Document.Text.GetNameLineNumber(tabItem.ScrollTrigger);
|
var lineNumber = avalonEditor.Document.Text.GetNameLineNumber(tabItem.ScrollTrigger);
|
||||||
|
if (lineNumber == -1) lineNumber = 1;
|
||||||
|
|
||||||
var line = avalonEditor.Document.GetLineByNumber(lineNumber);
|
var line = avalonEditor.Document.GetLineByNumber(lineNumber);
|
||||||
avalonEditor.Select(line.Offset, line.Length);
|
avalonEditor.Select(line.Offset, line.Length);
|
||||||
avalonEditor.ScrollToLine(lineNumber);
|
avalonEditor.ScrollToLine(lineNumber);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,5 @@
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
DataContextChanged="OnDataContextChanged">
|
DataContextChanged="OnDataContextChanged">
|
||||||
<UserControl.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="../Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</UserControl.Resources>
|
|
||||||
<StackPanel x:Name="InMeDaddy" Orientation="Horizontal" HorizontalAlignment="Right" Height="24" />
|
<StackPanel x:Name="InMeDaddy" Orientation="Horizontal" HorizontalAlignment="Right" Height="24" />
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
|
@ -10,7 +10,7 @@ namespace FModel.Views.Resources.Controls;
|
||||||
|
|
||||||
public partial class Breadcrumb
|
public partial class Breadcrumb
|
||||||
{
|
{
|
||||||
private const string _NAVIGATE_NEXT = "M9.31 6.71c-.39.39-.39 1.02 0 1.41L13.19 12l-3.88 3.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0l4.59-4.59c.39-.39.39-1.02 0-1.41L10.72 6.7c-.38-.38-1.02-.38-1.41.01z";
|
private const string NavigateNext = "M9.31 6.71c-.39.39-.39 1.02 0 1.41L13.19 12l-3.88 3.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0l4.59-4.59c.39-.39.39-1.02 0-1.41L10.72 6.7c-.38-.38-1.02-.38-1.41.01z";
|
||||||
|
|
||||||
public Breadcrumb()
|
public Breadcrumb()
|
||||||
{
|
{
|
||||||
|
|
@ -25,17 +25,27 @@ public partial class Breadcrumb
|
||||||
var folders = pathAtThisPoint.Split('/');
|
var folders = pathAtThisPoint.Split('/');
|
||||||
for (var i = 0; i < folders.Length; i++)
|
for (var i = 0; i < folders.Length; i++)
|
||||||
{
|
{
|
||||||
var textBlock = new TextBlock
|
var border = new Border
|
||||||
{
|
{
|
||||||
Text = folders[i],
|
BorderThickness = new Thickness(1),
|
||||||
|
BorderBrush = Brushes.Transparent,
|
||||||
Background = Brushes.Transparent,
|
Background = Brushes.Transparent,
|
||||||
|
Padding = new Thickness(6, 3, 6, 3),
|
||||||
Cursor = Cursors.Hand,
|
Cursor = Cursors.Hand,
|
||||||
Tag = i + 1,
|
Tag = i + 1,
|
||||||
Margin = new Thickness(0, 3, 0, 0)
|
IsEnabled = i < folders.Length - 1,
|
||||||
|
Child = new TextBlock
|
||||||
|
{
|
||||||
|
Text = folders[i],
|
||||||
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
|
}
|
||||||
};
|
};
|
||||||
textBlock.MouseUp += OnMouseClick;
|
|
||||||
|
|
||||||
InMeDaddy.Children.Add(textBlock);
|
border.MouseEnter += OnMouseEnter;
|
||||||
|
border.MouseLeave += OnMouseLeave;
|
||||||
|
border.MouseUp += OnMouseClick;
|
||||||
|
|
||||||
|
InMeDaddy.Children.Add(border);
|
||||||
if (i >= folders.Length - 1) continue;
|
if (i >= folders.Length - 1) continue;
|
||||||
|
|
||||||
InMeDaddy.Children.Add(new Viewbox
|
InMeDaddy.Children.Add(new Viewbox
|
||||||
|
|
@ -52,7 +62,8 @@ public partial class Breadcrumb
|
||||||
new Path
|
new Path
|
||||||
{
|
{
|
||||||
Fill = Brushes.White,
|
Fill = Brushes.White,
|
||||||
Data = Geometry.Parse(_NAVIGATE_NEXT)
|
Data = Geometry.Parse(NavigateNext),
|
||||||
|
Opacity = 0.6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -60,13 +71,31 @@ public partial class Breadcrumb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnMouseEnter(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Border border)
|
||||||
|
{
|
||||||
|
border.BorderBrush = new SolidColorBrush(Color.FromRgb(127, 127, 144));
|
||||||
|
border.Background = new SolidColorBrush(Color.FromRgb(72, 73, 92));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMouseLeave(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Border border)
|
||||||
|
{
|
||||||
|
border.BorderBrush = Brushes.Transparent;
|
||||||
|
border.Background = Brushes.Transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnMouseClick(object sender, MouseButtonEventArgs e)
|
private void OnMouseClick(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is not TextBlock { DataContext: string pathAtThisPoint, Tag: int index }) return;
|
if (sender is not Border { DataContext: string pathAtThisPoint, Tag: int index }) return;
|
||||||
|
|
||||||
var directory = string.Join('/', pathAtThisPoint.Split('/').Take(index));
|
var directory = string.Join('/', pathAtThisPoint.Split('/').Take(index));
|
||||||
if (pathAtThisPoint.Equals(directory)) return;
|
if (pathAtThisPoint.Equals(directory)) return;
|
||||||
|
|
||||||
ApplicationService.ApplicationView.CustomDirectories.GoToCommand.JumpTo(directory);
|
ApplicationService.ApplicationView.CustomDirectories.GoToCommand.JumpTo(directory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,6 @@
|
||||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||||
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls">
|
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls">
|
||||||
<UserControl.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="../Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</UserControl.Resources>
|
|
||||||
<Border BorderThickness="1" CornerRadius="0.5"
|
<Border BorderThickness="1" CornerRadius="0.5"
|
||||||
BorderBrush="{DynamicResource {x:Static adonisUi:Brushes.Layer4BackgroundBrush}}">
|
BorderBrush="{DynamicResource {x:Static adonisUi:Brushes.Layer4BackgroundBrush}}">
|
||||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||||
|
|
@ -42,22 +35,33 @@
|
||||||
|
|
||||||
<Grid Grid.Row="3">
|
<Grid Grid.Row="3">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="16"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="5"/>
|
<ColumnDefinition Width="5"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="5"/>
|
<ColumnDefinition Width="5"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<Ellipse Grid.Column="0">
|
<ItemsControl Grid.Column="0" ItemsSource="{Binding Authors}">
|
||||||
<Ellipse.Fill>
|
<ItemsControl.ItemsPanel>
|
||||||
<ImageBrush ImageSource="{Binding Author.AvatarUrl}" />
|
<ItemsPanelTemplate>
|
||||||
</Ellipse.Fill>
|
<StackPanel Orientation="Horizontal" />
|
||||||
</Ellipse>
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Ellipse Width="16" Height="16" Margin="0,0,2,0">
|
||||||
|
<Ellipse.Fill>
|
||||||
|
<ImageBrush ImageSource="{Binding AvatarUrl}" />
|
||||||
|
</Ellipse.Fill>
|
||||||
|
</Ellipse>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
<TextBlock Grid.Column="2" FontSize="11">
|
<TextBlock Grid.Column="2" FontSize="11">
|
||||||
<TextBlock.Text>
|
<TextBlock.Text>
|
||||||
<MultiBinding StringFormat="{}{0} committed {1}">
|
<MultiBinding StringFormat="{}{0} committed {1}">
|
||||||
<Binding Path="Author.Login" />
|
<Binding Path="AuthorNames" />
|
||||||
<Binding Path="Commit.Author.Date" Converter="{x:Static converters:RelativeDateTimeConverter.Instance}" />
|
<Binding Path="Commit.Author.Date" Converter="{x:Static converters:RelativeDateTimeConverter.Instance}" />
|
||||||
</MultiBinding>
|
</MultiBinding>
|
||||||
</TextBlock.Text>
|
</TextBlock.Text>
|
||||||
|
|
@ -65,7 +69,7 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Column="1">
|
<Grid Grid.Column="1" MaxHeight="96">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="15" />
|
<ColumnDefinition Width="15" />
|
||||||
|
|
@ -128,4 +132,3 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,6 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI">
|
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI">
|
||||||
<UserControl.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="../Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</UserControl.Resources>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,305 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||||
|
xmlns:settings="clr-namespace:FModel.Settings"
|
||||||
|
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters">
|
||||||
|
<ContextMenu x:Key="FileContextMenu" x:Shared="False"
|
||||||
|
DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=Window}}">
|
||||||
|
<MenuItem Header="Extract in New Tab" Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Assets_Extract_New_Tab" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExtractIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Show Metadata" Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Assets_Show_Metadata" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.IsEnabled>
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
|
||||||
|
<Binding.Converter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
<converters:ItemIsUePackageCondition />
|
||||||
|
</converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
</converters:AnyItemMeetsConditionConverter>
|
||||||
|
</Binding.Converter>
|
||||||
|
</Binding>
|
||||||
|
</MenuItem.IsEnabled>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Find References" Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Assets_Show_References" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.IsEnabled>
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
|
||||||
|
<Binding.Converter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
<converters:ItemIsUePackageCondition />
|
||||||
|
<converters:ItemIsIoStoreCondition />
|
||||||
|
</converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
</converters:AnyItemMeetsConditionConverter>
|
||||||
|
</Binding.Converter>
|
||||||
|
</Binding>
|
||||||
|
</MenuItem.IsEnabled>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Decompile Blueprint"
|
||||||
|
Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Assets_Decompile" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.IsEnabled>
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
|
||||||
|
<Binding.Converter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
<converters:ItemCategoryCondition Category="Blueprint" />
|
||||||
|
</converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
</converters:AnyItemMeetsConditionConverter>
|
||||||
|
</Binding.Converter>
|
||||||
|
</Binding>
|
||||||
|
</MenuItem.IsEnabled>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CppIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
<MenuItem.Style>
|
||||||
|
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding ShowDecompileOption, Source={x:Static settings:UserSettings.Default}}" Value="False">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</MenuItem.Style>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.Header>
|
||||||
|
<TextBlock
|
||||||
|
Text="{Binding PlacementTarget.SelectedItem.Asset.Extension,
|
||||||
|
FallbackValue='Export Raw Data',
|
||||||
|
StringFormat='Export Raw Data (.{0})',
|
||||||
|
RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||||
|
</MenuItem.Header>
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Assets_Export_Data" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Save Properties (.json)" Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Assets_Save_Properties" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Save Texture" Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Assets_Save_Textures" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.IsEnabled>
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
|
||||||
|
<Binding.Converter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
<converters:ItemCategoryCondition Category="Texture" />
|
||||||
|
</converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
</converters:AnyItemMeetsConditionConverter>
|
||||||
|
</Binding.Converter>
|
||||||
|
</Binding>
|
||||||
|
</MenuItem.IsEnabled>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Save Model" Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Assets_Save_Models" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.IsEnabled>
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
|
||||||
|
<Binding.Converter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
<converters:ItemCategoryCondition Category="Mesh" />
|
||||||
|
</converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
</converters:AnyItemMeetsConditionConverter>
|
||||||
|
</Binding.Converter>
|
||||||
|
</Binding>
|
||||||
|
</MenuItem.IsEnabled>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Save Animation" Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Assets_Save_Animations" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.IsEnabled>
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
|
||||||
|
<Binding.Converter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
<converters:ItemCategoryCondition Category="Animation" />
|
||||||
|
</converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
</converters:AnyItemMeetsConditionConverter>
|
||||||
|
</Binding.Converter>
|
||||||
|
</Binding>
|
||||||
|
</MenuItem.IsEnabled>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Save Audio" Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Assets_Save_Audio" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.IsEnabled>
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
|
||||||
|
<Binding.Converter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter>
|
||||||
|
<converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
<converters:ItemCategoryCondition Category="Audio" />
|
||||||
|
</converters:AnyItemMeetsConditionConverter.Conditions>
|
||||||
|
</converters:AnyItemMeetsConditionConverter>
|
||||||
|
</Binding.Converter>
|
||||||
|
</Binding>
|
||||||
|
</MenuItem.IsEnabled>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Copy">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
<MenuItem Header="Package Path" Command="{Binding CopyCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="File_Path" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Package Name" Command="{Binding CopyCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="File_Name" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Directory Path" Command="{Binding CopyCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Directory_Path" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Package Path w/o Extension" Command="{Binding CopyCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="File_Path_No_Extension" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Package Name w/o Extension" Command="{Binding CopyCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="File_Name_No_Extension" />
|
||||||
|
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
</MenuItem>
|
||||||
|
</MenuItem>
|
||||||
|
</ContextMenu>
|
||||||
|
</ResourceDictionary>
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||||
|
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||||
|
x:Class="FModel.Views.Resources.Controls.ContextMenus.FolderContextMenuDictionary">
|
||||||
|
<ContextMenu x:Key="FolderContextMenu" x:Shared="False"
|
||||||
|
Opened="FolderContextMenu_OnOpened">
|
||||||
|
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)"
|
||||||
|
Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Folders_Export_Data" />
|
||||||
|
<Binding Path="Tag"
|
||||||
|
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16"
|
||||||
|
Height="16">
|
||||||
|
<Canvas Width="24"
|
||||||
|
Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
|
||||||
|
Data="{StaticResource ExportIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Save Folder's Packages Properties (.json)"
|
||||||
|
Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Folders_Save_Properties" />
|
||||||
|
<Binding Path="Tag"
|
||||||
|
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16"
|
||||||
|
Height="16">
|
||||||
|
<Canvas Width="24"
|
||||||
|
Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
|
||||||
|
Data="{StaticResource SaveIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Save Folder's Packages Textures"
|
||||||
|
Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Folders_Save_Textures" />
|
||||||
|
<Binding Path="Tag"
|
||||||
|
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16"
|
||||||
|
Height="16">
|
||||||
|
<Canvas Width="24"
|
||||||
|
Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
|
||||||
|
Data="{StaticResource TextureIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Save Folder's Packages Models"
|
||||||
|
Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Folders_Save_Models" />
|
||||||
|
<Binding Path="Tag"
|
||||||
|
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16"
|
||||||
|
Height="16">
|
||||||
|
<Canvas Width="24"
|
||||||
|
Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
|
||||||
|
Data="{StaticResource ModelIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Save Folder's Packages Animations"
|
||||||
|
Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Folders_Save_Animations" />
|
||||||
|
<Binding Path="Tag"
|
||||||
|
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16"
|
||||||
|
Height="16">
|
||||||
|
<Canvas Width="24"
|
||||||
|
Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
|
||||||
|
Data="{StaticResource AnimationIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Save Folder's Packages Audio"
|
||||||
|
Command="{Binding RightClickMenuCommand}">
|
||||||
|
<MenuItem.CommandParameter>
|
||||||
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
|
<Binding Source="Folders_Save_Audio" />
|
||||||
|
<Binding Path="Tag"
|
||||||
|
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</MenuItem.CommandParameter>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16"
|
||||||
|
Height="16">
|
||||||
|
<Canvas Width="24"
|
||||||
|
Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
|
||||||
|
Data="{StaticResource AudioIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Favorite Directory" Click="OnFavoriteDirectoryClick"
|
||||||
|
CommandParameter="{Binding Tag, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoriesAddIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Copy Directory Path" Click="OnCopyDirectoryPathClick"
|
||||||
|
CommandParameter="{Binding Tag, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
</ContextMenu>
|
||||||
|
</ResourceDictionary>
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using FModel.Services;
|
||||||
|
using FModel.Settings;
|
||||||
|
using FModel.ViewModels;
|
||||||
|
|
||||||
|
namespace FModel.Views.Resources.Controls.ContextMenus;
|
||||||
|
|
||||||
|
public partial class FolderContextMenuDictionary
|
||||||
|
{
|
||||||
|
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||||
|
|
||||||
|
public FolderContextMenuDictionary()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FolderContextMenu_OnOpened(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not ContextMenu { PlacementTarget: FrameworkElement fe } menu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var listBox = FindAncestor<ListBox>(fe);
|
||||||
|
if (listBox != null)
|
||||||
|
{
|
||||||
|
menu.DataContext = listBox.DataContext;
|
||||||
|
menu.Tag = listBox.SelectedItems;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var treeView = FindAncestor<TreeView>(fe);
|
||||||
|
if (treeView != null)
|
||||||
|
{
|
||||||
|
menu.DataContext = treeView.DataContext;
|
||||||
|
menu.Tag = new[] { treeView.SelectedItem }.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T FindAncestor<T>(DependencyObject current) where T : DependencyObject
|
||||||
|
{
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
if (current is T t)
|
||||||
|
return t;
|
||||||
|
current = VisualTreeHelper.GetParent(current);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFavoriteDirectoryClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not MenuItem { CommandParameter: IEnumerable<object> list } || list.FirstOrDefault() is not TreeItem folder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
|
||||||
|
FLogger.Append(ELog.Information, () =>
|
||||||
|
FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new favorite directory", Constants.WHITE, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not MenuItem { CommandParameter: IEnumerable<object> list } || list.FirstOrDefault() is not TreeItem folder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Clipboard.SetText(folder.PathAtThisPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,13 +9,6 @@
|
||||||
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" ResizeMode="NoResize" SizeToContent="Width"
|
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" ResizeMode="NoResize" SizeToContent="Width"
|
||||||
MinWidth="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.30'}"
|
MinWidth="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.30'}"
|
||||||
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.20'}">
|
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.20'}">
|
||||||
<adonisControls:AdonisWindow.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="../Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</adonisControls:AdonisWindow.Resources>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,6 @@
|
||||||
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" ResizeMode="NoResize"
|
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" ResizeMode="NoResize"
|
||||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.50'}"
|
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.50'}"
|
||||||
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.35'}">
|
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.35'}">
|
||||||
<adonisControls:AdonisWindow.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="../Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</adonisControls:AdonisWindow.Resources>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,13 @@
|
||||||
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
|
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
|
||||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
||||||
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed">
|
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed">
|
||||||
<adonisControls:AdonisWindow.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="../Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</adonisControls:AdonisWindow.Resources>
|
|
||||||
<DockPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
<DockPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
<controls:MagnifierManager.Magnifier>
|
<controls:MagnifierManager.Magnifier>
|
||||||
<controls:Magnifier Radius="150" ZoomFactor=".7" />
|
<controls:Magnifier Radius="150" ZoomFactor=".7" />
|
||||||
</controls:MagnifierManager.Magnifier>
|
</controls:MagnifierManager.Magnifier>
|
||||||
|
|
||||||
<Image x:Name="ImageCtrl" UseLayoutRounding="True" />
|
<Border BorderBrush="#3b3d4a" BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
|
<Image x:Name="ImageCtrl" UseLayoutRounding="True" />
|
||||||
|
</Border>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</adonisControls:AdonisWindow>
|
</adonisControls:AdonisWindow>
|
||||||
|
|
|
||||||
41
FModel/Views/Resources/Controls/Inputs/SearchTextBox.xaml
Normal file
41
FModel/Views/Resources/Controls/Inputs/SearchTextBox.xaml
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
<UserControl x:Class="FModel.Views.Resources.Controls.Inputs.SearchTextBox"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignWidth="300"
|
||||||
|
x:Name="Root"
|
||||||
|
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||||
|
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Column="0" ZIndex="1" HorizontalAlignment="Left" Margin="5 2 0 0">
|
||||||
|
<Viewbox Width="16" Height="16">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<TextBox x:Name="TextBox" Grid.Column="0" Grid.ColumnSpan="2" AcceptsTab="False" AcceptsReturn="False"
|
||||||
|
Text="{Binding Text, ElementName=Root, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Padding="25 0 0 0" HorizontalAlignment="Stretch"
|
||||||
|
adonisExtensions:WatermarkExtension.Watermark="{Binding Watermark, ElementName=Root}" />
|
||||||
|
|
||||||
|
<Button Grid.Column="1" ToolTip="Clear Search Filter" Padding="5"
|
||||||
|
Click="OnClearButtonClick"
|
||||||
|
Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}">
|
||||||
|
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
||||||
|
<Canvas Width="24" Height="24">
|
||||||
|
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
|
||||||
|
Data="{StaticResource BackspaceIcon}"/>
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
48
FModel/Views/Resources/Controls/Inputs/SearchTextBox.xaml.cs
Normal file
48
FModel/Views/Resources/Controls/Inputs/SearchTextBox.xaml.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace FModel.Views.Resources.Controls.Inputs;
|
||||||
|
|
||||||
|
public partial class SearchTextBox : UserControl
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty TextProperty =
|
||||||
|
DependencyProperty.Register(nameof(Text), typeof(string), typeof(SearchTextBox),
|
||||||
|
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty WatermarkProperty =
|
||||||
|
DependencyProperty.Register(nameof(Watermark), typeof(string), typeof(SearchTextBox),
|
||||||
|
new PropertyMetadata("Search by name..."));
|
||||||
|
|
||||||
|
public static readonly RoutedEvent ClearButtonClickEvent =
|
||||||
|
EventManager.RegisterRoutedEvent(nameof(ClearButtonClick), RoutingStrategy.Bubble,
|
||||||
|
typeof(RoutedEventHandler), typeof(SearchTextBox));
|
||||||
|
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get => (string)GetValue(TextProperty);
|
||||||
|
set => SetValue(TextProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Watermark
|
||||||
|
{
|
||||||
|
get => (string)GetValue(WatermarkProperty);
|
||||||
|
set => SetValue(WatermarkProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event RoutedEventHandler ClearButtonClick
|
||||||
|
{
|
||||||
|
add => AddHandler(ClearButtonClickEvent, value);
|
||||||
|
remove => RemoveHandler(ClearButtonClickEvent, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchTextBox()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClearButtonClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Text = string.Empty;
|
||||||
|
RaiseEvent(new RoutedEventArgs(ClearButtonClickEvent, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
42
FModel/Views/Resources/Controls/ListBoxItemBehavior.cs
Normal file
42
FModel/Views/Resources/Controls/ListBoxItemBehavior.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace FModel.Views.Resources.Controls;
|
||||||
|
|
||||||
|
public sealed class ListBoxItemBehavior
|
||||||
|
{
|
||||||
|
public static bool GetIsBroughtIntoViewWhenSelected(ListBoxItem listBoxItem)
|
||||||
|
{
|
||||||
|
return (bool) listBoxItem.GetValue(IsBroughtIntoViewWhenSelectedProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetIsBroughtIntoViewWhenSelected(ListBoxItem listBoxItem, bool value)
|
||||||
|
{
|
||||||
|
listBoxItem.SetValue(IsBroughtIntoViewWhenSelectedProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty =
|
||||||
|
DependencyProperty.RegisterAttached("IsBroughtIntoViewWhenSelected", typeof(bool), typeof(ListBoxItemBehavior),
|
||||||
|
new UIPropertyMetadata(false, OnIsBroughtIntoViewWhenSelectedChanged));
|
||||||
|
|
||||||
|
private static void OnIsBroughtIntoViewWhenSelectedChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (depObj is not ListBoxItem item)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (e.NewValue is not bool value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
item.Selected += OnListBoxItemSelected;
|
||||||
|
else
|
||||||
|
item.Selected -= OnListBoxItemSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnListBoxItemSelected(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.OriginalSource is ListBoxItem item)
|
||||||
|
item.BringIntoView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -7,13 +7,6 @@
|
||||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
||||||
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" PreviewKeyDown="OnPreviewKeyDown"
|
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" PreviewKeyDown="OnPreviewKeyDown"
|
||||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.40'}">
|
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.40'}">
|
||||||
<adonisControls:AdonisWindow.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="../Resources.xaml"/>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</adonisControls:AdonisWindow.Resources>
|
|
||||||
<DockPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
<DockPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||||
<avalonEdit:TextEditor x:Name="MyAvalonEditor" Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
|
<avalonEdit:TextEditor x:Name="MyAvalonEditor" Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
|
||||||
FontFamily="Consolas" FontSize="8pt" IsReadOnly="True" ShowLineNumbers="True" Foreground="#DAE5F2"
|
FontFamily="Consolas" FontSize="8pt" IsReadOnly="True" ShowLineNumbers="True" Foreground="#DAE5F2"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using FModel.Extensions;
|
using CUE4Parse.Utils;
|
||||||
using FModel.ViewModels;
|
using FModel.ViewModels;
|
||||||
using ICSharpCode.AvalonEdit;
|
using ICSharpCode.AvalonEdit;
|
||||||
using ICSharpCode.AvalonEdit.Document;
|
using ICSharpCode.AvalonEdit.Document;
|
||||||
|
|
@ -24,7 +24,7 @@ public partial class PropertiesPopout
|
||||||
MyAvalonEditor.Document = new TextDocument
|
MyAvalonEditor.Document = new TextDocument
|
||||||
{
|
{
|
||||||
Text = contextViewModel.Document.Text,
|
Text = contextViewModel.Document.Text,
|
||||||
FileName = contextViewModel.Directory + '/' + contextViewModel.Header.SubstringBeforeLast('.')
|
FileName = contextViewModel.Entry.PathWithoutExtension
|
||||||
};
|
};
|
||||||
MyAvalonEditor.FontSize = contextViewModel.FontSize;
|
MyAvalonEditor.FontSize = contextViewModel.FontSize;
|
||||||
MyAvalonEditor.SyntaxHighlighting = contextViewModel.Highlighter;
|
MyAvalonEditor.SyntaxHighlighting = contextViewModel.Highlighter;
|
||||||
|
|
@ -32,6 +32,7 @@ public partial class PropertiesPopout
|
||||||
MyAvalonEditor.TextArea.TextView.LinkTextBackgroundBrush = null;
|
MyAvalonEditor.TextArea.TextView.LinkTextBackgroundBrush = null;
|
||||||
MyAvalonEditor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Cornsilk;
|
MyAvalonEditor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Cornsilk;
|
||||||
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator());
|
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator());
|
||||||
|
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new JumpElementGenerator());
|
||||||
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator());
|
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator());
|
||||||
_manager = new JsonFoldingStrategies(MyAvalonEditor);
|
_manager = new JsonFoldingStrategies(MyAvalonEditor);
|
||||||
_manager.UpdateFoldings(MyAvalonEditor.Document);
|
_manager.UpdateFoldings(MyAvalonEditor.Document);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Windows.Data;
|
||||||
using System.Windows.Documents;
|
using System.Windows.Documents;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
namespace FModel.Views.Resources.Controls;
|
namespace FModel.Views.Resources.Controls;
|
||||||
|
|
||||||
|
|
@ -33,6 +34,11 @@ public class FLogger : ITextFormatter
|
||||||
private static readonly BrushConverter _brushConverter = new();
|
private static readonly BrushConverter _brushConverter = new();
|
||||||
private static int _previous;
|
private static int _previous;
|
||||||
|
|
||||||
|
private const string _at = " at ";
|
||||||
|
private const char _dot = '.';
|
||||||
|
private const char _colon = ':';
|
||||||
|
private const string _gray = "#999";
|
||||||
|
|
||||||
public static void Append(ELog type, Action job)
|
public static void Append(ELog type, Action job)
|
||||||
{
|
{
|
||||||
Application.Current.Dispatcher.Invoke(delegate
|
Application.Current.Dispatcher.Invoke(delegate
|
||||||
|
|
@ -54,6 +60,45 @@ public class FLogger : ITextFormatter
|
||||||
}
|
}
|
||||||
|
|
||||||
job();
|
job();
|
||||||
|
}, DispatcherPriority.Background);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Append(Exception e)
|
||||||
|
{
|
||||||
|
Append(ELog.Error, () =>
|
||||||
|
{
|
||||||
|
if ((e.InnerException ?? e) is { TargetSite.DeclaringType: not null } exception)
|
||||||
|
{
|
||||||
|
if (exception.TargetSite.ToString() == "CUE4Parse.FileProvider.GameFile get_Item(System.String)")
|
||||||
|
{
|
||||||
|
Text(e.Message, Constants.WHITE, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var t = exception.GetType();
|
||||||
|
Text(t.Namespace + _dot, Constants.GRAY);
|
||||||
|
Text(t.Name, Constants.WHITE);
|
||||||
|
Text(_colon + " ", Constants.GRAY);
|
||||||
|
Text(exception.Message, Constants.RED, true);
|
||||||
|
|
||||||
|
Text(_at, _gray);
|
||||||
|
Text(exception.TargetSite.DeclaringType.FullName + _dot, Constants.GRAY);
|
||||||
|
Text(exception.TargetSite.Name, Constants.YELLOW);
|
||||||
|
|
||||||
|
var p = exception.TargetSite.GetParameters();
|
||||||
|
var parameters = new string[p.Length];
|
||||||
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
parameters[i] = p[i].ParameterType.Name + " " + p[i].Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("(" + string.Join(", ", parameters) + ")", Constants.GRAY, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Text(e.Message, Constants.WHITE, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
<UserControl x:Class="FModel.Views.Resources.Controls.TiledExplorer.FileButton2"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="128" d:DesignHeight="192"
|
||||||
|
d:DataContext="{d:DesignInstance Type=vm:GameFileViewModel, IsDesignTimeCreatable=False}"
|
||||||
|
|
||||||
|
xmlns:vm="clr-namespace:FModel.ViewModels"
|
||||||
|
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||||
|
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||||
|
Width="128" Height="192"
|
||||||
|
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer4BackgroundBrush}}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="2" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" Height="128" Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BorderBrush}}">
|
||||||
|
<Image Stretch="Uniform" Source="{Binding PreviewImage}" />
|
||||||
|
<Path x:Name="FallbackIcon" Width="64" Stretch="Uniform">
|
||||||
|
<Path.Style>
|
||||||
|
<Style TargetType="{x:Type Path}">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding PreviewImage}" Value="{x:Null}">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Path.Style>
|
||||||
|
<Path.Data>
|
||||||
|
<MultiBinding Converter="{x:Static converters:FileToGeometryConverter.Instance}">
|
||||||
|
<Binding Path="AssetCategory" />
|
||||||
|
<Binding Path="ResolvedAssetType" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Path.Data>
|
||||||
|
<Path.Fill>
|
||||||
|
<MultiBinding Converter="{x:Static converters:FileToGeometryConverter.Instance}">
|
||||||
|
<Binding Path="AssetCategory" />
|
||||||
|
<Binding Path="ResolvedAssetType" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Path.Fill>
|
||||||
|
</Path>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Rectangle Grid.Row="1" Fill="{Binding Fill, ElementName=FallbackIcon, FallbackValue=Red}" />
|
||||||
|
|
||||||
|
<Grid Grid.Row="2" Margin="5">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0" Text="{Binding Asset.NameWithoutExtension, FallbackValue=Asset Name}"
|
||||||
|
FontSize="13" TextWrapping="Wrap" TextTrimming="CharacterEllipsis" FontWeight="DemiBold"
|
||||||
|
TextAlignment="Left" HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||||
|
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"/>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="5" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.Column="0" Text="{Binding ResolvedAssetType, FallbackValue=Asset Type}"
|
||||||
|
FontSize="9" TextWrapping="NoWrap" TextTrimming="CharacterEllipsis" FontWeight="Normal"
|
||||||
|
TextAlignment="Left" HorizontalAlignment="Left" VerticalAlignment="Bottom"
|
||||||
|
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Column="2" Text="{Binding Asset.Size, Converter={x:Static converters:SizeToStringConverter.Instance}, FallbackValue=0 B}"
|
||||||
|
FontSize="9" TextWrapping="NoWrap" TextTrimming="CharacterEllipsis" FontWeight="Normal"
|
||||||
|
TextAlignment="Right" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||||
|
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user