Compare commits

..

No commits in common. "master" and "qa" have entirely different histories.
master ... qa

103 changed files with 2593 additions and 5059 deletions

View File

@ -21,16 +21,16 @@ jobs:
- name: Fetch Submodules Recursively - name: Fetch Submodules Recursively
run: git submodule update --init --recursive run: git submodule update --init --recursive
- name: .NET 8 Setup - name: .NET 6 Setup
uses: actions/setup-dotnet@v2 uses: actions/setup-dotnet@v2
with: with:
dotnet-version: '8.0.x' dotnet-version: '6.0.x'
- name: .NET Restore - name: .NET Restore
run: dotnet restore FModel run: dotnet restore FModel
- name: .NET Publish - name: .NET Publish
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 net6.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }}
- name: ZIP File - name: ZIP File
uses: papeloto/action-zip@v1 uses: papeloto/action-zip@v1

View File

@ -1,65 +0,0 @@
name: FModel QA Builder
on:
push:
branches: [ dev ]
jobs:
build:
runs-on: windows-latest
steps:
- name: GIT Checkout
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: .NET 8 Setup
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: .NET Restore
run: dotnet restore FModel
- 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
- name: ZIP File
uses: thedoctor0/zip-release@0.7.6
with:
type: zip
filename: ${{ github.sha }}.zip # will end up in working directory not the Publish folder
path: ./FModel/bin/Publish/FModel.exe
- name: Edit QA Artifact
uses: ncipollo/release-action@v1.14.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: 'FModel QA Testing'
body: 'Dev builds'
tag: 'qa'
artifacts: ${{ github.sha }}.zip
prerelease: true
allowUpdates: true
- name: Get Version
id: package_version
uses: kzrnm/get-net-sdk-project-versions-action@v2
with:
proj-path: ./FModel/FModel.csproj
- name: FModel Auth
id: fmodel_auth
uses: fjogeleit/http-request-action@v1.15.5
with:
url: "https://api.fmodel.app/v1/oauth/token"
data: '{"username": "${{ secrets.API_USERNAME }}", "password": "${{ secrets.API_PASSWORD }}"}'
- name: FModel Deploy Build
uses: fjogeleit/http-request-action@v1.15.5
with:
url: "https://api.fmodel.app/v1/infos/${{ secrets.QA_ID }}"
method: "PATCH"
bearerToken: ${{ fromJson(steps.fmodel_auth.outputs.response).accessToken }}
data: '{"version": "${{ steps.package_version.outputs.version }}-dev+${{ github.sha }}", "downloadUrl": "https://github.com/4sval/FModel/releases/download/qa/${{ github.sha }}.zip"}'

3
.gitmodules vendored
View File

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

@ -1 +1 @@
Subproject commit 455b72e5e38bfe9476b5823bc642fc8ef488347f Subproject commit 3ff8c179dfbe817f22e9672dab9c2901a58b9db7

1
EpicManifestParser Submodule

@ -0,0 +1 @@
Subproject commit 21df8a55d474f14148a35bc943e06f3fdc20c997

View File

@ -41,6 +41,8 @@ public partial class App
{ {
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>( UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings); File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
/*if (UserSettings.Default.ShowChangelog) */MigrateV1Games();
} }
catch catch
{ {
@ -50,14 +52,7 @@ public partial class App
var createMe = false; var createMe = false;
if (!Directory.Exists(UserSettings.Default.OutputDirectory)) if (!Directory.Exists(UserSettings.Default.OutputDirectory))
{ {
var currentDir = Directory.GetCurrentDirectory(); UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
var dirInfo = new DirectoryInfo(currentDir);
if (dirInfo.Attributes.HasFlag(FileAttributes.Archive))
throw new Exception("FModel cannot be run from an archive file. Please extract it and try again.");
if (dirInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
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");
} }
if (!Directory.Exists(UserSettings.Default.RawDataDirectory)) if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
@ -97,16 +92,14 @@ public partial class App
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")); Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
#if DEBUG #if DEBUG
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File( Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).CreateLogger();
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
#else #else
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File( Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"), Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger(); outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
#endif #endif
Log.Information("Version {Version} ({CommitId})", Constants.APP_VERSION, Constants.APP_COMMIT_ID); Log.Information("Version {Version}", Constants.APP_VERSION);
Log.Information("{OS}", GetOperatingSystemProductName()); Log.Information("{OS}", GetOperatingSystemProductName());
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription); Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
Log.Information("Culture {SysLang}", CultureInfo.CurrentCulture); Log.Information("Culture {SysLang}", CultureInfo.CurrentCulture);
@ -142,7 +135,7 @@ public partial class App
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore) if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
{ {
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings) if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
UserSettings.Delete(); UserSettings.Default = new UserSettings();
ApplicationService.ApplicationView.Restart(); ApplicationService.ApplicationView.Restart();
} }
@ -150,6 +143,17 @@ public partial class App
e.Handled = true; e.Handled = true;
} }
private void MigrateV1Games()
{
foreach ((var gameDir, var setting) in UserSettings.Default.ManualGames)
{
if (!Directory.Exists(gameDir)) continue;
UserSettings.Default.PerDirectory[gameDir] =
DirectorySettings.Default(setting.GameName, setting.GameDirectory, true, setting.OverridedGame, setting.AesKeys?.MainKey);
}
UserSettings.Default.ManualGames.Clear();
}
private string GetOperatingSystemProductName() private string GetOperatingSystemProductName()
{ {
var productName = string.Empty; var productName = string.Empty;

View File

@ -1,20 +1,12 @@
using System; using System.Numerics;
using System.Diagnostics;
using System.IO;
using System.Numerics;
using System.Reflection; using System.Reflection;
using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Extensions;
namespace FModel; namespace FModel;
public static class Constants public static class Constants
{ {
public static readonly string APP_PATH = Path.GetFullPath(Environment.GetCommandLineArgs()[0]); public static readonly string APP_VERSION = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
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_SHORT_COMMIT_ID = APP_COMMIT_ID[..7];
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);
@ -29,9 +21,6 @@ public static class Constants
public const string BLUE = "#528BCC"; public const string BLUE = "#528BCC";
public const string ISSUE_LINK = "https://github.com/4sval/FModel/discussions/categories/q-a"; public const string ISSUE_LINK = "https://github.com/4sval/FModel/discussions/categories/q-a";
public const string GH_REPO = "https://api.github.com/repos/4sval/FModel";
public const string GH_COMMITS_HISTORY = GH_REPO + "/commits";
public const string GH_RELEASES = GH_REPO + "/releases";
public const string DONATE_LINK = "https://fmodel.app/donate"; public const string DONATE_LINK = "https://fmodel.app/donate";
public const string DISCORD_LINK = "https://fmodel.app/discord"; public const string DISCORD_LINK = "https://fmodel.app/discord";

View File

@ -26,7 +26,7 @@ public class BaseBundle : UCreator
{ {
_quests = new List<BaseQuest>(); _quests = new List<BaseQuest>();
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName")) if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text.ToUpperInvariant(); DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :) if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :)

View File

@ -1,7 +1,5 @@
using System.Linq;
using CUE4Parse.GameTypes.FN.Enums; using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.GameplayTags; using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse.UE4.Versions; using CUE4Parse.UE4.Versions;
@ -34,27 +32,10 @@ public class BaseCommunity : BaseIcon
{ {
ParseForReward(UserSettings.Default.CosmeticDisplayAsset); ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FPackageIndex series, "Series")) if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export))
{ _rarityName = export.Name;
_rarityName = series.Name;
}
else if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList") &&
dataList.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
{
_rarityName = dl.NonConstStruct?.Get<FPackageIndex>("Series").Name;
}
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer") &&
componentContainer.TryGetValue(out FPackageIndex[] components, "Components") &&
components.FirstOrDefault(c => c.Name.Contains("Series")) is { } seriesDef &&
seriesDef.TryLoad(out var seriesDefObj) && seriesDefObj is not null &&
seriesDefObj.TryGetValue(out series, "Series"))
{
_rarityName = series.Name;
}
else else
{
_rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription(); _rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription();
}
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags")) if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags); CheckGameplayTags(gameplayTags);

View File

@ -1,325 +1,298 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using CUE4Parse.GameTypes.FN.Enums; using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine; using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture; 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.Core.Math; using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.GameplayTags; using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Conversion.Textures; using CUE4Parse_Conversion.Textures;
using FModel.Settings; using FModel.Settings;
using SkiaSharp; using SkiaSharp;
namespace FModel.Creator.Bases.FN; namespace FModel.Creator.Bases.FN;
public class BaseIcon : UCreator public class BaseIcon : UCreator
{ {
public SKBitmap SeriesBackground { get; protected set; } public SKBitmap SeriesBackground { get; protected set; }
protected string ShortDescription { get; set; } protected string ShortDescription { get; set; }
protected string CosmeticSource { get; set; } protected string CosmeticSource { get; set; }
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; } protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) { } public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) { }
public void ParseForReward(bool isUsingDisplayAsset) public void ParseForReward(bool isUsingDisplayAsset)
{ {
// rarity // rarity
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series); if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer")) GetSeries(componentContainer); else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
// preview
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList")) if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
{ Preview = preview;
GetSeries(dataList); else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
Preview = Utils.GetBitmap(dataList); Preview = Utils.GetBitmap(itemDefinition);
} else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
Preview = Utils.GetBitmap(largePreview);
// preview else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
if (Preview is null) Preview = Utils.GetBitmap(s);
{ else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview)) Preview = Utils.GetBitmap(otherPreview);
Preview = preview; else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition")) Preview = Utils.GetBitmap(materialInstancePreview);
Preview = Utils.GetBitmap(itemDefinition); else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "BundleImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon")) Preview = Utils.GetBitmap(res);
Preview = Utils.GetBitmap(largePreview);
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s)) // text
Preview = Utils.GetBitmap(s); if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item")) DisplayName = displayName.Text;
Preview = Utils.GetBitmap(otherPreview); if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage")) Description = description.Text;
Preview = Utils.GetBitmap(materialInstancePreview); else if (Object.TryGetValue(out FText[] descriptions, "Description"))
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject")) Description = string.Join('\n', descriptions.Select(x => x.Text));
Preview = Utils.GetBitmap(res); if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
} ShortDescription = shortDescription.Text;
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
// text ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
DisplayName = displayName.Text; // Only works on non-cataba designs
if (Object.TryGetValue(out FText description, "Description", "ItemDescription", "BundleDescription", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription")) if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
Description = description.Text; eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
else if (Object.TryGetValue(out FText[] descriptions, "Description")) Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
Description = string.Join('\n', descriptions.Select(x => x.Text)); eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName")) {
ShortDescription = shortDescription.Text; Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) };
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase)) Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) };
ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap"); }
// Only works on non-cataba designs Description = Utils.RemoveHtmlTags(Description);
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") && }
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") && public override void ParseForInfo()
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor")) {
{ ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) };
Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) }; if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
} CheckGameplayTags(gameplayTags);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
Description = Utils.RemoveHtmlTags(Description); CosmeticSource = cosmeticItem.Name;
} }
public override void ParseForInfo() protected void Draw(SKCanvas c)
{ {
ParseForReward(UserSettings.Default.CosmeticDisplayAsset); switch (Style)
{
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags")) case EIconStyle.NoBackground:
CheckGameplayTags(gameplayTags); DrawPreview(c);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item")) break;
CosmeticSource = cosmeticItem.Name; case EIconStyle.NoText:
} DrawBackground(c);
DrawPreview(c);
protected void Draw(SKCanvas c) DrawUserFacingFlags(c);
{ break;
switch (Style) default:
{ DrawBackground(c);
case EIconStyle.NoBackground: DrawPreview(c);
DrawPreview(c); DrawTextBackground(c);
break; DrawDisplayName(c);
case EIconStyle.NoText: DrawDescription(c);
DrawBackground(c); DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
DrawPreview(c); if (Description != ShortDescription)
DrawUserFacingFlags(c); DrawToBottom(c, SKTextAlign.Left, ShortDescription);
break; DrawUserFacingFlags(c);
default: break;
DrawBackground(c); }
DrawPreview(c); }
DrawTextBackground(c);
DrawDisplayName(c); public override SKBitmap[] Draw()
DrawDescription(c); {
DrawToBottom(c, SKTextAlign.Right, CosmeticSource); var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
if (Description != ShortDescription) using var c = new SKCanvas(ret);
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
DrawUserFacingFlags(c); Draw(c);
break;
} return new[] { ret };
} }
public override SKBitmap[] Draw() private void GetSeries(FPackageIndex s)
{ {
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
using var c = new SKCanvas(ret);
GetSeries(export);
Draw(c); }
return new[] { ret }; protected void GetSeries(UObject uObject)
} {
if (uObject is UTexture2D texture2D)
private void GetSeries(FPackageIndex s) {
{ SeriesBackground = texture2D.Decode();
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return; return;
}
GetSeries(export);
} if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
{
private void GetSeries(FInstancedStruct[] s) SeriesBackground = Utils.GetBitmap(backgroundTexture);
{ }
if (s.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
GetSeries(dl.NonConstStruct?.Get<FPackageIndex>("Series")); if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
} colors.TryGetValue(out FLinearColor color1, "Color1") &&
colors.TryGetValue(out FLinearColor color2, "Color2") &&
private void GetSeries(FStructFallback s) colors.TryGetValue(out FLinearColor color3, "Color3"))
{ {
if (!s.TryGetValue(out FPackageIndex[] components, "Components")) return; Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
if (components.FirstOrDefault(c => c.Name.Contains("Series")) is not { } seriesDef || Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
!seriesDef.TryLoad(out var seriesDefObj) || seriesDefObj is null || }
!seriesDefObj.TryGetValue(out UObject series, "Series")) return;
if (uObject.Name.Equals("PlatformSeries") &&
GetSeries(series); uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
} Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
{
protected void GetSeries(UObject uObject) foreach (var vectorParameter in material.VectorParameterValues)
{ {
if (uObject is UTexture2D texture2D) if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
{ continue;
SeriesBackground = texture2D.Decode();
return; Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
} }
}
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture")) }
{
SeriesBackground = Utils.GetBitmap(backgroundTexture); private void GetRarity(EFortRarity r)
} {
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
colors.TryGetValue(out FLinearColor color1, "Color1") && if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
colors.TryGetValue(out FLinearColor color2, "Color2") && data.TryGetValue(out FLinearColor color1, "Color1") &&
colors.TryGetValue(out FLinearColor color3, "Color3")) data.TryGetValue(out FLinearColor color2, "Color2") &&
{ data.TryGetValue(out FLinearColor color3, "Color3"))
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) }; {
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) }; Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
} Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
if (uObject.Name.Equals("PlatformSeries") && }
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material)) protected string GetCosmeticSet(string setName)
{ {
foreach (var vectorParameter in material.VectorParameterValues) if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
{ return string.Empty;
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
continue; if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
return string.Empty;
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
} var name = string.Empty;
} if (uObject.TryGetValue(out FText displayName, "DisplayName"))
} name = displayName.Text;
private void GetRarity(EFortRarity r) var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
{ return string.Format(format, name);
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return; }
if (export.GetByIndex<FStructFallback>((int) r) is { } data && protected (int, int) GetInternalSID(int number)
data.TryGetValue(out FLinearColor color1, "Color1") && {
data.TryGetValue(out FLinearColor color2, "Color2") && static int GetSeasonsInChapter(int chapter) => chapter switch
data.TryGetValue(out FLinearColor color3, "Color3")) {
{ 1 => 10,
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) }; 2 => 8,
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) }; 3 => 4,
} _ => 10
} };
protected string GetCosmeticSet(string setName) var chapterIdx = 0;
{ var seasonIdx = 0;
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets)) while (number > 0)
return string.Empty; {
var seasonsInChapter = GetSeasonsInChapter(++chapterIdx);
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject)) if (number > seasonsInChapter)
return string.Empty; number -= seasonsInChapter;
else
var name = string.Empty; {
if (uObject.TryGetValue(out FText displayName, "DisplayName")) seasonIdx = number;
name = displayName.Text; number = 0;
}
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set."); }
return string.Format(format, name); return (chapterIdx, seasonIdx);
} }
protected (int, int) GetInternalSID(int number) protected string GetCosmeticSeason(string seasonNumber)
{ {
static int GetSeasonsInChapter(int chapter) => chapter switch var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
{ var initial = int.Parse(s);
1 => 10, (int chapterIdx, int seasonIdx) = GetInternalSID(initial);
2 => 8,
3 => 4, var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
4 => 5, var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
_ => 10 if (initial <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
};
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
var chapterIdx = 0; var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
var seasonIdx = 0; var d = string.Format(chapterFormat, string.Format(chapter, chapterIdx), string.Format(season, seasonIdx));
while (number > 0) return Utils.RemoveHtmlTags(string.Format(introduced, d));
{ }
var seasonsInChapter = GetSeasonsInChapter(++chapterIdx);
if (number > seasonsInChapter) private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
number -= seasonsInChapter; {
else if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
{ CosmeticSource = source.Text["Cosmetics.Source.".Length..];
seasonIdx = number; else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
number = 0; CosmeticSource = action.Text["Athena.ItemAction.".Length..];
}
} if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
return (chapterIdx, seasonIdx); Description += GetCosmeticSet(set.Text);
} if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
Description += GetCosmeticSeason(season.Text);
protected string GetCosmeticSeason(string seasonNumber)
{ GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
var s = seasonNumber["Cosmetics.Filter.Season.".Length..]; "Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
var initial = int.Parse(s); }
(int chapterIdx, int seasonIdx) = GetInternalSID(initial);
protected void GetUserFacingFlags(IList<string> userFacingFlags)
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}"); {
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>."); if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
if (initial <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s))); return;
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}"); if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}"); return;
var d = string.Format(chapterFormat, string.Format(chapter, chapterIdx), string.Format(season, seasonIdx));
return Utils.RemoveHtmlTags(string.Format(introduced, d)); UserFacingFlags = new Dictionary<string, SKBitmap>(userFacingFlags.Count);
} foreach (var flag in userFacingFlags)
{
private void CheckGameplayTags(FGameplayTagContainer gameplayTags) if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
{ {
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source)) if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
CosmeticSource = source.Text["Cosmetics.Source.".Length..]; UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action)) else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
CosmeticSource = action.Text["Athena.ItemAction.".Length..]; }
else
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set)) {
Description += GetCosmeticSet(set.Text); foreach (var category in tertiaryCategories)
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season)) {
Description += GetCosmeticSeason(season.Text); if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
GetUserFacingFlags(gameplayTags.GetAllGameplayTags( brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender.")); {
} UserFacingFlags[flag] = Utils.GetBitmap(texture);
}
protected void GetUserFacingFlags(IList<string> userFacingFlags) }
{ }
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories)) }
return; }
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories")) private void DrawUserFacingFlags(SKCanvas c)
return; {
if (UserFacingFlags == null) return;
UserFacingFlags = new Dictionary<string, SKBitmap>(userFacingFlags.Count);
foreach (var flag in userFacingFlags) const int size = 25;
{ var x = Margin * (int) 2.5;
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase)) foreach (var flag in UserFacingFlags.Values.Where(flag => flag != null))
{ {
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase)) c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream); x += size;
else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream); }
} }
else }
{
foreach (var category in tertiaryCategories)
{
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
{
UserFacingFlags[flag] = Utils.GetBitmap(texture);
}
}
}
}
}
private void DrawUserFacingFlags(SKCanvas c)
{
if (UserFacingFlags == null) return;
const int size = 25;
var x = Margin * (int) 2.5;
foreach (var flag in UserFacingFlags.Values.Where(flag => flag != null))
{
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
x += size;
}
}
}

View File

@ -29,12 +29,12 @@ public class BaseItemAccessToken : UCreator
_icon.ParseForReward(false); _icon.ParseForReward(false);
} }
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName") && displayName.Text != "TBD") if (Object.TryGetValue(out FText displayName, "DisplayName") && displayName.Text != "TBD")
DisplayName = displayName.Text; DisplayName = displayName.Text;
else else
DisplayName = _icon?.DisplayName; DisplayName = _icon?.DisplayName;
Description = Object.TryGetValue(out FText description, "Description", "ItemDescription") ? description.Text : _icon?.Description; Description = Object.TryGetValue(out FText description, "Description") ? description.Text : _icon?.Description;
if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text; if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text;
} }

View File

@ -1,49 +0,0 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseJuno : BaseIcon
{
private BaseIcon _character;
public BaseJuno(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FSoftObjectPath baseCid, "BaseAthenaCharacterItemDefinition") &&
Utils.TryLoadObject(baseCid.AssetPathName.Text, out UObject cid))
{
_character = new BaseIcon(cid, Style);
_character.ParseForInfo();
if (Object.TryGetValue(out FSoftObjectPath assembledMeshSchema, "AssembledMeshSchema", "LowDetailsAssembledMeshSchema") &&
Utils.TryLoadObject(assembledMeshSchema.AssetPathName.Text, out UObject meshSchema) &&
meshSchema.TryGetValue(out FInstancedStruct[] additionalData, "AdditionalData"))
{
foreach (var data in additionalData)
{
if (data.NonConstStruct?.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SmallPreviewImage") ?? false)
{
_character.Preview = Utils.GetBitmap(largePreview);
break;
}
}
}
}
if (Object.TryGetValue(out FSoftObjectPath baseEid, "BaseAthenaDanceItemDefinition") &&
Utils.TryLoadObject(baseEid.AssetPathName.Text, out UObject eid))
{
_character = new BaseIcon(eid, Style);
_character.ParseForInfo();
}
}
public override SKBitmap[] Draw() => _character.Draw();
}

View File

@ -31,7 +31,6 @@ public class BaseMaterialInstance : BaseIcon
case "TextureA": case "TextureA":
case "TextureB": case "TextureB":
case "OfferImage": case "OfferImage":
case "CarTexture":
Preview = Utils.GetBitmap(texture); Preview = Utils.GetBitmap(texture);
break; break;
} }
@ -89,4 +88,4 @@ public class BaseMaterialInstance : BaseIcon
return new[] { ret }; return new[] { ret };
} }
} }

View File

@ -17,9 +17,10 @@ public class BaseMtxOffer : UCreator
public override void ParseForInfo() public override void ParseForInfo()
{ {
if (Object.TryGetValue(out FSoftObjectPath image, "SoftDetailsImage", "SoftTileImage")) if (Object.TryGetValue(out FStructFallback typeImage, "DetailsImage", "TileImage") &&
typeImage.TryGetValue(out FPackageIndex resource, "ResourceObject"))
{ {
Preview = Utils.GetBitmap(image); Preview = Utils.GetBitmap(resource);
} }
if (Object.TryGetValue(out FStructFallback gradient, "Gradient") && if (Object.TryGetValue(out FStructFallback gradient, "Gradient") &&
@ -80,4 +81,4 @@ public class BaseMtxOffer : UCreator
return new[] { ret }; return new[] { ret };
} }
} }

View File

@ -1,45 +1,39 @@
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp; using SkiaSharp;
namespace FModel.Creator.Bases.FN; namespace FModel.Creator.Bases.FN;
public class BaseOfferDisplayData : UCreator public class BaseOfferDisplayData : UCreator
{ {
private readonly List<BaseMaterialInstance> _offerImages; private BaseMaterialInstance[] _offerImages;
public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style) public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style)
{ {
_offerImages = new List<BaseMaterialInstance>();
} }
public override void ParseForInfo() public override void ParseForInfo()
{ {
if (!Object.TryGetValue(out FStructFallback[] contextualPresentations, "ContextualPresentations")) if (!Object.TryGetValue(out UMaterialInterface[] presentations, "Presentations"))
return; return;
for (var i = 0; i < contextualPresentations.Length; i++) _offerImages = new BaseMaterialInstance[presentations.Length];
for (var i = 0; i < _offerImages.Length; i++)
{ {
if (!contextualPresentations[i].TryGetValue(out FSoftObjectPath material, "Material") || var offerImage = new BaseMaterialInstance(presentations[i], Style);
!material.TryLoad(out UMaterialInterface presentation)) continue;
var offerImage = new BaseMaterialInstance(presentation, Style);
offerImage.ParseForInfo(); offerImage.ParseForInfo();
_offerImages.Add(offerImage); _offerImages[i] = offerImage;
} }
} }
public override SKBitmap[] Draw() public override SKBitmap[] Draw()
{ {
var ret = new SKBitmap[_offerImages.Count]; var ret = new SKBitmap[_offerImages.Length];
for (var i = 0; i < ret.Length; i++) for (var i = 0; i < ret.Length; i++)
{ {
ret[i] = _offerImages[i]?.Draw()[0]; ret[i] = _offerImages[i].Draw()[0];
} }
return ret; return ret;
} }
} }

View File

@ -34,7 +34,7 @@ public class BasePlaylist : UCreator
return; return;
var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text); var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text);
if (!playlist.IsSuccess || playlist.Data.Images is not { HasShowcase: true } || if (!playlist.IsSuccess || !playlist.Data.Images.HasShowcase ||
!_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image)) !_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image))
return; return;
@ -74,4 +74,4 @@ public class BasePlaylist : UCreator
if (_missionIcon == null) return; if (_missionIcon == null) return;
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint); c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
} }
} }

View File

@ -69,19 +69,13 @@ public class BaseQuest : BaseIcon
} }
else else
{ {
if (!string.IsNullOrEmpty(ShortDescription)) Description = ShortDescription;
Description = ShortDescription; if (Object.TryGetValue(out FText completionText, "CompletionText"))
if (string.IsNullOrEmpty(DisplayName) && !string.IsNullOrEmpty(Description)) Description += "\n" + completionText.Text;
DisplayName = Description; if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
if (DisplayName == Description)
Description = string.Empty;
if ((Object.TryGetValue(out FSoftObjectPath icon, "QuestGiverWidgetIcon", "NotificationIconOverride") &&
Utils.TryLoadObject(icon.AssetPathName.Text, out UObject iconObject)) ||
(Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) && Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") && uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") &&
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out iconObject))) Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out UObject iconObject))
{ {
Preview = iconObject switch Preview = iconObject switch
{ {
@ -133,16 +127,9 @@ public class BaseQuest : BaseIcon
} }
} }
if (_reward == null) if (_reward == null && Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
{ {
FName rowName = null; if (rewardsTable.TryGetDataTableRow("Default", StringComparison.InvariantCulture, out var row))
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") && if (row.TryGetValue(out FName templateId, "TemplateId") &&
row.TryGetValue(out int quantity, "Quantity")) row.TryGetValue(out int quantity, "Quantity"))

View File

@ -36,7 +36,7 @@ public class BaseSeason : UCreator
{ {
_bookXpSchedule = Array.Empty<Page>(); _bookXpSchedule = Array.Empty<Page>();
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName")) if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text.ToUpperInvariant(); DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") && if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&

View File

@ -64,7 +64,7 @@ public class Reward
_rewardPaint.TextSize = 50; _rewardPaint.TextSize = 50;
if (HasReward()) if (HasReward())
{ {
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).Resize((int) rect.Height), new SKPoint(rect.Left, rect.Top), _rewardPaint); c.DrawBitmap(_theReward.Preview.Resize((int) rect.Height), new SKPoint(rect.Left, rect.Top), _rewardPaint);
_rewardPaint.Color = _theReward.Border[0]; _rewardPaint.Color = _theReward.Border[0];
_rewardPaint.Typeface = _rewardQuantity.StartsWith("x") ? Utils.Typefaces.BundleNumber : Utils.Typefaces.Bundle; _rewardPaint.Typeface = _rewardQuantity.StartsWith("x") ? Utils.Typefaces.BundleNumber : Utils.Typefaces.Bundle;
@ -88,7 +88,7 @@ public class Reward
public void DrawSeasonWin(SKCanvas c, int size) public void DrawSeasonWin(SKCanvas c, int size)
{ {
if (!HasReward()) return; if (!HasReward()) return;
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).Resize(size), new SKPoint(0, 0), _rewardPaint); c.DrawBitmap(_theReward.Preview.Resize(size), new SKPoint(0, 0), _rewardPaint);
} }
public void DrawSeason(SKCanvas c, int x, int y, int areaSize) public void DrawSeason(SKCanvas c, int x, int y, int areaSize)
@ -115,33 +115,33 @@ public class Reward
{ {
switch (trigger.ToLower()) switch (trigger.ToLower())
{ {
// case "athenabattlestar": case "athenabattlestar":
// _theReward = new BaseIcon(null, EIconStyle.Default); _theReward = new BaseIcon(null, EIconStyle.Default);
// _theReward.Border[0] = SKColor.Parse("FFDB67"); _theReward.Border[0] = SKColor.Parse("FFDB67");
// _theReward.Background[0] = SKColor.Parse("8F4A20"); _theReward.Background[0] = SKColor.Parse("8F4A20");
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/Athena/UI/Frontend/Art/T_UI_BP_BattleStar_L.T_UI_BP_BattleStar_L"); _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/Athena/UI/Frontend/Art/T_UI_BP_BattleStar_L.T_UI_BP_BattleStar_L");
// break; break;
// case "athenaseasonalxp": case "athenaseasonalxp":
// _theReward = new BaseIcon(null, EIconStyle.Default); _theReward = new BaseIcon(null, EIconStyle.Default);
// _theReward.Border[0] = SKColor.Parse("E6FDB1"); _theReward.Border[0] = SKColor.Parse("E6FDB1");
// _theReward.Background[0] = SKColor.Parse("51830F"); _theReward.Background[0] = SKColor.Parse("51830F");
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPUncommon-L.T-FNBR-XPUncommon-L"); _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPUncommon-L.T-FNBR-XPUncommon-L");
// break; break;
// case "mtxgiveaway": case "mtxgiveaway":
// _theReward = new BaseIcon(null, EIconStyle.Default); _theReward = new BaseIcon(null, EIconStyle.Default);
// _theReward.Border[0] = SKColor.Parse("DCE6FF"); _theReward.Border[0] = SKColor.Parse("DCE6FF");
// _theReward.Background[0] = SKColor.Parse("64A0AF"); _theReward.Background[0] = SKColor.Parse("64A0AF");
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX"); _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX");
// break; break;
default: default:
{ {
var path = Utils.GetFullPath($"FortniteGame/(?:Content/Athena|Content/Items|Plugins/GameFeatures)/.*?/{trigger}.uasset"); // path has no objectname and its needed so we push the trigger again as the objectname var path = Utils.GetFullPath($"FortniteGame/Content/Athena/.*?/{trigger}.*"); // path has no objectname and its needed so we push the trigger again as the objectname
if (!string.IsNullOrWhiteSpace(path) && Utils.TryLoadObject(path.Replace("uasset", trigger), out UObject d)) if (!string.IsNullOrWhiteSpace(path) && Utils.TryLoadObject(path.Replace("uasset", trigger), out UObject d))
{ {
_theReward = new BaseIcon(d, EIconStyle.Default); _theReward = new BaseIcon(d, EIconStyle.Default);
_theReward.ParseForReward(false); _theReward.ParseForReward(false);
_theReward.Border[0] = SKColors.White; _theReward.Border[0] = SKColors.White;
_rewardQuantity = $"{_theReward.DisplayName} ({_rewardQuantity})"; _rewardQuantity = _theReward.DisplayName;
} }
break; break;

View File

@ -1,256 +1,218 @@
using System; using System;
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.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; using FModel.Creator.Bases.SB;
namespace FModel.Creator; namespace FModel.Creator;
public class CreatorPackage : IDisposable public class CreatorPackage : IDisposable
{ {
private UObject _object; private UObject _object;
private EIconStyle _style; private EIconStyle _style;
public CreatorPackage(UObject uObject, EIconStyle style) public CreatorPackage(UObject uObject, EIconStyle style)
{ {
_object = uObject; _object = uObject;
_style = style; _style = style;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public UCreator ConstructCreator() public UCreator ConstructCreator()
{ {
TryConstructCreator(out var creator); TryConstructCreator(out var creator);
return creator; return creator;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryConstructCreator(out UCreator creator) public bool TryConstructCreator(out UCreator creator)
{ {
switch (_object.ExportType) switch (_object.ExportType)
{ {
// Fortnite // Fortnite
case "FortCreativeWeaponMeleeItemDefinition": case "FortCreativeWeaponMeleeItemDefinition":
case "AthenaConsumableEmoteItemDefinition": case "AthenaConsumableEmoteItemDefinition":
case "AthenaSkyDiveContrailItemDefinition": case "AthenaSkyDiveContrailItemDefinition":
case "AthenaLoadingScreenItemDefinition": case "AthenaLoadingScreenItemDefinition":
case "AthenaVictoryPoseItemDefinition": case "AthenaVictoryPoseItemDefinition":
case "AthenaPetCarrierItemDefinition": case "AthenaPetCarrierItemDefinition":
case "AthenaMusicPackItemDefinition": case "AthenaMusicPackItemDefinition":
case "AthenaBattleBusItemDefinition": case "AthenaBattleBusItemDefinition":
case "AthenaCharacterItemDefinition": case "AthenaCharacterItemDefinition":
case "AthenaMapMarkerItemDefinition": case "AthenaMapMarkerItemDefinition":
case "AthenaBackpackItemDefinition": case "AthenaBackpackItemDefinition":
case "AthenaPickaxeItemDefinition": case "AthenaPickaxeItemDefinition":
case "AthenaGadgetItemDefinition": case "AthenaGadgetItemDefinition":
case "AthenaGliderItemDefinition": case "AthenaGliderItemDefinition":
case "AthenaSprayItemDefinition": case "AthenaSprayItemDefinition":
case "AthenaDanceItemDefinition": case "AthenaDanceItemDefinition":
case "AthenaEmojiItemDefinition": case "AthenaEmojiItemDefinition":
case "AthenaItemWrapDefinition": case "AthenaItemWrapDefinition":
case "AthenaToyItemDefinition": case "AthenaToyItemDefinition":
case "FortHeroType": case "FortHeroType":
case "FortTokenType": case "FortTokenType":
case "FortAbilityKit": case "FortAbilityKit":
case "FortWorkerType": case "FortWorkerType":
case "RewardGraphToken": case "RewardGraphToken":
case "JunoKnowledgeBundle": case "FortBannerTokenType":
case "FortBannerTokenType": case "FortVariantTokenType":
case "FortVariantTokenType": case "FortDecoItemDefinition":
case "FortDecoItemDefinition": case "FortStatItemDefinition":
case "FortStatItemDefinition": case "FortAmmoItemDefinition":
case "FortAmmoItemDefinition": case "FortEmoteItemDefinition":
case "FortEmoteItemDefinition": case "FortBadgeItemDefinition":
case "FortBadgeItemDefinition": case "FortAwardItemDefinition":
case "SparksMicItemDefinition": case "FortGadgetItemDefinition":
case "FortAwardItemDefinition": case "AthenaCharmItemDefinition":
case "FortStackItemDefinition": case "FortPlaysetItemDefinition":
case "FortWorldItemDefinition": case "FortGiftBoxItemDefinition":
case "SparksAuraItemDefinition": case "FortOutpostItemDefinition":
case "SparksDrumItemDefinition": case "FortVehicleItemDefinition":
case "SparksBassItemDefinition": case "FortCardPackItemDefinition":
case "FortGadgetItemDefinition": case "FortDefenderItemDefinition":
case "AthenaCharmItemDefinition": case "FortCurrencyItemDefinition":
case "FortPlaysetItemDefinition": case "FortResourceItemDefinition":
case "FortGiftBoxItemDefinition": case "FortBackpackItemDefinition":
case "FortOutpostItemDefinition": case "FortEventQuestMapDataAsset":
case "FortVehicleItemDefinition": case "FortWeaponModItemDefinition":
case "FortMissionItemDefinition": case "FortCodeTokenItemDefinition":
case "FortAccountItemDefinition": case "FortSchematicItemDefinition":
case "SparksGuitarItemDefinition": case "FortWorldMultiItemDefinition":
case "FortCardPackItemDefinition": case "FortAlterationItemDefinition":
case "FortDefenderItemDefinition": case "FortExpeditionItemDefinition":
case "FortCurrencyItemDefinition": case "FortIngredientItemDefinition":
case "FortResourceItemDefinition": case "FortAccountBuffItemDefinition":
case "FortBackpackItemDefinition": case "FortWeaponMeleeItemDefinition":
case "FortEventQuestMapDataAsset": case "FortPlayerPerksItemDefinition":
case "FortBuildingItemDefinition": case "FortPlaysetPropItemDefinition":
case "FortWeaponModItemDefinition": case "FortHomebaseNodeItemDefinition":
case "FortCodeTokenItemDefinition": case "FortNeverPersistItemDefinition":
case "FortSchematicItemDefinition": case "FortPlayerAugmentItemDefinition":
case "FortAlterableItemDefinition": case "RadioContentSourceItemDefinition":
case "SparksKeyboardItemDefinition": case "FortPlaysetGrenadeItemDefinition":
case "FortWorldMultiItemDefinition": case "FortPersonalVehicleItemDefinition":
case "FortAlterationItemDefinition": case "FortGameplayModifierItemDefinition":
case "FortExpeditionItemDefinition": case "FortHardcoreModifierItemDefinition":
case "FortIngredientItemDefinition": case "FortConsumableAccountItemDefinition":
case "FortConsumableItemDefinition": case "FortConversionControlItemDefinition":
case "StWFortAccoladeItemDefinition": case "FortAccountBuffCreditItemDefinition":
case "FortAccountBuffItemDefinition": case "FortEventCurrencyItemDefinitionRedir":
case "FortWeaponMeleeItemDefinition": case "FortPersistentResourceItemDefinition":
case "FortPlayerPerksItemDefinition": case "FortHomebaseBannerIconItemDefinition":
case "FortPlaysetPropItemDefinition": case "FortCampaignHeroLoadoutItemDefinition":
case "FortPrerollDataItemDefinition": case "FortConditionalResourceItemDefinition":
case "JunoRecipeBundleItemDefinition": case "FortChallengeBundleScheduleDefinition":
case "FortHomebaseNodeItemDefinition": case "FortWeaponMeleeDualWieldItemDefinition":
case "FortNeverPersistItemDefinition": case "FortDailyRewardScheduleTokenDefinition":
case "FortPlayerAugmentItemDefinition": case "FortCreativeWeaponRangedItemDefinition":
case "FortSmartBuildingItemDefinition": case "FortCreativeRealEstatePlotItemDefinition":
case "FortGiftBoxUnlockItemDefinition": case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
case "FortWeaponModItemDefinitionOptic": case "StWFortAccoladeItemDefinition":
case "RadioContentSourceItemDefinition": case "FortSmartBuildingItemDefinition":
case "FortPlaysetGrenadeItemDefinition": creator = _style switch
case "JunoWeaponCreatureItemDefinition": {
case "FortEventDependentItemDefinition": EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
case "FortPersonalVehicleItemDefinition": _ => new BaseIcon(_object, _style)
case "FortGameplayModifierItemDefinition": };
case "FortHardcoreModifierItemDefinition": return true;
case "FortWeaponModItemDefinitionMagazine": case "FortTandemCharacterData":
case "FortConsumableAccountItemDefinition": creator = new BaseTandem(_object, _style);
case "FortConversionControlItemDefinition": return true;
case "FortAccountBuffCreditItemDefinition": case "FortTrapItemDefinition":
case "JunoBuildInstructionsItemDefinition": case "FortSpyTechItemDefinition":
case "FortCharacterCosmeticItemDefinition": case "FortAccoladeItemDefinition":
case "JunoBuildingSetAccountItemDefinition": case "FortContextTrapItemDefinition":
case "FortEventCurrencyItemDefinitionRedir": case "FortWeaponRangedItemDefinition":
case "FortPersistentResourceItemDefinition": case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
case "FortWeaponMeleeOffhandItemDefinition": creator = new BaseIconStats(_object, _style);
case "FortHomebaseBannerIconItemDefinition": return true;
case "FortVehicleCosmeticsVariantTokenType": case "FortItemSeriesDefinition":
case "JunoBuildingPropAccountItemDefinition": creator = new BaseSeries(_object, _style);
case "FortCampaignHeroLoadoutItemDefinition": return true;
case "FortConditionalResourceItemDefinition": case "MaterialInstanceConstant"
case "FortChallengeBundleScheduleDefinition": when _object.Owner != null &&
case "FortWeaponMeleeDualWieldItemDefinition": (_object.Owner.Name.EndsWith($"/MI_OfferImages/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
case "FortDailyRewardScheduleTokenDefinition": _object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
case "FortCreativeWeaponRangedItemDefinition": _object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)):
case "FortVehicleCosmeticsItemDefinition_Body": creator = new BaseMaterialInstance(_object, _style);
case "FortVehicleCosmeticsItemDefinition_Skin": return true;
case "FortVehicleCosmeticsItemDefinition_Wheel": case "AthenaItemShopOfferDisplayData":
case "FortCreativeRealEstatePlotItemDefinition": creator = new BaseOfferDisplayData(_object, _style);
case "FortDeployableBaseCloudSaveItemDefinition": return true;
case "FortVehicleCosmeticsItemDefinition_Booster": case "FortMtxOfferData":
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C": creator = new BaseMtxOffer(_object, _style);
case "FortVehicleCosmeticsItemDefinition_DriftSmoke": return true;
case "FortVehicleCosmeticsItemDefinition_EngineAudio": case "FortPlaylistAthena":
creator = _style switch creator = new BasePlaylist(_object, _style);
{ return true;
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"), case "FortFeatItemDefinition":
_ => new BaseIcon(_object, _style) case "FortQuestItemDefinition":
}; case "FortQuestItemDefinition_Athena":
return true; case "FortQuestItemDefinition_Campaign":
case "JunoAthenaCharacterItemOverrideDefinition": case "AthenaDailyQuestDefinition":
case "JunoAthenaDanceItemOverrideDefinition": case "FortUrgentQuestItemDefinition":
creator = new BaseJuno(_object, _style); creator = new Bases.FN.BaseQuest(_object, _style);
return true; return true;
case "FortTandemCharacterData": case "FortCompendiumItemDefinition":
creator = new BaseTandem(_object, _style); case "FortChallengeBundleItemDefinition":
return true; creator = new BaseBundle(_object, _style);
case "FortTrapItemDefinition": return true;
case "FortSpyTechItemDefinition": // case "AthenaSeasonItemDefinition":
case "FortAccoladeItemDefinition": // creator = new BaseSeason(_object, _style);
case "FortContextTrapItemDefinition": // return true;
case "FortWeaponRangedItemDefinition": case "FortItemAccessTokenType":
case "Daybreak_LevelExitVehicle_PartItemDefinition_C": creator = new BaseItemAccessToken(_object, _style);
creator = new BaseIconStats(_object, _style); return true;
return true; case "FortCreativeOption":
case "FortItemSeriesDefinition": case "PlaylistUserOptionEnum":
creator = new BaseSeries(_object, _style); case "PlaylistUserOptionBool":
return true; case "PlaylistUserOptionString":
case "MaterialInstanceConstant" case "PlaylistUserOptionIntEnum":
when _object.Owner != null && case "PlaylistUserOptionIntRange":
(_object.Owner.Name.Contains("/MI_OfferImages/", StringComparison.OrdinalIgnoreCase) || case "PlaylistUserOptionColorEnum":
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) || case "PlaylistUserOptionFloatEnum":
_object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)): case "PlaylistUserOptionFloatRange":
creator = new BaseMaterialInstance(_object, _style); case "PlaylistUserTintedIconIntEnum":
return true; case "PlaylistUserOptionPrimaryAsset":
case "AthenaItemShopOfferDisplayData": case "PlaylistUserOptionCollisionProfileEnum":
creator = new BaseOfferDisplayData(_object, _style); creator = new BaseUserControl(_object, _style);
return true; return true;
case "FortMtxOfferData": // PandaGame
creator = new BaseMtxOffer(_object, _style); case "CharacterData":
return true; creator = new BaseFighter(_object, _style);
case "FortPlaylistAthena": return true;
creator = new BasePlaylist(_object, _style); case "PerkGroup":
return true; creator = new BasePerkGroup(_object, _style);
case "FortFeatItemDefinition": return true;
case "FortQuestItemDefinition": case "StatTrackingBundleData":
case "FortQuestItemDefinition_Athena": case "HydraSyncedDataAsset":
case "FortQuestItemDefinition_Campaign": case "AnnouncerPackData":
case "AthenaDailyQuestDefinition": case "CharacterGiftData":
case "FortUrgentQuestItemDefinition": case "ProfileIconData":
creator = new Bases.FN.BaseQuest(_object, _style); case "RingOutVfxData":
return true; case "BannerData":
case "FortCompendiumItemDefinition": case "EmoteData":
case "FortChallengeBundleItemDefinition": case "TauntData":
creator = new BaseBundle(_object, _style); case "SkinData":
return true; case "PerkData":
// case "AthenaSeasonItemDefinition": creator = new BasePandaIcon(_object, _style);
// creator = new BaseSeason(_object, _style); return true;
// return true; case "QuestData":
case "FortItemAccessTokenType": creator = new Bases.MV.BaseQuest(_object, _style);
creator = new BaseItemAccessToken(_object, _style); return true;
return true; default:
case "FortCreativeOption": creator = null;
case "PlaylistUserOptionEnum": return false;
case "PlaylistUserOptionBool": }
case "PlaylistUserOptionString": }
case "PlaylistUserOptionIntEnum":
case "PlaylistUserOptionIntRange": public override string ToString() => $"{_object.ExportType} | {_style}";
case "PlaylistUserOptionColorEnum":
case "PlaylistUserOptionFloatEnum": public void Dispose()
case "PlaylistUserOptionFloatRange": {
case "PlaylistUserTintedIconIntEnum": _object = null;
case "PlaylistUserOptionPrimaryAsset": }
case "PlaylistUserOptionCollisionProfileEnum": }
creator = new BaseUserControl(_object, _style);
return true;
// PandaGame
case "CharacterData":
creator = new BaseFighter(_object, _style);
return true;
case "PerkGroup":
creator = new BasePerkGroup(_object, _style);
return true;
case "StatTrackingBundleData":
case "HydraSyncedDataAsset":
case "AnnouncerPackData":
case "CharacterGiftData":
case "ProfileIconData":
case "RingOutVfxData":
case "BannerData":
case "EmoteData":
case "TauntData":
case "SkinData":
case "PerkData":
creator = new BasePandaIcon(_object, _style);
return true;
case "QuestData":
creator = new Bases.MV.BaseQuest(_object, _style);
return true;
default:
creator = null;
return false;
}
}
public override string ToString() => $"{_object.ExportType} | {_style}";
public void Dispose()
{
_object = null;
}
}

View File

@ -11,7 +11,6 @@ using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.UObject; 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 FModel.Framework; using FModel.Framework;
using FModel.Extensions; using FModel.Extensions;
using FModel.Services; using FModel.Services;
@ -72,7 +71,6 @@ public static class Utils
return GetBitmap(material); return GetBitmap(material);
default: default:
{ {
if (export.TryGetValue(out FInstancedStruct[] dataList, "DataList")) return GetBitmap(dataList);
if (export.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage); if (export.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage);
if (export.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview); if (export.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
if (export.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage")) if (export.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage"))
@ -87,21 +85,6 @@ public static class Utils
} }
} }
public static SKBitmap GetBitmap(FInstancedStruct[] structs)
{
if (structs.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FSoftObjectPath p, "LargeIcon") == true && !p.AssetPathName.IsNone) is { NonConstStruct: not null } isl)
{
return GetBitmap(isl.NonConstStruct.Get<FSoftObjectPath>("LargeIcon"));
}
if (structs.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FSoftObjectPath p, "Icon") == true && !p.AssetPathName.IsNone) is { NonConstStruct: not null } isi)
{
return GetBitmap(isi.NonConstStruct.Get<FSoftObjectPath>("Icon"));
}
return null;
}
public static SKBitmap GetBitmap(UMaterialInstanceConstant material) public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
{ {
if (material == null) return null; if (material == null) return null;
@ -417,4 +400,4 @@ public static class Utils
return ret; return ret;
} }
} }

View File

@ -20,7 +20,8 @@ public enum EErrorKind
public enum SettingsOut public enum SettingsOut
{ {
ReloadLocres, ReloadLocres,
ReloadMappings ReloadMappings,
CheckForUpdates
} }
public enum EStatusKind public enum EStatusKind
@ -53,6 +54,8 @@ public enum EDiscordRpc
public enum ELoadingMode public enum ELoadingMode
{ {
[Description("Single")]
Single,
[Description("Multiple")] [Description("Multiple")]
Multiple, Multiple,
[Description("All")] [Description("All")]
@ -63,15 +66,15 @@ public enum ELoadingMode
AllButModified AllButModified
} }
// public enum EUpdateMode public enum EUpdateMode
// { {
// [Description("Stable")] [Description("Stable")]
// Stable, Stable,
// [Description("Beta")] [Description("Beta")]
// Beta, Beta,
// [Description("QA Testing")] [Description("QA Testing")]
// Qa Qa
// } }
public enum ECompressedAudio public enum ECompressedAudio
{ {

View File

@ -11,21 +11,8 @@ public static class EnumExtensions
{ {
var fi = value.GetType().GetField(value.ToString()); var fi = value.GetType().GetField(value.ToString());
if (fi == null) return $"{value} ({value:D})"; if (fi == null) return $"{value} ({value:D})";
var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false); var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0) return attributes[0].Description; return attributes.Length > 0 ? attributes[0].Description : $"{value} ({value:D})";
var suffix = $"{value:D}";
var current = Convert.ToInt32(suffix);
var target = current & ~0xF;
if (current != target)
{
var values = Enum.GetValues(value.GetType());
var index = Array.IndexOf(values, value);
suffix = values.GetValue(index - (current - target))?.ToString();
}
return $"{value} ({suffix})";
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -56,4 +43,4 @@ public static class EnumExtensions
var i = Array.IndexOf(values, value) - 1; var i = Array.IndexOf(values, value) - 1;
return i == -1 ? (T) values.GetValue(values.Length - 1) : (T) values.GetValue(i); return i == -1 ? (T) values.GetValue(values.Length - 1) : (T) values.GetValue(i);
} }
} }

View File

@ -2,7 +2,6 @@ using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using ICSharpCode.AvalonEdit.Document;
namespace FModel.Extensions; namespace FModel.Extensions;
@ -95,7 +94,7 @@ public static class StringExtensions
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetNameLineNumber(this string s, string lineToFind) public static int GetLineNumber(this string s, string lineToFind)
{ {
if (int.TryParse(lineToFind, out var index)) if (int.TryParse(lineToFind, out var index))
return s.GetLineNumber(index); return s.GetLineNumber(index);
@ -114,25 +113,6 @@ public static class StringExtensions
return 1; return 1;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetParentExportType(this TextDocument doc, int startOffset)
{
var line = doc.GetLineByOffset(startOffset);
var lineNumber = line.LineNumber - 1;
while (doc.GetText(line.Offset, line.Length) is { } content)
{
if (content.StartsWith(" \"Type\": \"", StringComparison.OrdinalIgnoreCase))
return content.Split("\"")[3];
lineNumber--;
if (lineNumber < 1) break;
line = doc.GetLineByNumber(lineNumber);
}
return string.Empty;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetKismetLineNumber(this string s, string input) public static int GetKismetLineNumber(this string s, string input)
{ {

View File

@ -2,12 +2,12 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<ApplicationIcon>FModel.ico</ApplicationIcon> <ApplicationIcon>FModel.ico</ApplicationIcon>
<Version>4.4.4.0</Version> <Version>4.4.3.4</Version>
<AssemblyVersion>4.4.4.0</AssemblyVersion> <AssemblyVersion>4.4.3.4</AssemblyVersion>
<FileVersion>4.4.4.0</FileVersion> <FileVersion>4.4.3.4</FileVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsPublishable>true</IsPublishable> <IsPublishable>true</IsPublishable>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
@ -45,12 +45,6 @@
<None Remove="Resources\gear.png" /> <None Remove="Resources\gear.png" />
<None Remove="Resources\localization.png" /> <None Remove="Resources\localization.png" />
<None Remove="Resources\materialicon.png" /> <None Remove="Resources\materialicon.png" />
<None Remove="Resources\square.png" />
<None Remove="Resources\square_off.png" />
<None Remove="Resources\cube.png" />
<None Remove="Resources\cube_off.png" />
<None Remove="Resources\light.png" />
<None Remove="Resources\light_off.png" />
<None Remove="Resources\pc.png" /> <None Remove="Resources\pc.png" />
<None Remove="Resources\puzzle.png" /> <None Remove="Resources\puzzle.png" />
<None Remove="Resources\roguecompany.png" /> <None Remove="Resources\roguecompany.png" />
@ -116,7 +110,6 @@
<None Remove="Resources\light.vert" /> <None Remove="Resources\light.vert" />
<None Remove="Resources\bone.frag" /> <None Remove="Resources\bone.frag" />
<None Remove="Resources\bone.vert" /> <None Remove="Resources\bone.vert" />
<None Remove="Resources\collision.vert" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -142,34 +135,34 @@
<EmbeddedResource Include="Resources\light.vert" /> <EmbeddedResource Include="Resources\light.vert" />
<EmbeddedResource Include="Resources\bone.frag" /> <EmbeddedResource Include="Resources\bone.frag" />
<EmbeddedResource Include="Resources\bone.vert" /> <EmbeddedResource Include="Resources\bone.vert" />
<EmbeddedResource Include="Resources\collision.vert" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<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.8.3" />
<PackageReference Include="AvalonEdit" Version="6.3.0.90" /> <PackageReference Include="AvalonEdit" Version="6.3.0.90" />
<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.2.1.24" />
<PackageReference Include="EpicManifestParser" Version="2.3.3" /> <PackageReference Include="ImGui.NET" Version="1.89.7.1" />
<PackageReference Include="ImGui.NET" Version="1.91.0.1" /> <PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.5" />
<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.3" />
<PackageReference Include="NVorbis" Version="0.10.5" /> <PackageReference Include="NVorbis" Version="0.10.5" />
<PackageReference Include="Oodle.NET" Version="1.0.1" />
<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.8.0" />
<PackageReference Include="RestSharp" Version="112.1.0" /> <PackageReference Include="RestSharp" Version="110.2.0" />
<PackageReference Include="Serilog" Version="4.0.2" /> <PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.8" /> <PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.3" />
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" /> <PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj" /> <ProjectReference Include="..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse\CUE4Parse.csproj" /> <ProjectReference Include="..\CUE4Parse\CUE4Parse\CUE4Parse.csproj" />
<ProjectReference Include="..\EpicManifestParser\src\EpicManifestParser\EpicManifestParser.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -189,12 +182,6 @@
<Resource Include="Resources\gear.png" /> <Resource Include="Resources\gear.png" />
<Resource Include="Resources\localization.png" /> <Resource Include="Resources\localization.png" />
<Resource Include="Resources\materialicon.png" /> <Resource Include="Resources\materialicon.png" />
<Resource Include="Resources\square.png" />
<Resource Include="Resources\square_off.png" />
<Resource Include="Resources\cube.png" />
<Resource Include="Resources\cube_off.png" />
<Resource Include="Resources\light.png" />
<Resource Include="Resources\light_off.png" />
<Resource Include="Resources\pc.png" /> <Resource Include="Resources\pc.png" />
<Resource Include="Resources\puzzle.png" /> <Resource Include="Resources\puzzle.png" />
<Resource Include="Resources\roguecompany.png" /> <Resource Include="Resources\roguecompany.png" />

View File

@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse", "..\CUE4Parse\C
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Conversion", "..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj", "{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Conversion", "..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj", "{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EpicManifestParser", "..\EpicManifestParser\src\EpicManifestParser\EpicManifestParser.csproj", "{D4958A8B-815B-421D-A988-2A4E8E2B582D}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -27,6 +29,10 @@ Global
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.Build.0 = Debug|Any CPU {D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.ActiveCfg = Release|Any CPU {D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.Build.0 = Release|Any CPU {D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.Build.0 = Release|Any CPU
{D4958A8B-815B-421D-A988-2A4E8E2B582D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4958A8B-815B-421D-A988-2A4E8E2B582D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4958A8B-815B-421D-A988-2A4E8E2B582D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4958A8B-815B-421D-A988-2A4E8E2B582D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -1,19 +1,19 @@
using System; using System;
using RestSharp; using RestSharp;
namespace FModel.Framework; namespace FModel.Framework;
public class FRestRequest : RestRequest public class FRestRequest : RestRequest
{ {
private const int TimeoutSeconds = 5; private const int _timeout = 3 * 1000;
public FRestRequest(string url, Method method = Method.Get) : base(url, method) public FRestRequest(string url, Method method = Method.Get) : base(url, method)
{ {
Timeout = TimeSpan.FromSeconds(TimeoutSeconds); Timeout = _timeout;
} }
public FRestRequest(Uri uri, Method method = Method.Get) : base(uri, method) public FRestRequest(Uri uri, Method method = Method.Get) : base(uri, method)
{ {
Timeout = TimeSpan.FromSeconds(TimeoutSeconds); Timeout = _timeout;
} }
} }

View File

@ -38,8 +38,8 @@ public class FStatus : ViewModel
UpdateStatusLabel(label); UpdateStatusLabel(label);
} }
public void UpdateStatusLabel(string label, string prefix = null) public void UpdateStatusLabel(string label)
{ {
Label = Kind == EStatusKind.Loading ? $"{prefix ?? Kind.ToString()} {label}".Trim() : Kind.ToString(); Label = Kind == EStatusKind.Loading ? $"{Kind} {label}".Trim() : Kind.ToString();
} }
} }

View File

@ -1,13 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows; using System.Windows;
using System.Windows.Forms; using System.Windows.Forms;
using FModel.Settings;
using ImGuiNET; using ImGuiNET;
using OpenTK.Graphics.OpenGL4; using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Desktop; using OpenTK.Windowing.Desktop;
@ -37,6 +34,7 @@ public class ImGuiController : IDisposable
private int _windowWidth; private int _windowWidth;
private int _windowHeight; private int _windowHeight;
// private string _iniPath;
public ImFontPtr FontNormal; public ImFontPtr FontNormal;
public ImFontPtr FontBold; public ImFontPtr FontBold;
@ -51,6 +49,7 @@ public class ImGuiController : IDisposable
{ {
_windowWidth = width; _windowWidth = width;
_windowHeight = height; _windowHeight = height;
// _iniPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini");
int major = GL.GetInteger(GetPName.MajorVersion); int major = GL.GetInteger(GetPName.MajorVersion);
int minor = GL.GetInteger(GetPName.MinorVersion); int minor = GL.GetInteger(GetPName.MinorVersion);
@ -59,13 +58,9 @@ public class ImGuiController : IDisposable
IntPtr context = ImGui.CreateContext(); IntPtr context = ImGui.CreateContext();
ImGui.SetCurrentContext(context); ImGui.SetCurrentContext(context);
// ImGui.LoadIniSettingsFromDisk(_iniPath);
var io = ImGui.GetIO(); var io = ImGui.GetIO();
unsafe
{
var iniFileNamePtr = Marshal.StringToCoTaskMemUTF8(Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini"));
io.NativePtr->IniFilename = (byte*)iniFileNamePtr;
}
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);
@ -76,6 +71,7 @@ public class ImGuiController : IDisposable
io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines; io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines;
CreateDeviceResources(); CreateDeviceResources();
SetKeyMappings();
SetPerFrameImGuiData(1f / 60f); SetPerFrameImGuiData(1f / 60f);
@ -275,8 +271,8 @@ outputColor = color * texture(in_fontTexture, texCoord);
foreach (Keys key in Enum.GetValues(typeof(Keys))) foreach (Keys key in Enum.GetValues(typeof(Keys)))
{ {
if (key == Keys.Unknown) continue; if (key == Keys.Unknown || io.KeyMap[(int) key] == -1) continue;
io.AddKeyEvent(TranslateKey(key), kState.IsKeyDown(key)); io.AddKeyEvent((ImGuiKey) io.KeyMap[(int) key], kState.IsKeyDown(key));
} }
foreach (var c in PressedChars) foreach (var c in PressedChars)
@ -296,6 +292,115 @@ outputColor = color * texture(in_fontTexture, texCoord);
PressedChars.Add(keyChar); PressedChars.Add(keyChar);
} }
private static void SetKeyMappings()
{
ImGuiIOPtr io = ImGui.GetIO();
io.KeyMap[(int)ImGuiKey.LeftShift] = (int)Keys.LeftShift;
io.KeyMap[(int)ImGuiKey.RightShift] = (int)Keys.RightShift;
io.KeyMap[(int)ImGuiKey.LeftCtrl] = (int)Keys.LeftControl;
io.KeyMap[(int)ImGuiKey.RightCtrl] = (int)Keys.RightControl;
io.KeyMap[(int)ImGuiKey.LeftAlt] = (int)Keys.LeftAlt;
io.KeyMap[(int)ImGuiKey.RightAlt] = (int)Keys.RightAlt;
io.KeyMap[(int)ImGuiKey.LeftSuper] = (int)Keys.LeftSuper;
io.KeyMap[(int)ImGuiKey.RightSuper] = (int)Keys.RightSuper;
io.KeyMap[(int)ImGuiKey.Menu] = (int)Keys.Menu;
io.KeyMap[(int)ImGuiKey.UpArrow] = (int)Keys.Up;
io.KeyMap[(int)ImGuiKey.DownArrow] = (int)Keys.Down;
io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)Keys.Left;
io.KeyMap[(int)ImGuiKey.RightArrow] = (int)Keys.Right;
io.KeyMap[(int)ImGuiKey.Enter] = (int)Keys.Enter;
io.KeyMap[(int)ImGuiKey.Escape] = (int)Keys.Escape;
io.KeyMap[(int)ImGuiKey.Space] = (int)Keys.Space;
io.KeyMap[(int)ImGuiKey.Tab] = (int)Keys.Tab;
io.KeyMap[(int)ImGuiKey.Backspace] = (int)Keys.Backspace;
io.KeyMap[(int)ImGuiKey.Insert] = (int)Keys.Insert;
io.KeyMap[(int)ImGuiKey.Delete] = (int)Keys.Delete;
io.KeyMap[(int)ImGuiKey.PageUp] = (int)Keys.PageUp;
io.KeyMap[(int)ImGuiKey.PageDown] = (int)Keys.PageDown;
io.KeyMap[(int)ImGuiKey.Home] = (int)Keys.Home;
io.KeyMap[(int)ImGuiKey.End] = (int)Keys.End;
io.KeyMap[(int)ImGuiKey.CapsLock] = (int)Keys.CapsLock;
io.KeyMap[(int)ImGuiKey.ScrollLock] = (int)Keys.ScrollLock;
io.KeyMap[(int)ImGuiKey.PrintScreen] = (int)Keys.PrintScreen;
io.KeyMap[(int)ImGuiKey.Pause] = (int)Keys.Pause;
io.KeyMap[(int)ImGuiKey.NumLock] = (int)Keys.NumLock;
io.KeyMap[(int)ImGuiKey.KeypadDivide] = (int)Keys.KeyPadDivide;
io.KeyMap[(int)ImGuiKey.KeypadMultiply] = (int)Keys.KeyPadMultiply;
io.KeyMap[(int)ImGuiKey.KeypadSubtract] = (int)Keys.KeyPadSubtract;
io.KeyMap[(int)ImGuiKey.KeypadAdd] = (int)Keys.KeyPadAdd;
io.KeyMap[(int)ImGuiKey.KeypadDecimal] = (int)Keys.KeyPadDecimal;
io.KeyMap[(int)ImGuiKey.KeypadEnter] = (int)Keys.KeyPadEnter;
io.KeyMap[(int)ImGuiKey.GraveAccent] = (int)Keys.GraveAccent;
io.KeyMap[(int)ImGuiKey.Minus] = (int)Keys.Minus;
io.KeyMap[(int)ImGuiKey.Equal] = (int)Keys.Equal;
io.KeyMap[(int)ImGuiKey.LeftBracket] = (int)Keys.LeftBracket;
io.KeyMap[(int)ImGuiKey.RightBracket] = (int)Keys.RightBracket;
io.KeyMap[(int)ImGuiKey.Semicolon] = (int)Keys.Semicolon;
io.KeyMap[(int)ImGuiKey.Apostrophe] = (int)Keys.Apostrophe;
io.KeyMap[(int)ImGuiKey.Comma] = (int)Keys.Comma;
io.KeyMap[(int)ImGuiKey.Period] = (int)Keys.Period;
io.KeyMap[(int)ImGuiKey.Slash] = (int)Keys.Slash;
io.KeyMap[(int)ImGuiKey.Backslash] = (int)Keys.Backslash;
io.KeyMap[(int)ImGuiKey.F1] = (int)Keys.F1;
io.KeyMap[(int)ImGuiKey.F2] = (int)Keys.F2;
io.KeyMap[(int)ImGuiKey.F3] = (int)Keys.F3;
io.KeyMap[(int)ImGuiKey.F4] = (int)Keys.F4;
io.KeyMap[(int)ImGuiKey.F5] = (int)Keys.F5;
io.KeyMap[(int)ImGuiKey.F6] = (int)Keys.F6;
io.KeyMap[(int)ImGuiKey.F7] = (int)Keys.F7;
io.KeyMap[(int)ImGuiKey.F8] = (int)Keys.F8;
io.KeyMap[(int)ImGuiKey.F9] = (int)Keys.F9;
io.KeyMap[(int)ImGuiKey.F10] = (int)Keys.F10;
io.KeyMap[(int)ImGuiKey.F11] = (int)Keys.F11;
io.KeyMap[(int)ImGuiKey.F12] = (int)Keys.F12;
io.KeyMap[(int)ImGuiKey.Keypad0] = (int)Keys.KeyPad0;
io.KeyMap[(int)ImGuiKey.Keypad1] = (int)Keys.KeyPad1;
io.KeyMap[(int)ImGuiKey.Keypad2] = (int)Keys.KeyPad2;
io.KeyMap[(int)ImGuiKey.Keypad3] = (int)Keys.KeyPad3;
io.KeyMap[(int)ImGuiKey.Keypad4] = (int)Keys.KeyPad4;
io.KeyMap[(int)ImGuiKey.Keypad5] = (int)Keys.KeyPad5;
io.KeyMap[(int)ImGuiKey.Keypad6] = (int)Keys.KeyPad6;
io.KeyMap[(int)ImGuiKey.Keypad7] = (int)Keys.KeyPad7;
io.KeyMap[(int)ImGuiKey.Keypad8] = (int)Keys.KeyPad8;
io.KeyMap[(int)ImGuiKey.Keypad9] = (int)Keys.KeyPad9;
io.KeyMap[(int)ImGuiKey._0] = (int)Keys.D0;
io.KeyMap[(int)ImGuiKey._1] = (int)Keys.D1;
io.KeyMap[(int)ImGuiKey._2] = (int)Keys.D2;
io.KeyMap[(int)ImGuiKey._3] = (int)Keys.D3;
io.KeyMap[(int)ImGuiKey._4] = (int)Keys.D4;
io.KeyMap[(int)ImGuiKey._5] = (int)Keys.D5;
io.KeyMap[(int)ImGuiKey._6] = (int)Keys.D6;
io.KeyMap[(int)ImGuiKey._7] = (int)Keys.D7;
io.KeyMap[(int)ImGuiKey._8] = (int)Keys.D8;
io.KeyMap[(int)ImGuiKey._9] = (int)Keys.D9;
io.KeyMap[(int)ImGuiKey.A] = (int)Keys.A;
io.KeyMap[(int)ImGuiKey.B] = (int)Keys.B;
io.KeyMap[(int)ImGuiKey.C] = (int)Keys.C;
io.KeyMap[(int)ImGuiKey.D] = (int)Keys.D;
io.KeyMap[(int)ImGuiKey.E] = (int)Keys.E;
io.KeyMap[(int)ImGuiKey.F] = (int)Keys.F;
io.KeyMap[(int)ImGuiKey.G] = (int)Keys.G;
io.KeyMap[(int)ImGuiKey.H] = (int)Keys.H;
io.KeyMap[(int)ImGuiKey.I] = (int)Keys.I;
io.KeyMap[(int)ImGuiKey.J] = (int)Keys.J;
io.KeyMap[(int)ImGuiKey.K] = (int)Keys.K;
io.KeyMap[(int)ImGuiKey.L] = (int)Keys.L;
io.KeyMap[(int)ImGuiKey.M] = (int)Keys.M;
io.KeyMap[(int)ImGuiKey.N] = (int)Keys.N;
io.KeyMap[(int)ImGuiKey.O] = (int)Keys.O;
io.KeyMap[(int)ImGuiKey.P] = (int)Keys.P;
io.KeyMap[(int)ImGuiKey.Q] = (int)Keys.Q;
io.KeyMap[(int)ImGuiKey.R] = (int)Keys.R;
io.KeyMap[(int)ImGuiKey.S] = (int)Keys.S;
io.KeyMap[(int)ImGuiKey.T] = (int)Keys.T;
io.KeyMap[(int)ImGuiKey.U] = (int)Keys.U;
io.KeyMap[(int)ImGuiKey.V] = (int)Keys.V;
io.KeyMap[(int)ImGuiKey.W] = (int)Keys.W;
io.KeyMap[(int)ImGuiKey.X] = (int)Keys.X;
io.KeyMap[(int)ImGuiKey.Y] = (int)Keys.Y;
io.KeyMap[(int)ImGuiKey.Z] = (int)Keys.Z;
}
private void RenderImDrawData(ImDrawDataPtr draw_data) private void RenderImDrawData(ImDrawDataPtr draw_data)
{ {
if (draw_data.CmdListsCount == 0) if (draw_data.CmdListsCount == 0)
@ -335,7 +440,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBuffer); GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBuffer);
for (int i = 0; i < draw_data.CmdListsCount; i++) for (int i = 0; i < draw_data.CmdListsCount; i++)
{ {
ImDrawListPtr cmd_list = draw_data.CmdLists[i]; ImDrawListPtr cmd_list = draw_data.CmdListsRange[i];
int vertexSize = cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>(); int vertexSize = cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>();
if (vertexSize > _vertexBufferSize) if (vertexSize > _vertexBufferSize)
@ -385,7 +490,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
// Render command lists // Render command lists
for (int n = 0; n < draw_data.CmdListsCount; n++) for (int n = 0; n < draw_data.CmdListsCount; n++)
{ {
ImDrawListPtr cmd_list = draw_data.CmdLists[n]; ImDrawListPtr cmd_list = draw_data.CmdListsRange[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 * Unsafe.SizeOf<ImDrawVert>(), cmd_list.VtxBuffer.Data);
CheckGLError($"Data Vert {n}"); CheckGLError($"Data Vert {n}");
@ -538,71 +643,4 @@ outputColor = color * texture(in_fontTexture, texCoord);
{ {
return Math.Max((float)(Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth), (float)(Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight)); return Math.Max((float)(Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth), (float)(Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight));
} }
public static ImGuiKey TranslateKey(Keys key)
{
if (key is >= Keys.D0 and <= Keys.D9)
return key - Keys.D0 + ImGuiKey._0;
if (key is >= Keys.A and <= Keys.Z)
return key - Keys.A + ImGuiKey.A;
if (key is >= Keys.KeyPad0 and <= Keys.KeyPad9)
return key - Keys.KeyPad0 + ImGuiKey.Keypad0;
if (key is >= Keys.F1 and <= Keys.F24)
return key - Keys.F1 + ImGuiKey.F24;
return key switch
{
Keys.Tab => ImGuiKey.Tab,
Keys.Left => ImGuiKey.LeftArrow,
Keys.Right => ImGuiKey.RightArrow,
Keys.Up => ImGuiKey.UpArrow,
Keys.Down => ImGuiKey.DownArrow,
Keys.PageUp => ImGuiKey.PageUp,
Keys.PageDown => ImGuiKey.PageDown,
Keys.Home => ImGuiKey.Home,
Keys.End => ImGuiKey.End,
Keys.Insert => ImGuiKey.Insert,
Keys.Delete => ImGuiKey.Delete,
Keys.Backspace => ImGuiKey.Backspace,
Keys.Space => ImGuiKey.Space,
Keys.Enter => ImGuiKey.Enter,
Keys.Escape => ImGuiKey.Escape,
Keys.Apostrophe => ImGuiKey.Apostrophe,
Keys.Comma => ImGuiKey.Comma,
Keys.Minus => ImGuiKey.Minus,
Keys.Period => ImGuiKey.Period,
Keys.Slash => ImGuiKey.Slash,
Keys.Semicolon => ImGuiKey.Semicolon,
Keys.Equal => ImGuiKey.Equal,
Keys.LeftBracket => ImGuiKey.LeftBracket,
Keys.Backslash => ImGuiKey.Backslash,
Keys.RightBracket => ImGuiKey.RightBracket,
Keys.GraveAccent => ImGuiKey.GraveAccent,
Keys.CapsLock => ImGuiKey.CapsLock,
Keys.ScrollLock => ImGuiKey.ScrollLock,
Keys.NumLock => ImGuiKey.NumLock,
Keys.PrintScreen => ImGuiKey.PrintScreen,
Keys.Pause => ImGuiKey.Pause,
Keys.KeyPadDecimal => ImGuiKey.KeypadDecimal,
Keys.KeyPadDivide => ImGuiKey.KeypadDivide,
Keys.KeyPadMultiply => ImGuiKey.KeypadMultiply,
Keys.KeyPadSubtract => ImGuiKey.KeypadSubtract,
Keys.KeyPadAdd => ImGuiKey.KeypadAdd,
Keys.KeyPadEnter => ImGuiKey.KeypadEnter,
Keys.KeyPadEqual => ImGuiKey.KeypadEqual,
Keys.LeftShift => ImGuiKey.LeftShift,
Keys.LeftControl => ImGuiKey.LeftCtrl,
Keys.LeftAlt => ImGuiKey.LeftAlt,
Keys.LeftSuper => ImGuiKey.LeftSuper,
Keys.RightShift => ImGuiKey.RightShift,
Keys.RightControl => ImGuiKey.RightCtrl,
Keys.RightAlt => ImGuiKey.RightAlt,
Keys.RightSuper => ImGuiKey.RightSuper,
Keys.Menu => ImGuiKey.Menu,
_ => ImGuiKey.None
};
}
} }

View File

@ -1,32 +1,30 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.InteropServices;
using System.Windows; using System.Windows;
namespace FModel; namespace FModel;
public static class Helper public static class Helper
{ {
[StructLayout(LayoutKind.Explicit)]
private struct NanUnion
{
[FieldOffset(0)]
internal double DoubleValue;
[FieldOffset(0)]
internal readonly ulong UlongValue;
}
public static string FixKey(string key) public static string FixKey(string key)
{ {
if (string.IsNullOrEmpty(key)) if (string.IsNullOrEmpty(key))
return string.Empty; return string.Empty;
var keySpan = key.AsSpan().Trim(); if (key.StartsWith("0x"))
if (keySpan.Length > sizeof(char) * (2 /* 0x */ + 32 /* FAES = 256 bit */)) // maybe strictly check for length? key = key[2..];
return string.Empty; // bullshit key
Span<char> resultSpan = stackalloc char[keySpan.Length + 2 /* pad for 0x */]; return "0x" + key.ToUpper().Trim();
keySpan.ToUpperInvariant(resultSpan[2..]);
if (resultSpan[2..].StartsWith("0X"))
resultSpan = resultSpan[2..];
else
resultSpan[0] = '0';
resultSpan[1] = 'x';
return new string(resultSpan);
} }
public static void OpenWindow<T>(string windowName, Action action) where T : Window public static void OpenWindow<T>(string windowName, Action action) where T : Window
@ -76,9 +74,9 @@ public static class Helper
public static bool IsNaN(double value) public static bool IsNaN(double value)
{ {
var ulongValue = Unsafe.As<double, ulong>(ref value); var t = new NanUnion { DoubleValue = value };
var exp = ulongValue & 0xfff0000000000000; var exp = t.UlongValue & 0xfff0000000000000;
var man = ulongValue & 0x000fffffffffffff; var man = t.UlongValue & 0x000fffffffffffff;
return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0; return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0;
} }
@ -98,17 +96,13 @@ public static class Helper
return -d < n && d > n; return -d < n && d > n;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees) public static float DegreesToRadians(float degrees)
{ {
const float ratio = MathF.PI / 180f; return MathF.PI / 180f * degrees;
return ratio * degrees;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float RadiansToDegrees(float radians) public static float RadiansToDegrees(float radians)
{ {
const float ratio = 180f / MathF.PI; return radians* 180f / MathF.PI;
return radians * ratio;
} }
} }

View File

@ -147,11 +147,11 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Releases" Command="{Binding MenuCommand}" CommandParameter="Help_Releases"> <MenuItem Header="Changelog" Command="{Binding MenuCommand}" CommandParameter="Help_Changelog">
<MenuItem.Icon> <MenuItem.Icon>
<Viewbox Width="16" Height="16"> <Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24"> <Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GitHubIcon}" /> <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource NoteIcon}" />
</Canvas> </Canvas>
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
@ -321,16 +321,16 @@
<TreeView Grid.Row="2" x:Name="AssetsFolderName" Style="{StaticResource AssetsFolderTreeView}" PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick"> <TreeView Grid.Row="2" x:Name="AssetsFolderName" Style="{StaticResource AssetsFolderTreeView}" PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick">
<TreeView.ContextMenu> <TreeView.ContextMenu>
<ContextMenu> <ContextMenu>
<!-- <MenuItem Header="Extract Folder's Packages" Click="OnFolderExtractClick"> --> <MenuItem Header="Extract Folder's Packages" Click="OnFolderExtractClick">
<!-- <MenuItem.Icon> --> <MenuItem.Icon>
<!-- <Viewbox Width="16" Height="16"> --> <Viewbox Width="16" Height="16">
<!-- <Canvas Width="24" Height="24"> --> <Canvas Width="24" Height="24">
<!-- <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExtractIcon}" /> --> <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExtractIcon}" />
<!-- </Canvas> --> </Canvas>
<!-- </Viewbox> --> </Viewbox>
<!-- </MenuItem.Icon> --> </MenuItem.Icon>
<!-- </MenuItem> --> </MenuItem>
<!-- <Separator /> --> <Separator />
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)" Click="OnFolderExportClick"> <MenuItem Header="Export Folder's Packages Raw Data (.uasset)" Click="OnFolderExportClick">
<MenuItem.Icon> <MenuItem.Icon>
<Viewbox Width="16" Height="16"> <Viewbox Width="16" Height="16">
@ -349,7 +349,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Folder's Packages Textures" Click="OnFolderTextureClick"> <MenuItem Header="Save Folder's Packages Textures (.png)" Click="OnFolderTextureClick">
<MenuItem.Icon> <MenuItem.Icon>
<Viewbox Width="16" Height="16"> <Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24"> <Canvas Width="24" Height="24">
@ -358,7 +358,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Folder's Packages Models" Click="OnFolderModelClick"> <MenuItem Header="Save Folder's Packages Models (.psk)" Click="OnFolderModelClick">
<MenuItem.Icon> <MenuItem.Icon>
<Viewbox Width="16" Height="16"> <Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24"> <Canvas Width="24" Height="24">
@ -367,7 +367,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Folder's Packages Animations" Click="OnFolderAnimationClick"> <MenuItem Header="Save Folder's Packages Animations (.psa)" Click="OnFolderAnimationClick">
<MenuItem.Icon> <MenuItem.Icon>
<Viewbox Width="16" Height="16"> <Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24"> <Canvas Width="24" Height="24">
@ -517,7 +517,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Texture" Command="{Binding DataContext.RightClickMenuCommand}"> <MenuItem Header="Save Texture (.png)" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter> <MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}"> <MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Textures" /> <Binding Source="Assets_Save_Textures" />
@ -532,7 +532,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Model" Command="{Binding DataContext.RightClickMenuCommand}"> <MenuItem Header="Save Model (.psk)" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter> <MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}"> <MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Models" /> <Binding Source="Assets_Save_Models" />
@ -547,7 +547,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Animation" Command="{Binding DataContext.RightClickMenuCommand}"> <MenuItem Header="Save Animation (.psa)" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter> <MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}"> <MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Animations" /> <Binding Source="Assets_Save_Animations" />
@ -797,17 +797,13 @@
<Style TargetType="StatusBarItem"> <Style TargetType="StatusBarItem">
<Style.Triggers> <Style.Triggers>
<DataTrigger Binding="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" Value="False"> <DataTrigger Binding="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Collapsed" /> <Setter Property="Visibility" Value="Hidden" />
</DataTrigger> </DataTrigger>
</Style.Triggers> </Style.Triggers>
</Style> </Style>
</StatusBarItem.Style> </StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="SND" /> <TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="SND" />
</StatusBarItem> </StatusBarItem>
<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}}" />
</StatusBarItem>
</StackPanel> </StackPanel>
</StatusBarItem> </StatusBarItem>
</StatusBar> </StatusBar>

View File

@ -47,7 +47,7 @@ public partial class MainWindow
{ {
var newOrUpdated = UserSettings.Default.ShowChangelog; var newOrUpdated = UserSettings.Default.ShowChangelog;
#if !DEBUG #if !DEBUG
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(true); ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode, true);
#endif #endif
switch (UserSettings.Default.AesReload) switch (UserSettings.Default.AesReload)
@ -61,8 +61,6 @@ public partial class MainWindow
break; break;
} }
await ApplicationViewModel.InitOodle();
await ApplicationViewModel.InitZlib();
await _applicationView.CUE4Parse.Initialize(); await _applicationView.CUE4Parse.Initialize();
await _applicationView.AesManager.InitAes(); await _applicationView.AesManager.InitAes();
await _applicationView.UpdateProvider(true); await _applicationView.UpdateProvider(true);
@ -71,10 +69,12 @@ public partial class MainWindow
#endif #endif
await Task.WhenAll( await Task.WhenAll(
_applicationView.CUE4Parse.VerifyConsoleVariables(), _applicationView.CUE4Parse.VerifyConsoleVariables(),
_applicationView.CUE4Parse.VerifyOnDemandArchives(), _applicationView.CUE4Parse.VerifyVirtualCache(),
_applicationView.CUE4Parse.VerifyContentBuildManifest(),
_applicationView.CUE4Parse.InitMappings(), _applicationView.CUE4Parse.InitMappings(),
ApplicationViewModel.InitVgmStream(), _applicationView.InitImGuiSettings(newOrUpdated),
ApplicationViewModel.InitImGuiSettings(newOrUpdated), _applicationView.InitVgmStream(),
_applicationView.InitOodle(),
Task.Run(() => Task.Run(() =>
{ {
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always) if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
@ -83,12 +83,12 @@ public partial class MainWindow
).ConfigureAwait(false); ).ConfigureAwait(false);
#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")); "fortnitegame/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01.uasset"));
// await _threadWorkerView.Begin(cancellationToken => await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken, _applicationView.CUE4Parse.Extract(cancellationToken,
// "FortniteGame/Content/Environments/Helios/Props/GlacierHotel/GlacierHotel_Globe_A/Meshes/SM_GlacierHotel_Globe_A.uasset")); "fortnitegame/Content/Animation/Game/MainPlayer/Emotes/Cowbell/Cowbell_CMM_Loop_M.uasset"));
#endif #endif
} }

View File

@ -1,20 +0,0 @@
#version 460 core
layout (location = 0) in vec3 vPos;
uniform mat4 uView;
uniform mat4 uProjection;
uniform mat4 uInstanceMatrix;
uniform mat4 uCollisionMatrix;
uniform float uScaleDown;
out vec3 fPos;
out vec3 fColor;
void main()
{
gl_PointSize = 7.5f;
gl_Position = uProjection * uView * uInstanceMatrix * uCollisionMatrix * vec4(vPos.xzy * uScaleDown, 1.0);
fPos = vec3(uInstanceMatrix * uCollisionMatrix * vec4(vPos.xzy * uScaleDown, 1.0));
fColor = vec3(1.0);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -8,7 +8,7 @@ in vec3 fPos;
in vec3 fNormal; in vec3 fNormal;
in vec3 fTangent; in vec3 fTangent;
in vec2 fTexCoords; in vec2 fTexCoords;
flat in int fTexLayer; in float fTexLayer;
in vec4 fColor; in vec4 fColor;
struct Texture struct Texture
@ -89,7 +89,6 @@ uniform Parameters uParameters;
uniform Light uLights[MAX_LIGHT_COUNT]; uniform Light uLights[MAX_LIGHT_COUNT];
uniform int uNumLights; uniform int uNumLights;
uniform int uUvCount; uniform int uUvCount;
uniform float uOpacity;
uniform bool uHasVertexColors; uniform bool uHasVertexColors;
uniform vec3 uSectionColor; uniform vec3 uSectionColor;
uniform bool bVertexColors[6]; uniform bool bVertexColors[6];
@ -99,7 +98,7 @@ out vec4 FragColor;
int LayerToIndex() int LayerToIndex()
{ {
return clamp(fTexLayer, 0, uUvCount - 1); return clamp(int(fTexLayer), 0, uUvCount - 1);
} }
vec4 SamplerToVector(sampler2D s, vec2 coords) vec4 SamplerToVector(sampler2D s, vec2 coords)
@ -149,11 +148,6 @@ vec3 CalcLight(int layer, vec3 normals, vec3 position, vec3 color, float attenua
{ {
vec3 fLambert = SamplerToVector(uParameters.Diffuse[layer].Sampler).rgb * uParameters.Diffuse[layer].Color.rgb; vec3 fLambert = SamplerToVector(uParameters.Diffuse[layer].Sampler).rgb * uParameters.Diffuse[layer].Color.rgb;
vec3 specular_masks = SamplerToVector(uParameters.SpecularMasks[layer].Sampler).rgb; vec3 specular_masks = SamplerToVector(uParameters.SpecularMasks[layer].Sampler).rgb;
float cavity = specular_masks.g;
if (uParameters.HasAo)
{
cavity = SamplerToVector(uParameters.Ao.Sampler).g;
}
float roughness = mix(uParameters.RoughnessMin, uParameters.RoughnessMax, specular_masks.b); float roughness = mix(uParameters.RoughnessMin, uParameters.RoughnessMax, specular_masks.b);
vec3 l = normalize(uViewPos - fPos); vec3 l = normalize(uViewPos - fPos);
@ -171,7 +165,7 @@ vec3 CalcLight(int layer, vec3 normals, vec3 position, vec3 color, float attenua
vec3 kS = f; vec3 kS = f;
vec3 kD = 1.0 - kS; vec3 kD = 1.0 - kS;
kD *= 1.0 - cavity; kD *= 1.0 - specular_masks.g;
vec3 specBrdfNom = ggxDistribution(roughness, nDotH) * geomSmith(roughness, nDotL) * geomSmith(roughness, nDotV) * f; vec3 specBrdfNom = ggxDistribution(roughness, nDotH) * geomSmith(roughness, nDotL) * geomSmith(roughness, nDotV) * f;
float specBrdfDenom = 4.0 * nDotV * nDotL + 0.0001; float specBrdfDenom = 4.0 * nDotV * nDotL + 0.0001;
@ -218,32 +212,30 @@ vec3 CalcSpotLight(int layer, vec3 normals, Light light)
void main() void main()
{ {
int layer = LayerToIndex();
vec3 normals = ComputeNormals(layer);
vec3 lightDir = normalize(uViewPos - fPos);
float diffuseFactor = max(dot(normals, lightDir), 0.4);
if (bVertexColors[1]) if (bVertexColors[1])
{ {
FragColor = vec4(diffuseFactor * uSectionColor, uOpacity); FragColor = vec4(uSectionColor, 1.0);
} }
else if (bVertexColors[2] && uHasVertexColors) else if (bVertexColors[2] && uHasVertexColors)
{ {
FragColor = vec4(diffuseFactor * fColor.rgb, fColor.a); FragColor = fColor;
} }
else if (bVertexColors[3]) else if (bVertexColors[3])
{ {
FragColor = vec4(normals, uOpacity); int layer = LayerToIndex();
vec3 normals = ComputeNormals(layer);
FragColor = vec4(normals, 1.0);
} }
else if (bVertexColors[4]) else if (bVertexColors[4])
{ {
vec4 diffuse = SamplerToVector(uParameters.Diffuse[0].Sampler); FragColor = SamplerToVector(uParameters.Diffuse[0].Sampler);
FragColor = vec4(diffuseFactor * diffuse.rgb, diffuse.a);
} }
else else
{ {
int layer = LayerToIndex();
vec3 normals = ComputeNormals(layer);
vec4 diffuse = SamplerToVector(uParameters.Diffuse[layer].Sampler); vec4 diffuse = SamplerToVector(uParameters.Diffuse[layer].Sampler);
vec3 result = diffuseFactor * diffuse.rgb * uParameters.Diffuse[layer].Color.rgb; vec3 result = uParameters.Diffuse[layer].Color.rgb * diffuse.rgb;
if (uParameters.HasAo) if (uParameters.HasAo)
{ {
@ -253,7 +245,7 @@ void main()
vec3 color = uParameters.Ao.ColorBoost.Color * uParameters.Ao.ColorBoost.Exponent; vec3 color = uParameters.Ao.ColorBoost.Color * uParameters.Ao.ColorBoost.Exponent;
result = mix(result, result * color, m.b); result = mix(result, result * color, m.b);
} }
result *= m.r; result = vec3(uParameters.Ao.AmbientOcclusion) * result * m.r;
} }
vec2 coords = fTexCoords; vec2 coords = fTexCoords;
@ -271,7 +263,7 @@ void main()
} }
{ {
result += CalcLight(layer, normals, uViewPos, vec3(1.0), 1.0, false); result += CalcLight(layer, normals, uViewPos, vec3(0.75), 1.0, false);
vec3 lights = vec3(uNumLights > 0 ? 0 : 1); vec3 lights = vec3(uNumLights > 0 ? 0 : 1);
for (int i = 0; i < uNumLights; i++) for (int i = 0; i < uNumLights; i++)
@ -289,6 +281,6 @@ void main()
} }
result = result / (result + vec3(1.0)); result = result / (result + vec3(1.0));
FragColor = vec4(pow(result, vec3(1.0 / 2.2)), uOpacity); FragColor = vec4(pow(result, vec3(1.0 / 2.2)), 1.0);
} }
} }

View File

@ -4,10 +4,10 @@ layout (location = 1) in vec3 vPos;
layout (location = 2) in vec3 vNormal; layout (location = 2) in vec3 vNormal;
layout (location = 3) in vec3 vTangent; layout (location = 3) in vec3 vTangent;
layout (location = 4) in vec2 vTexCoords; layout (location = 4) in vec2 vTexCoords;
layout (location = 5) in int vTexLayer; layout (location = 5) in float vTexLayer;
layout (location = 6) in float vColor; layout (location = 6) in vec4 vColor;
layout (location = 7) in vec4 vBoneInfluence; layout (location = 7) in vec4 vBoneIds;
layout (location = 8) in vec4 vBoneInfluenceExtra; layout (location = 8) in vec4 vBoneWeights;
layout (location = 9) in mat4 vInstanceMatrix; layout (location = 9) in mat4 vInstanceMatrix;
layout (location = 13) in vec3 vMorphTargetPos; layout (location = 13) in vec3 vMorphTargetPos;
layout (location = 14) in vec3 vMorphTargetTangent; layout (location = 14) in vec3 vMorphTargetTangent;
@ -30,23 +30,9 @@ out vec3 fPos;
out vec3 fNormal; out vec3 fNormal;
out vec3 fTangent; out vec3 fTangent;
out vec2 fTexCoords; out vec2 fTexCoords;
flat out int fTexLayer; out float fTexLayer;
out vec4 fColor; out vec4 fColor;
vec4 unpackARGB(int color)
{
float a = float((color >> 24) & 0xFF);
float r = float((color >> 16) & 0xFF);
float g = float((color >> 8) & 0xFF);
float b = float((color >> 0) & 0xFF);
return vec4(r, g, b, a);
}
vec2 unpackBoneIDsAndWeights(int packedData)
{
return vec2(float((packedData >> 16) & 0xFFFF), float(packedData & 0xFFFF));
}
void main() void main()
{ {
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0); vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
@ -58,28 +44,19 @@ void main()
vec4 finalTangent = vec4(0.0); vec4 finalTangent = vec4(0.0);
if (uIsAnimated) if (uIsAnimated)
{ {
vec4 boneInfluences[2]; for(int i = 0 ; i < 4; i++)
boneInfluences[0] = vBoneInfluence;
boneInfluences[1] = vBoneInfluenceExtra;
for (int i = 0; i < 2; i++)
{ {
for(int j = 0 ; j < 4; j++) int boneIndex = int(vBoneIds[i]);
{ if(boneIndex < 0) break;
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
int boneIndex = int(boneInfluence.x);
float weight = boneInfluence.y;
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]); mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
mat4 inverseBoneMatrix = transpose(inverse(boneMatrix)); mat4 inverseBoneMatrix = transpose(inverse(boneMatrix));
float weight = vBoneWeights[i];
finalPos += boneMatrix * bindPos * weight; finalPos += boneMatrix * bindPos * weight;
finalNormal += inverseBoneMatrix * bindNormal * weight; finalNormal += inverseBoneMatrix * bindNormal * weight;
finalTangent += inverseBoneMatrix * bindTangent * weight; finalTangent += inverseBoneMatrix * bindTangent * weight;
}
} }
finalPos = normalize(finalPos);
finalNormal = normalize(finalNormal);
finalTangent = normalize(finalTangent);
} }
else else
{ {
@ -95,5 +72,5 @@ void main()
fTangent = vec3(transpose(inverse(vInstanceMatrix)) * finalTangent); fTangent = vec3(transpose(inverse(vInstanceMatrix)) * finalTangent);
fTexCoords = vTexCoords; fTexCoords = vTexCoords;
fTexLayer = vTexLayer; fTexLayer = vTexLayer;
fColor = unpackARGB(int(vColor)) / 255.0; fColor = vColor;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -2,8 +2,8 @@
layout (location = 1) in vec3 vPos; layout (location = 1) in vec3 vPos;
layout (location = 2) in vec3 vNormal; layout (location = 2) in vec3 vNormal;
layout (location = 7) in vec4 vBoneInfluence; layout (location = 7) in vec4 vBoneIds;
layout (location = 8) in vec4 vBoneInfluenceExtra; layout (location = 8) in vec4 vBoneWeights;
layout (location = 9) in mat4 vInstanceMatrix; layout (location = 9) in mat4 vInstanceMatrix;
layout (location = 13) in vec3 vMorphTargetPos; layout (location = 13) in vec3 vMorphTargetPos;
@ -22,44 +22,25 @@ uniform mat4 uProjection;
uniform float uMorphTime; uniform float uMorphTime;
uniform bool uIsAnimated; uniform bool uIsAnimated;
vec2 unpackBoneIDsAndWeights(int packedData)
{
return vec2(float((packedData >> 16) & 0xFFFF), float(packedData & 0xFFFF));
}
vec4 calculateScale(vec4 bindPos, vec4 bindNormal)
{
vec4 worldPos = vInstanceMatrix * bindPos;
float scaleFactor = length(uViewPos - worldPos.xyz) * 0.0035;
return transpose(inverse(vInstanceMatrix)) * bindNormal * scaleFactor;
}
void main() void main()
{ {
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0); vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
vec4 bindNormal = vec4(vNormal, 1.0); vec4 bindNormal = vec4(vNormal, 1.0);
bindPos.xyz += calculateScale(bindPos, bindNormal).xyz;
vec4 finalPos = vec4(0.0); vec4 finalPos = vec4(0.0);
vec4 finalNormal = vec4(0.0); vec4 finalNormal = vec4(0.0);
if (uIsAnimated) if (uIsAnimated)
{ {
vec4 boneInfluences[2]; for(int i = 0 ; i < 4; i++)
boneInfluences[0] = vBoneInfluence;
boneInfluences[1] = vBoneInfluenceExtra;
for(int i = 0 ; i < 2; i++)
{ {
for(int j = 0; j < 4; j++) int boneIndex = int(vBoneIds[i]);
{ if(boneIndex < 0) break;
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
int boneIndex = int(boneInfluence.x);
float weight = boneInfluence.y;
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]); mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
float weight = vBoneWeights[i];
finalPos += boneMatrix * bindPos * weight; finalPos += boneMatrix * bindPos * weight;
finalNormal += transpose(inverse(boneMatrix)) * bindNormal * weight; finalNormal += transpose(inverse(boneMatrix)) * bindNormal * weight;
}
} }
} }
else else
@ -68,5 +49,10 @@ void main()
finalNormal = bindNormal; finalNormal = bindNormal;
} }
gl_Position = uProjection * uView * vInstanceMatrix * finalPos; finalPos = vInstanceMatrix * finalPos;
float scaleFactor = distance(vec3(finalPos), uViewPos) * 0.0035;
vec4 nor = transpose(inverse(vInstanceMatrix)) * normalize(finalNormal) * scaleFactor;
finalPos.xyz += nor.xyz;
gl_Position = uProjection * uView * finalPos;
} }

View File

@ -1,8 +1,8 @@
#version 460 core #version 460 core
layout (location = 1) in vec3 vPos; layout (location = 1) in vec3 vPos;
layout (location = 7) in vec4 vBoneInfluence; layout (location = 7) in vec4 vBoneIds;
layout (location = 8) in vec4 vBoneInfluenceExtra; layout (location = 8) in vec4 vBoneWeights;
layout (location = 9) in mat4 vInstanceMatrix; layout (location = 9) in mat4 vInstanceMatrix;
layout (location = 13) in vec3 vMorphTargetPos; layout (location = 13) in vec3 vMorphTargetPos;
@ -20,11 +20,6 @@ uniform mat4 uProjection;
uniform float uMorphTime; uniform float uMorphTime;
uniform bool uIsAnimated; uniform bool uIsAnimated;
vec2 unpackBoneIDsAndWeights(int packedData)
{
return vec2(float((packedData >> 16) & 0xFFFF), float(packedData & 0xFFFF));
}
void main() void main()
{ {
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0); vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
@ -32,19 +27,12 @@ void main()
vec4 finalPos = vec4(0.0); vec4 finalPos = vec4(0.0);
if (uIsAnimated) if (uIsAnimated)
{ {
vec4 boneInfluences[2]; for(int i = 0 ; i < 4; i++)
boneInfluences[0] = vBoneInfluence;
boneInfluences[1] = vBoneInfluenceExtra;
for(int i = 0 ; i < 2; i++)
{ {
for(int j = 0; j < 4; j++) int boneIndex = int(vBoneIds[i]);
{ if(boneIndex < 0) break;
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
int boneIndex = int(boneInfluence.x);
float weight = boneInfluence.y;
finalPos += uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]) * bindPos * weight; finalPos += uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]) * bindPos * vBoneWeights[i];
}
} }
} }
else finalPos = bindPos; else finalPos = bindPos;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -13,9 +13,9 @@ public class CustomDirectory : ViewModel
case "Fortnite [LIVE]": case "Fortnite [LIVE]":
return new List<CustomDirectory> return new List<CustomDirectory>
{ {
new("Cosmetics", "FortniteGame/Plugins/GameFeatures/BRCosmetics/Content/Athena/Items/Cosmetics/"), new("Cosmetics", "FortniteGame/Content/Athena/Items/Cosmetics/"),
new("Emotes [AUDIO]", "FortniteGame/Plugins/GameFeatures/BRCosmetics/Content/Athena/Sounds/Emotes/"), new("Emotes [AUDIO]", "FortniteGame/Content/Athena/Sounds/Emotes/"),
new("Music Packs [AUDIO]", "FortniteGame/Plugins/GameFeatures/BRCosmetics/Content/Athena/Sounds/MusicPacks/"), new("Music Packs [AUDIO]", "FortniteGame/Content/Athena/Sounds/MusicPacks/"),
new("Weapons", "FortniteGame/Content/Athena/Items/Weapons/"), new("Weapons", "FortniteGame/Content/Athena/Items/Weapons/"),
new("Strings", "FortniteGame/Content/Localization/") new("Strings", "FortniteGame/Content/Localization/")
}; };

View File

@ -113,11 +113,6 @@ public class DirectorySettings : ViewModel, ICloneable
return HashCode.Combine(GameDirectory, (int) UeVersion); return HashCode.Combine(GameDirectory, (int) UeVersion);
} }
public override string ToString()
{
return GameName;
}
public object Clone() public object Clone()
{ {
return this.MemberwiseClone(); return this.MemberwiseClone();

View File

@ -1,109 +1,109 @@
using System.Linq; using System.Linq;
using FModel.Framework; using FModel.Framework;
using FModel.ViewModels.ApiEndpoints; using FModel.ViewModels.ApiEndpoints;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace FModel.Settings; namespace FModel.Settings;
public class EndpointSettings : ViewModel public class EndpointSettings : ViewModel
{ {
public static EndpointSettings[] Default(string gameName) public static EndpointSettings[] Default(string gameName)
{ {
switch (gameName) switch (gameName)
{ {
case "Fortnite": case "Fortnite":
case "Fortnite [LIVE]": case "Fortnite [LIVE]":
return new EndpointSettings[] return new EndpointSettings[]
{ {
new("https://fortnitecentral.genxgames.gg/api/v1/aes", "$.['mainKey','dynamicKeys']"), new("https://fortnitecentral.genxgames.gg/api/v1/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://fortnitecentral.genxgames.gg/api/v1/mappings", "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']")
}; };
default: default:
return new EndpointSettings[] { new(), new() }; return new EndpointSettings[] { new(), new() };
} }
} }
private string _url; private string _url;
public string Url public string Url
{ {
get => _url; get => _url;
set => SetProperty(ref _url, value); set => SetProperty(ref _url, value);
} }
private string _path; private string _path;
public string Path public string Path
{ {
get => _path; get => _path;
set => SetProperty(ref _path, value); set => SetProperty(ref _path, value);
} }
private bool _overwrite; private bool _overwrite;
public bool Overwrite public bool Overwrite
{ {
get => _overwrite; get => _overwrite;
set => SetProperty(ref _overwrite, value); set => SetProperty(ref _overwrite, value);
} }
private string _filePath; private string _filePath;
public string FilePath public string FilePath
{ {
get => _filePath; get => _filePath;
set => SetProperty(ref _filePath, value); set => SetProperty(ref _filePath, value);
} }
private bool _isValid; private bool _isValid;
public bool IsValid public bool IsValid
{ {
get => _isValid; get => _isValid;
set set
{ {
SetProperty(ref _isValid, value); SetProperty(ref _isValid, value);
RaisePropertyChanged(nameof(Label)); RaisePropertyChanged(nameof(Label));
} }
} }
[JsonIgnore] [JsonIgnore]
public string Label => IsValid ? public string Label => IsValid ?
"Your endpoint configuration is valid! Please, avoid any unnecessary modifications!" : "Your endpoint configuration is valid! Please, avoid any unnecessary modifications!" :
"Your endpoint configuration DOES NOT seem to be valid yet! Please, test it out!"; "Your endpoint configuration DOES NOT seem to be valid yet! Please, test it out!";
public EndpointSettings() {} public EndpointSettings() {}
public EndpointSettings(string url, string path) public EndpointSettings(string url, string path)
{ {
Url = url; Url = url;
Path = path; Path = path;
IsValid = !string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(path); // be careful with this IsValid = !string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(path); // be careful with this
} }
public void TryValidate(DynamicApiEndpoint endpoint, EEndpointType type, out JToken response) public void TryValidate(DynamicApiEndpoint endpoint, EEndpointType type, out JToken response)
{ {
response = null; response = null;
if (string.IsNullOrEmpty(Url) || string.IsNullOrEmpty(Path)) if (string.IsNullOrEmpty(Url) || string.IsNullOrEmpty(Path))
{ {
IsValid = false; IsValid = false;
} }
else switch (type) else switch (type)
{ {
case EEndpointType.Aes: case EEndpointType.Aes:
{ {
var r = endpoint.GetAesKeys(default, Url, Path); var r = endpoint.GetAesKeys(default, Url, Path);
response = JToken.FromObject(r); response = JToken.FromObject(r);
IsValid = r.IsValid; IsValid = r.IsValid;
break; break;
} }
case EEndpointType.Mapping: case EEndpointType.Mapping:
{ {
var r = endpoint.GetMappings(default, Url, Path); var r = endpoint.GetMappings(default, Url, Path);
response = JToken.FromObject(r); response = JToken.FromObject(r);
IsValid = r.Any(x => x.IsValid); IsValid = r.Any(x => x.IsValid);
break; break;
} }
default: default:
{ {
IsValid = false; IsValid = false;
break; break;
} }
} }
} }
} }

View File

@ -3,12 +3,9 @@ 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_Conversion;
using CUE4Parse_Conversion.Animations;
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.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.Material;
using FModel.Framework; using FModel.Framework;
using FModel.ViewModels; using FModel.ViewModels;
@ -32,21 +29,16 @@ namespace FModel.Settings
Default = new UserSettings(); Default = new UserSettings();
} }
private static bool _bSave = true;
public static void Save() public static void Save()
{ {
if (!_bSave || Default == null) return; if (Default == null) return;
Default.PerDirectory[Default.CurrentDir.GameDirectory] = Default.CurrentDir; Default.PerDirectory[Default.CurrentDir.GameDirectory] = Default.CurrentDir;
File.WriteAllText(FilePath, JsonConvert.SerializeObject(Default, Formatting.Indented)); File.WriteAllText(FilePath, JsonConvert.SerializeObject(Default, Formatting.Indented));
} }
public static void Delete() public static void Delete()
{ {
if (File.Exists(FilePath)) if (File.Exists(FilePath)) File.Delete(FilePath);
{
_bSave = false;
File.Delete(FilePath);
}
} }
public static bool IsEndpointValid(EEndpointType type, out EndpointSettings endpoint) public static bool IsEndpointValid(EEndpointType type, out EndpointSettings endpoint)
@ -55,25 +47,6 @@ namespace FModel.Settings
return endpoint.Overwrite || endpoint.IsValid; return endpoint.Overwrite || endpoint.IsValid;
} }
[JsonIgnore]
public ExporterOptions ExportOptions => new()
{
LodFormat = Default.LodExportFormat,
MeshFormat = Default.MeshExportFormat,
AnimFormat = Default.MeshExportFormat switch
{
EMeshFormat.UEFormat => EAnimFormat.UEFormat,
_ => EAnimFormat.ActorX
},
MaterialFormat = Default.MaterialExportFormat,
TextureFormat = Default.TextureExportFormat,
SocketFormat = Default.SocketExportFormat,
CompressionFormat = Default.CompressionFormat,
Platform = Default.CurrentDir.TexturePlatform,
ExportMorphTargets = Default.SaveMorphTargets,
ExportMaterials = Default.SaveEmbeddedMaterials
};
private bool _showChangelog = true; private bool _showChangelog = true;
public bool ShowChangelog public bool ShowChangelog
{ {
@ -179,18 +152,18 @@ namespace FModel.Settings
set => SetProperty(ref _loadingMode, value); set => SetProperty(ref _loadingMode, value);
} }
private DateTime _lastUpdateCheck = DateTime.MinValue; private EUpdateMode _updateMode = EUpdateMode.Beta;
public DateTime LastUpdateCheck public EUpdateMode UpdateMode
{ {
get => _lastUpdateCheck; get => _updateMode;
set => SetProperty(ref _lastUpdateCheck, value); set => SetProperty(ref _updateMode, value);
} }
private DateTime _nextUpdateCheck = DateTime.Now; private string _commitId = Constants.APP_VERSION;
public DateTime NextUpdateCheck public string CommitId
{ {
get => _nextUpdateCheck; get => _commitId;
set => SetProperty(ref _nextUpdateCheck, value); set => SetProperty(ref _commitId, value);
} }
private bool _keepDirectoryStructure = true; private bool _keepDirectoryStructure = true;
@ -265,6 +238,8 @@ namespace FModel.Settings
[JsonIgnore] [JsonIgnore]
public DirectorySettings CurrentDir { get; set; } public DirectorySettings CurrentDir { get; set; }
[JsonIgnore]
public string ShortCommitId => CommitId[..7];
/// <summary> /// <summary>
/// TO DELETEEEEEEEEEEEEE /// TO DELETEEEEEEEEEEEEE
@ -381,13 +356,6 @@ namespace FModel.Settings
set => SetProperty(ref _socketExportFormat, value); set => SetProperty(ref _socketExportFormat, value);
} }
private EFileCompressionFormat _compressionFormat = EFileCompressionFormat.ZSTD;
public EFileCompressionFormat CompressionFormat
{
get => _compressionFormat;
set => SetProperty(ref _compressionFormat, value);
}
private ELodFormat _lodExportFormat = ELodFormat.FirstLod; private ELodFormat _lodExportFormat = ELodFormat.FirstLod;
public ELodFormat LodExportFormat public ELodFormat LodExportFormat
{ {

View File

@ -8,6 +8,7 @@ using FModel.Framework;
using FModel.Services; using FModel.Services;
using FModel.Settings; using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models; using FModel.ViewModels.ApiEndpoints.Models;
using Serilog;
namespace FModel.ViewModels; namespace FModel.ViewModels;

View File

@ -1,4 +1,4 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using FModel.Framework; using FModel.Framework;
@ -12,7 +12,7 @@ public class ApiEndpointViewModel
private readonly RestClient _client = new (new RestClientOptions private readonly RestClient _client = new (new RestClientOptions
{ {
UserAgent = $"FModel/{Constants.APP_VERSION}", UserAgent = $"FModel/{Constants.APP_VERSION}",
Timeout = TimeSpan.FromSeconds(5) MaxTimeout = 3 * 1000
}, configureSerialization: s => s.UseSerializer<JsonNetSerializer>()); }, configureSerialization: s => s.UseSerializer<JsonNetSerializer>());
public FortniteApiEndpoint FortniteApi { get; } public FortniteApiEndpoint FortniteApi { get; }
@ -20,7 +20,6 @@ public class ApiEndpointViewModel
public FortniteCentralApiEndpoint CentralApi { get; } public FortniteCentralApiEndpoint CentralApi { get; }
public EpicApiEndpoint EpicApi { get; } public EpicApiEndpoint EpicApi { get; }
public FModelApiEndpoint FModelApi { get; } public FModelApiEndpoint FModelApi { get; }
public GitHubApiEndpoint GitHubApi { get; }
public DynamicApiEndpoint DynamicApi { get; } public DynamicApiEndpoint DynamicApi { get; }
public ApiEndpointViewModel() public ApiEndpointViewModel()
@ -30,7 +29,6 @@ public class ApiEndpointViewModel
CentralApi = new FortniteCentralApiEndpoint(_client); CentralApi = new FortniteCentralApiEndpoint(_client);
EpicApi = new EpicApiEndpoint(_client); EpicApi = new EpicApiEndpoint(_client);
FModelApi = new FModelApiEndpoint(_client); FModelApi = new FModelApiEndpoint(_client);
GitHubApi = new GitHubApiEndpoint(_client);
DynamicApi = new DynamicApiEndpoint(_client); DynamicApi = new DynamicApiEndpoint(_client);
} }

View File

@ -1,14 +1,10 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using EpicManifestParser.Objects;
using EpicManifestParser.Api;
using FModel.Framework; using FModel.Framework;
using FModel.Settings; using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models; using FModel.ViewModels.ApiEndpoints.Models;
using RestSharp; using RestSharp;
using Serilog; using Serilog;
namespace FModel.ViewModels.ApiEndpoints; namespace FModel.ViewModels.ApiEndpoints;
@ -18,26 +14,11 @@ 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 MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE=";
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";
private const string _CBM_URL = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/Windows/5cb97847cee34581afdbc445400e2f77/FortniteContentBuilds";
public EpicApiEndpoint(RestClient client) : base(client) { } public EpicApiEndpoint(RestClient client) : base(client) { }
public async Task<ManifestInfo> GetManifestAsync(CancellationToken token) public async Task<ManifestInfo> GetManifestAsync(CancellationToken token)
{
await VerifyAuth(token).ConfigureAwait(false);
var request = new FRestRequest(_APP_URL);
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
return response.IsSuccessful ? ManifestInfo.Deserialize(response.RawBytes) : null;
}
public ManifestInfo GetManifest(CancellationToken token)
{
return GetManifestAsync(token).GetAwaiter().GetResult();
}
public async Task VerifyAuth(CancellationToken token)
{ {
if (await IsExpired().ConfigureAwait(false)) if (await IsExpired().ConfigureAwait(false))
{ {
@ -47,6 +28,41 @@ public class EpicApiEndpoint : AbstractApiProvider
UserSettings.Default.LastAuthResponse = auth; UserSettings.Default.LastAuthResponse = auth;
} }
} }
var request = new FRestRequest(_APP_URL);
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
return response.IsSuccessful ? new ManifestInfo(response.Content) : null;
}
public async Task<ContentBuildManifestInfo> GetContentBuildManifestAsync(CancellationToken token, string label)
{
if (await IsExpired().ConfigureAwait(false))
{
var auth = await GetAuthAsync(token).ConfigureAwait(false);
if (auth != null)
{
UserSettings.Default.LastAuthResponse = auth;
}
}
var request = new FRestRequest(_CBM_URL);
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
request.AddQueryParameter("label", label);
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
return response.IsSuccessful ? new ContentBuildManifestInfo(response.Content) : null;
}
public ManifestInfo GetManifest(CancellationToken token)
{
return GetManifestAsync(token).GetAwaiter().GetResult();
}
public ContentBuildManifestInfo GetContentBuildManifest(CancellationToken token, string label)
{
return GetContentBuildManifestAsync(token, label).GetAwaiter().GetResult();
} }
private async Task<AuthResponse> GetAuthAsync(CancellationToken token) private async Task<AuthResponse> GetAuthAsync(CancellationToken token)

View File

@ -10,13 +10,13 @@ using FModel.Framework;
using FModel.Services; using FModel.Services;
using FModel.Settings; using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models; using FModel.ViewModels.ApiEndpoints.Models;
using FModel.Views;
using Newtonsoft.Json; using Newtonsoft.Json;
using RestSharp; using RestSharp;
using Serilog; using Serilog;
using MessageBox = AdonisUI.Controls.MessageBox; using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton; using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage; using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
namespace FModel.ViewModels.ApiEndpoints; namespace FModel.ViewModels.ApiEndpoints;
@ -46,6 +46,19 @@ public class FModelApiEndpoint : AbstractApiProvider
return _news ??= GetNewsAsync(token, game).GetAwaiter().GetResult(); return _news ??= GetNewsAsync(token, game).GetAwaiter().GetResult();
} }
public async Task<Info> GetInfosAsync(CancellationToken token, EUpdateMode updateMode)
{
var request = new FRestRequest($"https://api.fmodel.app/v1/infos/{updateMode}");
var response = await _client.ExecuteAsync<Info>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
return response.Data;
}
public Info GetInfos(CancellationToken token, EUpdateMode updateMode)
{
return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult();
}
public async Task<Donator[]> GetDonatorsAsync() public async Task<Donator[]> GetDonatorsAsync()
{ {
var request = new FRestRequest($"https://api.fmodel.app/v1/donations/donators"); var request = new FRestRequest($"https://api.fmodel.app/v1/donations/donators");
@ -103,16 +116,14 @@ public class FModelApiEndpoint : AbstractApiProvider
return communityDesign; return communityDesign;
} }
public void CheckForUpdates(bool launch = false) public void CheckForUpdates(EUpdateMode updateMode, bool launch = false)
{ {
if (DateTime.Now < UserSettings.Default.NextUpdateCheck) return;
if (launch) if (launch)
{ {
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent; AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent; AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
} }
AutoUpdater.Start("https://api.fmodel.app/v1/infos/Qa"); AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}");
} }
private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args) private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
@ -127,7 +138,8 @@ public class FModelApiEndpoint : AbstractApiProvider
DownloadURL = _infos.DownloadUrl, DownloadURL = _infos.DownloadUrl,
Mandatory = new CustomMandatory Mandatory = new CustomMandatory
{ {
CommitHash = _infos.Version.SubstringAfter('+') Value = UserSettings.Default.UpdateMode == EUpdateMode.Qa,
CommitId = _infos.Version.SubstringAfter('+')
} }
}; };
} }
@ -137,21 +149,43 @@ public class FModelApiEndpoint : AbstractApiProvider
{ {
if (args is { CurrentVersion: { } }) if (args is { CurrentVersion: { } })
{ {
UserSettings.Default.LastUpdateCheck = DateTime.Now; var qa = (CustomMandatory) args.Mandatory;
var currentVersion = new System.Version(args.CurrentVersion);
if (((CustomMandatory)args.Mandatory).CommitHash == Constants.APP_COMMIT_ID) if ((qa.Value && qa.CommitId == UserSettings.Default.CommitId) || // qa branch : same commit id
(!qa.Value && currentVersion == args.InstalledVersion && args.CurrentVersion == UserSettings.Default.CommitId)) // stable - beta branch : same version + commit id = version
{ {
if (UserSettings.Default.ShowChangelog) if (UserSettings.Default.ShowChangelog)
ShowChangelog(args); ShowChangelog(args);
return; return;
} }
var currentVersion = new System.Version(args.CurrentVersion); var downgrade = currentVersion < args.InstalledVersion;
UserSettings.Default.ShowChangelog = currentVersion != args.InstalledVersion; var messageBox = new MessageBoxModel
{
Text = $"The latest version of FModel {UserSettings.Default.UpdateMode.GetDescription()} is {(qa.Value ? qa.ShortCommitId : args.CurrentVersion)}. You are using version {(qa.Value ? UserSettings.Default.ShortCommitId : args.InstalledVersion)}. Do you want to {(downgrade ? "downgrade" : "update")} the application now?",
Caption = $"{(downgrade ? "Downgrade" : "Update")} Available",
Icon = MessageBoxImage.Question,
Buttons = MessageBoxButtons.YesNo(),
IsSoundEnabled = false
};
const string message = "A new update is available!"; MessageBox.Show(messageBox);
Helper.OpenWindow<AdonisWindow>(message, () => new UpdateView { Title = message, ResizeMode = ResizeMode.NoResize }.ShowDialog()); if (messageBox.Result != MessageBoxResult.Yes) return;
try
{
if (AutoUpdater.DownloadUpdate(args))
{
UserSettings.Default.ShowChangelog = true;
UserSettings.Default.CommitId = qa.CommitId;
Application.Current.Shutdown();
}
}
catch (Exception exception)
{
UserSettings.Default.ShowChangelog = false;
MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
}
} }
else else
{ {
@ -165,7 +199,7 @@ public class FModelApiEndpoint : AbstractApiProvider
{ {
var request = new FRestRequest(args.ChangelogURL); var request = new FRestRequest(args.ChangelogURL);
var response = _client.Execute(request); var response = _client.Execute(request);
if (!response.IsSuccessful || string.IsNullOrEmpty(response.Content)) return; if (string.IsNullOrEmpty(response.Content)) return;
_applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}"); _applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}");
_applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog"); _applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog");
@ -176,6 +210,6 @@ public class FModelApiEndpoint : AbstractApiProvider
public class CustomMandatory : Mandatory public class CustomMandatory : Mandatory
{ {
public string CommitHash { get; set; } public string CommitId { get; set; }
public string ShortCommitHash => CommitHash[..7]; public string ShortCommitId => CommitId[..7];
} }

View File

@ -1,28 +0,0 @@
using System.Threading.Tasks;
using FModel.Framework;
using FModel.ViewModels.ApiEndpoints.Models;
using RestSharp;
namespace FModel.ViewModels.ApiEndpoints;
public class GitHubApiEndpoint : AbstractApiProvider
{
public GitHubApiEndpoint(RestClient client) : base(client) { }
public async Task<GitHubCommit[]> GetCommitHistoryAsync(string branch = "dev", int page = 1, int limit = 20)
{
var request = new FRestRequest(Constants.GH_COMMITS_HISTORY);
request.AddParameter("sha", branch);
request.AddParameter("page", page);
request.AddParameter("per_page", limit);
var response = await _client.ExecuteAsync<GitHubCommit[]>(request).ConfigureAwait(false);
return response.Data;
}
public async Task<GitHubRelease> GetReleaseAsync(string tag)
{
var request = new FRestRequest($"{Constants.GH_RELEASES}/tags/{tag}");
var response = await _client.ExecuteAsync<GitHubRelease>(request).ConfigureAwait(false);
return response.Data;
}
}

View File

@ -1,125 +0,0 @@
using System;
using System.Windows;
using AdonisUI.Controls;
using AutoUpdaterDotNET;
using FModel.Framework;
using FModel.Settings;
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
using J = Newtonsoft.Json.JsonPropertyAttribute;
namespace FModel.ViewModels.ApiEndpoints.Models;
public class GitHubRelease
{
[J("assets")] public GitHubAsset[] Assets { get; private set; }
}
public class GitHubAsset : ViewModel
{
[J("name")] public string Name { get; private set; }
[J("size")] public int Size { get; private set; }
[J("download_count")] public int DownloadCount { get; private set; }
[J("browser_download_url")] public string BrowserDownloadUrl { get; private set; }
[J("created_at")] public DateTime CreatedAt { get; private set; }
[J("uploader")] public Author Uploader { get; private set; }
private bool _isLatest;
public bool IsLatest
{
get => _isLatest;
set => SetProperty(ref _isLatest, value);
}
}
public class GitHubCommit : ViewModel
{
private string _sha;
[J("sha")]
public string Sha
{
get => _sha;
set
{
SetProperty(ref _sha, value);
RaisePropertyChanged(nameof(IsCurrent));
RaisePropertyChanged(nameof(ShortSha));
}
}
[J("commit")] public Commit Commit { get; set; }
[J("author")] public Author Author { get; set; }
private GitHubAsset _asset;
public GitHubAsset Asset
{
get => _asset;
set
{
SetProperty(ref _asset, value);
RaisePropertyChanged(nameof(IsDownloadable));
}
}
public bool IsCurrent => Sha == Constants.APP_COMMIT_ID;
public string ShortSha => Sha[..7];
public bool IsDownloadable => Asset != null;
public void Download()
{
if (IsCurrent)
{
MessageBox.Show(new MessageBoxModel
{
Text = "You are already on the latest version.",
Caption = "Update FModel",
Icon = MessageBoxImage.Information,
Buttons = [MessageBoxButtons.Ok()],
IsSoundEnabled = false
});
return;
}
var messageBox = new MessageBoxModel
{
Text = $"Are you sure you want to update to version '{ShortSha}'?{(!Asset.IsLatest ? "\nThis is not the latest version." : "")}",
Caption = "Update FModel",
Icon = MessageBoxImage.Question,
Buttons = MessageBoxButtons.YesNo(),
IsSoundEnabled = false
};
MessageBox.Show(messageBox);
if (messageBox.Result != MessageBoxResult.Yes) return;
try
{
if (AutoUpdater.DownloadUpdate(new UpdateInfoEventArgs { DownloadURL = Asset.BrowserDownloadUrl }))
{
Application.Current.Shutdown();
}
}
catch (Exception exception)
{
UserSettings.Default.ShowChangelog = false;
MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
public class Commit
{
[J("author")] public Author Author { get; set; }
[J("message")] public string Message { get; set; }
}
public class Author
{
[J("name")] public string Name { get; set; }
[J("login")] public string Login { get; set; }
[J("date")] public DateTime Date { get; set; }
[J("avatar_url")] public string AvatarUrl { get; set; }
[J("html_url")] public string HtmlUrl { get; set; }
}

View File

@ -1,3 +1,8 @@
using CUE4Parse.UE4.Exceptions;
using CUE4Parse.UE4.Readers;
using FModel.Settings;
using Ionic.Zlib;
using RestSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -8,15 +13,7 @@ using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CUE4Parse.Compression;
using CUE4Parse.UE4.Exceptions;
using CUE4Parse.UE4.Readers;
using FModel.Framework; using FModel.Framework;
using FModel.Settings;
using OffiUtils;
using RestSharp;
namespace FModel.ViewModels.ApiEndpoints; namespace FModel.ViewModels.ApiEndpoints;
@ -43,22 +40,26 @@ public class VManifest
public readonly VChunk[] Chunks; public readonly VChunk[] Chunks;
public readonly VPak[] Paks; public readonly VPak[] Paks;
public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data)) { } public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data))
{
}
private VManifest(FArchive Ar) private VManifest(FArchive Ar)
{ {
using (Ar) using (Ar)
{ {
Header = new VHeader(Ar); Header = new VHeader(Ar);
var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize); var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize);
var uncompressedBuffer = new byte[(int)Header.UncompressedSize]; var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
ZlibHelper.Decompress(compressedBuffer, 0, compressedBuffer.Length, uncompressedBuffer, 0, uncompressedBuffer.Length); if (uncompressedBuffer.Length != Header.UncompressedSize)
throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}");
var manifestAr = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer); using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
Chunks = manifestAr.ReadArray<VChunk>((int) Header.ChunkCount); Chunks = manifest.ReadArray<VChunk>((int) Header.ChunkCount);
Paks = manifestAr.ReadArray((int) Header.PakCount, () => new VPak(manifestAr)); Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest));
if (manifestAr.Position != manifestAr.Length) if (manifest.Position != manifest.Length)
throw new ParserException(manifestAr, $"Parsing failed, {manifestAr.Position} != {manifestAr.Length}"); throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}");
} }
_client = new HttpClient(new HttpClientHandler _client = new HttpClient(new HttpClientHandler
@ -117,7 +118,7 @@ public class VManifest
return chunkBytes; return chunkBytes;
} }
public VPakStream GetPakStream(int index) => new VPakStream(this, index); public Stream GetPakStream(int index) => new VPakStream(this, index);
} }
public readonly struct VHeader public readonly struct VHeader
@ -179,7 +180,7 @@ public readonly struct VChunk
public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}"; public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}";
} }
public class VPakStream : Stream, IRandomAccessStream, ICloneable public class VPakStream : Stream, ICloneable
{ {
private readonly VManifest _manifest; private readonly VManifest _manifest;
private readonly int _pakIndex; private readonly int _pakIndex;
@ -203,22 +204,11 @@ public class VPakStream : Stream, IRandomAccessStream, ICloneable
public object Clone() => new VPakStream(_manifest, _pakIndex, _position); public object Clone() => new VPakStream(_manifest, _pakIndex, _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) =>
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)
{ {
var bytesRead = await ReadAtAsync(_position, buffer, offset, count, cancellationToken); var (i, startPos) = GetChunkIndex(_position);
_position += bytesRead;
return bytesRead;
}
public async Task<int> ReadAtAsync(long position, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var (i, startPos) = GetChunkIndex(position);
if (i == -1) return 0; if (i == -1) return 0;
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false); await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
@ -245,14 +235,10 @@ public class VPakStream : Stream, IRandomAccessStream, ICloneable
if (++i == _chunks.Length) break; if (++i == _chunks.Length) break;
} }
_position += bytesRead;
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>();

View File

@ -1,24 +1,21 @@
using System; using FModel.Extensions;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using CUE4Parse.Compression;
using CUE4Parse.Encryption.Aes;
using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.UE4.VirtualFileSystem;
using FModel.Framework; using FModel.Framework;
using FModel.Services; using FModel.Services;
using FModel.Settings; using FModel.Settings;
using FModel.ViewModels.Commands; using FModel.ViewModels.Commands;
using FModel.Views; using FModel.Views;
using FModel.Views.Resources.Controls; using FModel.Views.Resources.Controls;
using Ionic.Zip;
using Oodle.NET;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using MessageBox = AdonisUI.Controls.MessageBox; using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton; using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage; using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
using OodleCUE4 = CUE4Parse.Compression.Oodle;
namespace FModel.ViewModels; namespace FModel.ViewModels;
@ -49,7 +46,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 {UserSettings.Default.UpdateMode.GetDescription()}";
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})" : "")}";
@ -59,6 +56,7 @@ public class ApplicationViewModel : ViewModel
public SettingsViewModel SettingsView { get; } public SettingsViewModel SettingsView { get; }
public AesManagerViewModel AesManager { get; } public AesManagerViewModel AesManager { get; }
public AudioPlayerViewModel AudioPlayer { get; } public AudioPlayerViewModel AudioPlayer { get; }
private OodleCompressor _oodle;
public ApplicationViewModel() public ApplicationViewModel()
{ {
@ -81,24 +79,6 @@ public class ApplicationViewModel : ViewModel
} }
CUE4Parse = new CUE4ParseViewModel(); CUE4Parse = new CUE4ParseViewModel();
CUE4Parse.Provider.VfsRegistered += (sender, count) =>
{
if (sender is not IAesVfsReader reader) return;
Status.UpdateStatusLabel($"{count} Archives ({reader.Name})", "Registered");
CUE4Parse.GameDirectory.Add(reader);
};
CUE4Parse.Provider.VfsMounted += (sender, count) =>
{
if (sender is not IAesVfsReader reader) return;
Status.UpdateStatusLabel($"{count:N0} Packages ({reader.Name})", "Mounted");
CUE4Parse.GameDirectory.Verify(reader);
};
CUE4Parse.Provider.VfsUnmounted += (sender, _) =>
{
if (sender is not IAesVfsReader reader) return;
CUE4Parse.GameDirectory.Disable(reader);
};
CustomDirectories = new CustomDirectoriesViewModel(); CustomDirectories = new CustomDirectoriesViewModel();
SettingsView = new SettingsViewModel(); SettingsView = new SettingsViewModel();
AesManager = new AesManagerViewModel(CUE4Parse); AesManager = new AesManagerViewModel(CUE4Parse);
@ -143,7 +123,7 @@ public class ApplicationViewModel : ViewModel
StartInfo = new ProcessStartInfo StartInfo = new ProcessStartInfo
{ {
FileName = "dotnet", FileName = "dotnet",
Arguments = $"\"{path}\"", Arguments = $"\"{Path.GetFullPath(Environment.GetCommandLineArgs()[0])}\"",
UseShellExecute = false, UseShellExecute = false,
RedirectStandardOutput = false, RedirectStandardOutput = false,
RedirectStandardError = false, RedirectStandardError = false,
@ -176,23 +156,14 @@ public class ApplicationViewModel : ViewModel
CUE4Parse.ClearProvider(); CUE4Parse.ClearProvider();
await ApplicationService.ThreadWorkerView.Begin(cancellationToken => await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
{ {
// TODO: refactor after release, select updated keys only CUE4Parse.LoadVfs(cancellationToken, AesManager.AesKeys);
var aes = AesManager.AesKeys.Select(x => CUE4Parse.Provider.LoadIniConfigs();
{
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
var k = x.Key.Trim();
if (k.Length != 66) k = Constants.ZERO_64_CHAR;
return new KeyValuePair<FGuid, FAesKey>(x.Guid, new FAesKey(k));
});
CUE4Parse.LoadVfs(aes);
AesManager.SetAesKeys(); AesManager.SetAesKeys();
}); });
RaisePropertyChanged(nameof(GameDisplayName)); RaisePropertyChanged(nameof(GameDisplayName));
} }
public static async Task InitVgmStream() public 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; if (File.Exists(vgmZipFilePath)) return;
@ -200,17 +171,9 @@ public class ApplicationViewModel : ViewModel
await ApplicationService.ApiEndpointView.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath); await ApplicationService.ApiEndpointView.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
if (new FileInfo(vgmZipFilePath).Length > 0) if (new FileInfo(vgmZipFilePath).Length > 0)
{ {
var zipDir = Path.GetDirectoryName(vgmZipFilePath)!; var zip = ZipFile.Read(vgmZipFilePath);
await using var zipFs = File.OpenRead(vgmZipFilePath); var zipDir = vgmZipFilePath.SubstringBeforeLast("\\");
using var zip = new ZipArchive(zipFs, ZipArchiveMode.Read); foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently);
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 else
{ {
@ -218,44 +181,43 @@ public class ApplicationViewModel : ViewModel
} }
} }
public static async Task InitImGuiSettings(bool forceDownload) public async Task InitOodle()
{ {
var imgui = "imgui.ini"; var dataDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
var imguiPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", imgui); var oodlePath = Path.Combine(dataDir.FullName, OodleCUE4.OODLE_DLL_NAME);
if (File.Exists(imgui)) File.Move(imgui, imguiPath, true); if (File.Exists(OodleCUE4.OODLE_DLL_NAME))
if (File.Exists(imguiPath) && !forceDownload) return; {
File.Move(OodleCUE4.OODLE_DLL_NAME, oodlePath, true);
}
else if (!File.Exists(oodlePath))
{
var result = await OodleCUE4.DownloadOodleDll(oodlePath);
if (!result) return;
}
await ApplicationService.ApiEndpointView.DownloadFileAsync($"https://cdn.fmodel.app/d/configurations/{imgui}", imguiPath); if (File.Exists("oo2core_8_win64.dll"))
if (new FileInfo(imguiPath).Length == 0) File.Delete("oo2core_8_win64.dll");
_oodle = new OodleCompressor(oodlePath);
unsafe
{
OodleCUE4.DecompressFunc = (bufferPtr, bufferSize, outputPtr, outputSize, a, b, c, d, e, f, g, h, i, threadModule) =>
_oodle.Decompress(new IntPtr(bufferPtr), bufferSize, new IntPtr(outputPtr), outputSize,
(OodleLZ_FuzzSafe) a, (OodleLZ_CheckCRC) b, (OodleLZ_Verbosity) c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase) threadModule);
}
}
public async Task InitImGuiSettings(bool forceDownload)
{
var imgui = Path.Combine(/*UserSettings.Default.OutputDirectory, ".data", */"imgui.ini");
if (File.Exists(imgui) && !forceDownload) return;
await ApplicationService.ApiEndpointView.DownloadFileAsync("https://cdn.fmodel.app/d/configurations/imgui.ini", imgui);
if (new FileInfo(imgui).Length == 0)
{ {
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download ImGui settings", Constants.WHITE, true)); FLogger.Append(ELog.Error, () => FLogger.Text("Could not download ImGui settings", Constants.WHITE, true));
} }
} }
public static async ValueTask InitOodle()
{
var oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME);
if (File.Exists(OodleHelper.OODLE_DLL_NAME))
{
File.Move(OodleHelper.OODLE_DLL_NAME, oodlePath, true);
}
else if (!File.Exists(oodlePath))
{
await OodleHelper.DownloadOodleDllAsync(oodlePath);
}
OodleHelper.Initialize(oodlePath);
}
public static async ValueTask InitZlib()
{
var zlibPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", ZlibHelper.DLL_NAME);
if (!File.Exists(zlibPath))
{
await ZlibHelper.DownloadDllAsync(zlibPath);
}
ZlibHelper.Initialize(zlibPath);
}
} }

View File

@ -552,26 +552,14 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
case "opus": case "opus":
case "wem": case "wem":
case "at9": case "at9":
case "raw":
{ {
if (TryConvert(out var wavFilePath)) if (TryConvert(out var wavFilePath) && !string.IsNullOrEmpty(wavFilePath))
{ {
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath)); var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath));
Replace(newAudio); Replace(newAudio);
return true; return true;
} }
return false;
}
case "binka":
{
if (TryDecode(out var rawFilePath))
{
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(rawFilePath));
Replace(newAudio);
return true;
}
return false; return false;
} }
} }
@ -579,8 +567,7 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
return true; return true;
} }
private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath); private bool TryConvert(out string wavFilePath)
private bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath)
{ {
wavFilePath = string.Empty; wavFilePath = string.Empty;
var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe"); var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe");
@ -590,46 +577,22 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
if (!File.Exists(vgmFilePath)) return false; if (!File.Exists(vgmFilePath)) return false;
} }
Directory.CreateDirectory(inputFilePath.SubstringBeforeLast("/"));
File.WriteAllBytes(inputFilePath, inputFileData);
wavFilePath = Path.ChangeExtension(inputFilePath, ".wav");
var vgmProcess = Process.Start(new ProcessStartInfo
{
FileName = vgmFilePath,
Arguments = $"-o \"{wavFilePath}\" \"{inputFilePath}\"",
UseShellExecute = false,
CreateNoWindow = true
});
vgmProcess?.WaitForExit(5000);
File.Delete(inputFilePath);
return vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath);
}
private bool TryDecode(out string rawFilePath)
{
rawFilePath = string.Empty;
var binkadecPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "binkadec.exe");
if (!File.Exists(binkadecPath))
{
return false;
}
Directory.CreateDirectory(SelectedAudioFile.FilePath.SubstringBeforeLast("/")); Directory.CreateDirectory(SelectedAudioFile.FilePath.SubstringBeforeLast("/"));
File.WriteAllBytes(SelectedAudioFile.FilePath, SelectedAudioFile.Data); File.WriteAllBytes(SelectedAudioFile.FilePath, SelectedAudioFile.Data);
rawFilePath = Path.ChangeExtension(SelectedAudioFile.FilePath, ".wav"); wavFilePath = Path.ChangeExtension(SelectedAudioFile.FilePath, ".wav");
var binkadecProcess = Process.Start(new ProcessStartInfo var vgmProcess = Process.Start(new ProcessStartInfo
{ {
FileName = binkadecPath, FileName = vgmFilePath,
Arguments = $"-i \"{SelectedAudioFile.FilePath}\" -o \"{rawFilePath}\"", Arguments = $"-o \"{wavFilePath}\" \"{SelectedAudioFile.FilePath}\"",
UseShellExecute = false, UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true CreateNoWindow = true
}); });
binkadecProcess?.WaitForExit(5000); vgmProcess?.WaitForExit();
File.Delete(SelectedAudioFile.FilePath); File.Delete(SelectedAudioFile.FilePath);
return binkadecProcess?.ExitCode == 0 && File.Exists(rawFilePath); return vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath);
} }
} }

View File

@ -6,7 +6,6 @@ using System.Linq;
using System.Threading.Tasks; 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.VirtualFileSystem; using CUE4Parse.UE4.VirtualFileSystem;
using FModel.Framework; using FModel.Framework;
using FModel.Services; using FModel.Services;
@ -21,8 +20,6 @@ namespace FModel.ViewModels;
public class BackupManagerViewModel : ViewModel public class BackupManagerViewModel : ViewModel
{ {
public const uint FBKP_MAGIC = 0x504B4246;
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
@ -67,21 +64,23 @@ 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"));
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);
using var writer = new BinaryWriter(compressedStream); using var writer = new BinaryWriter(compressedStream);
writer.Write(FBKP_MAGIC);
writer.Write((byte) EBackupVersion.Latest);
writer.Write(_applicationView.CUE4Parse.Provider.Files.Values.Count(func));
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values) foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
{ {
if (!func(asset)) continue; if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") ||
writer.Write(asset.Size); entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
writer.Write(asset.IsEncrypted); continue;
writer.Write($"/{asset.Path.ToLower()}");
writer.Write((long) 0);
writer.Write((long) 0);
writer.Write(entry.Size);
writer.Write(entry.IsEncrypted);
writer.Write(0);
writer.Write($"/{entry.Path.ToLower()}");
writer.Write(0);
} }
SaveCheck(fullPath, fileName, "created", "create"); SaveCheck(fullPath, fileName, "created", "create");
@ -117,12 +116,3 @@ public class BackupManagerViewModel : ViewModel
} }
} }
} }
public enum EBackupVersion : byte
{
BeforeVersionWasAdded = 0,
Initial,
LatestPlusOne,
Latest = LatestPlusOne - 1
}

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,7 @@ 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.GameDirectory.HasNoFile) return;
if (_applicationView.CUE4Parse.Provider.Keys.Count == 0 && _applicationView.CUE4Parse.Provider.RequiredKeys.Count > 0) if (_applicationView.CUE4Parse.Provider.Files.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));
@ -61,6 +61,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
// filter what to show // filter what to show
switch (UserSettings.Default.LoadingMode) switch (UserSettings.Default.LoadingMode)
{ {
case ELoadingMode.Single:
case ELoadingMode.Multiple: case ELoadingMode.Multiple:
{ {
var l = (IList) parameter; var l = (IList) parameter;
@ -154,16 +155,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
FLogger.Append(ELog.Information, () => FLogger.Append(ELog.Information, () =>
FLogger.Text($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true)); FLogger.Text($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true));
var mode = UserSettings.Default.LoadingMode; using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
var entries = ParseBackup(openFileDialog.FileName, mode, cancellationToken);
_applicationView.Status.UpdateStatusLabel($"{mode.ToString()[6..]} Folders & Packages");
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
private List<VfsEntry> ParseBackup(string path, ELoadingMode mode, CancellationToken cancellationToken = default)
{
using var fileStream = new FileStream(path, FileMode.Open);
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
if (fileStream.ReadUInt32() == _IS_LZ4) if (fileStream.ReadUInt32() == _IS_LZ4)
@ -178,41 +170,25 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
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<VfsEntry>();
var mode = UserSettings.Default.LoadingMode;
switch (mode) switch (mode)
{ {
case ELoadingMode.AllButNew: case ELoadingMode.AllButNew:
{ {
var paths = new HashSet<string>(); var paths = new Dictionary<string, int>();
var magic = archive.Read<uint>(); while (archive.Position < archive.Length)
if (magic != BackupManagerViewModel.FBKP_MAGIC)
{ {
archive.Position -= sizeof(uint); cancellationToken.ThrowIfCancellationRequested();
while (archive.Position < archive.Length)
{
cancellationToken.ThrowIfCancellationRequested();
archive.Position += 29; archive.Position += 29;
paths.Add(archive.ReadString().ToLower()[1..]); paths[archive.ReadString().ToLower()[1..]] = 0;
archive.Position += 4; archive.Position += 4;
}
}
else
{
var version = archive.Read<EBackupVersion>();
var count = archive.Read<int>();
for (var i = 0; i < count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
archive.Position += sizeof(long) + sizeof(byte);
paths.Add(archive.ReadString().ToLower()[1..]);
}
} }
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files) foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
if (value is not VfsEntry entry || paths.Contains(key) || entry.Path.EndsWith(".uexp") || if (value is not VfsEntry entry || paths.ContainsKey(key) || entry.Path.EndsWith(".uexp") ||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue; entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
entries.Add(entry); entries.Add(entry);
@ -223,54 +199,31 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
} }
case ELoadingMode.AllButModified: case ELoadingMode.AllButModified:
{ {
var magic = archive.Read<uint>(); while (archive.Position < archive.Length)
if (magic != BackupManagerViewModel.FBKP_MAGIC)
{ {
archive.Position -= sizeof(uint); cancellationToken.ThrowIfCancellationRequested();
while (archive.Position < archive.Length)
{
cancellationToken.ThrowIfCancellationRequested();
archive.Position += 16; archive.Position += 16;
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().ToLower()[1..];
archive.Position += 4; archive.Position += 4;
AddEntry(fullPath, uncompressedSize, isEncrypted, entries); if (fullPath.EndsWith(".uexp") || fullPath.EndsWith(".ubulk") || fullPath.EndsWith(".uptnl") ||
} !_applicationView.CUE4Parse.Provider.Files.TryGetValue(fullPath, out var asset) || asset is not VfsEntry entry ||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
continue;
entries.Add(entry);
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
} }
else
{
var version = archive.Read<EBackupVersion>();
var count = archive.Read<int>();
for (var i = 0; i < count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var uncompressedSize = archive.Read<long>();
var isEncrypted = archive.ReadFlag();
var fullPath = archive.ReadString().ToLower()[1..];
AddEntry(fullPath, uncompressedSize, isEncrypted, entries);
}
}
break; break;
} }
} }
return entries; _applicationView.Status.UpdateStatusLabel($"{mode.ToString()[6..]} Folders & Packages");
} _applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
private void AddEntry(string path, long uncompressedSize, bool isEncrypted, List<VfsEntry> entries)
{
if (path.EndsWith(".uexp") || path.EndsWith(".ubulk") || path.EndsWith(".uptnl") ||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(path, out var asset) || asset is not VfsEntry entry ||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
return;
entries.Add(entry);
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
} }
} }

View File

@ -54,8 +54,9 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
case "Help_Donate": case "Help_Donate":
Process.Start(new ProcessStartInfo { FileName = Constants.DONATE_LINK, UseShellExecute = true }); Process.Start(new ProcessStartInfo { FileName = Constants.DONATE_LINK, UseShellExecute = true });
break; break;
case "Help_Releases": case "Help_Changelog":
Helper.OpenWindow<AdonisWindow>("Releases", () => new UpdateView().Show()); UserSettings.Default.ShowChangelog = true;
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
break; break;
case "Help_BugsReport": case "Help_BugsReport":
Process.Start(new ProcessStartInfo { FileName = Constants.ISSUE_LINK, UseShellExecute = true }); Process.Start(new ProcessStartInfo { FileName = Constants.ISSUE_LINK, UseShellExecute = true });

View File

@ -1,40 +0,0 @@
using System;
using FModel.Framework;
using FModel.Settings;
namespace FModel.ViewModels.Commands;
public class RemindMeCommand : ViewModelCommand<UpdateViewModel>
{
public RemindMeCommand(UpdateViewModel contextViewModel) : base(contextViewModel)
{
}
public override void Execute(UpdateViewModel contextViewModel, object parameter)
{
switch (parameter)
{
case "Days":
// check for update in 3 days
UserSettings.Default.NextUpdateCheck = DateTime.Now.AddDays(3);
break;
case "Week":
// check for update next week (a week starts on Monday)
var delay = (DayOfWeek.Monday - DateTime.Now.DayOfWeek + 7) % 7;
UserSettings.Default.NextUpdateCheck = DateTime.Now.AddDays(delay == 0 ? 7 : delay);
break;
case "Month":
// check for update next month (if today is 31st, it will be 1st of next month)
UserSettings.Default.NextUpdateCheck = DateTime.Now.AddDays(1 - DateTime.Now.Day).AddMonths(1);
break;
case "Never":
// never check for updates
UserSettings.Default.NextUpdateCheck = DateTime.MaxValue;
break;
default:
// reset
UserSettings.Default.NextUpdateCheck = DateTime.Now;
break;
}
}
}

View File

@ -22,7 +22,6 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray(); var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
if (!assetItems.Any()) return; if (!assetItems.Any()) return;
var updateUi = assetItems.Length > 1 ? EBulkType.Auto : EBulkType.None;
await _threadWorkerView.Begin(cancellationToken => await _threadWorkerView.Begin(cancellationToken =>
{ {
switch (trigger) switch (trigger)
@ -48,7 +47,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
{ {
Thread.Yield(); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties | updateUi); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties);
} }
break; break;
case "Assets_Save_Textures": case "Assets_Save_Textures":
@ -56,7 +55,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
{ {
Thread.Yield(); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures | updateUi); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures);
} }
break; break;
case "Assets_Save_Models": case "Assets_Save_Models":
@ -64,7 +63,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
{ {
Thread.Yield(); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | updateUi); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | EBulkType.Auto);
} }
break; break;
case "Assets_Save_Animations": case "Assets_Save_Animations":
@ -72,7 +71,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
{ {
Thread.Yield(); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | updateUi); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | EBulkType.Auto);
} }
break; break;
} }

View File

@ -1,11 +1,8 @@
using FModel.Framework; using FModel.Framework;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows; using System.Windows;
using System.Windows.Data; using System.Windows.Data;
using CUE4Parse.UE4.IO;
using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.UE4.VirtualFileSystem; using CUE4Parse.UE4.VirtualFileSystem;
@ -75,17 +72,6 @@ public class FileItem : ViewModel
Length = length; Length = length;
} }
public FileItem(IAesVfsReader reader)
{
Name = reader.Name;
Length = reader.Length;
Guid = reader.EncryptionKeyGuid;
IsEncrypted = reader.IsEncrypted;
IsEnabled = false;
Key = string.Empty;
FileCount = reader is IoStoreReader storeReader ? (int) storeReader.TocResource.Header.TocEntryCount - 1 : 0;
}
public override string ToString() public override string ToString()
{ {
return $"{Name} | {Key}"; return $"{Name} | {Key}";
@ -98,35 +84,31 @@ public class GameDirectoryViewModel : ViewModel
public readonly ObservableCollection<FileItem> DirectoryFiles; public readonly ObservableCollection<FileItem> DirectoryFiles;
public ICollectionView DirectoryFilesView { get; } public ICollectionView DirectoryFilesView { get; }
private readonly Regex _hiddenArchives = new(@"^(?!global|pakchunk.+(optional|ondemand)\-).+(pak|utoc)$", // should be universal
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
public GameDirectoryViewModel() public GameDirectoryViewModel()
{ {
DirectoryFiles = new ObservableCollection<FileItem>(); DirectoryFiles = new ObservableCollection<FileItem>();
DirectoryFilesView = new ListCollectionView(DirectoryFiles) { SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending) } }; DirectoryFilesView = new ListCollectionView(DirectoryFiles) { SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending) } };
} }
public void DeactivateAll()
{
foreach (var file in DirectoryFiles)
{
file.IsEnabled = false;
}
}
public void Add(IAesVfsReader reader) public void Add(IAesVfsReader reader)
{ {
if (!_hiddenArchives.IsMatch(reader.Name)) return; Application.Current.Dispatcher.Invoke(() =>
{
var fileItem = new FileItem(reader); DirectoryFiles.Add(new FileItem(reader.Name, reader.Length)
Application.Current.Dispatcher.Invoke(() => DirectoryFiles.Add(fileItem)); {
} Guid = reader.EncryptionKeyGuid,
IsEncrypted = reader.IsEncrypted,
public void Verify(IAesVfsReader reader) IsEnabled = false,
{ Key = string.Empty
if (DirectoryFiles.FirstOrDefault(x => x.Name == reader.Name) is not { } file) return; });
});
file.IsEnabled = true;
file.MountPoint = reader.MountPoint;
file.FileCount = reader.FileCount;
}
public void Disable(IAesVfsReader reader)
{
if (DirectoryFiles.FirstOrDefault(x => x.Name == reader.Name) is not { } file) return;
file.IsEnabled = false;
} }
} }

View File

@ -42,7 +42,7 @@ public class GameSelectorViewModel : ViewModel
private readonly ObservableCollection<DirectorySettings> _detectedDirectories; private readonly ObservableCollection<DirectorySettings> _detectedDirectories;
public ReadOnlyObservableCollection<DirectorySettings> DetectedDirectories { get; } public ReadOnlyObservableCollection<DirectorySettings> DetectedDirectories { get; }
public ReadOnlyObservableCollection<EGame> UeGames { get; } public ReadOnlyObservableCollection<EGame> UeVersions { get; }
public GameSelectorViewModel(string gameDirectory) public GameSelectorViewModel(string gameDirectory)
{ {
@ -61,7 +61,7 @@ public class GameSelectorViewModel : ViewModel
else else
SelectedDirectory = DetectedDirectories.FirstOrDefault(); SelectedDirectory = DetectedDirectories.FirstOrDefault();
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames())); UeVersions = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
} }
public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory); public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory);
@ -87,8 +87,8 @@ public class GameSelectorViewModel : ViewModel
.OrderBy(value => (int)value == ((int)value & ~0xF)); .OrderBy(value => (int)value == ((int)value & ~0xF));
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_4);
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_4);
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);

View File

@ -7,7 +7,6 @@ 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.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.Material;
using FModel.Framework; using FModel.Framework;
using FModel.Services; using FModel.Services;
@ -26,6 +25,13 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _useCustomOutputFolders, value); set => SetProperty(ref _useCustomOutputFolders, value);
} }
private EUpdateMode _selectedUpdateMode;
public EUpdateMode SelectedUpdateMode
{
get => _selectedUpdateMode;
set => SetProperty(ref _selectedUpdateMode, value);
}
private ETexturePlatform _selectedUePlatform; private ETexturePlatform _selectedUePlatform;
public ETexturePlatform SelectedUePlatform public ETexturePlatform SelectedUePlatform
{ {
@ -114,12 +120,7 @@ public class SettingsViewModel : ViewModel
public EMeshFormat SelectedMeshExportFormat public EMeshFormat SelectedMeshExportFormat
{ {
get => _selectedMeshExportFormat; get => _selectedMeshExportFormat;
set set => SetProperty(ref _selectedMeshExportFormat, value);
{
SetProperty(ref _selectedMeshExportFormat, value);
RaisePropertyChanged(nameof(SocketSettingsEnabled));
RaisePropertyChanged(nameof(CompressionSettingsEnabled));
}
} }
private ESocketFormat _selectedSocketExportFormat; private ESocketFormat _selectedSocketExportFormat;
@ -129,13 +130,6 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _selectedSocketExportFormat, value); set => SetProperty(ref _selectedSocketExportFormat, value);
} }
private EFileCompressionFormat _selectedCompressionFormat;
public EFileCompressionFormat SelectedCompressionFormat
{
get => _selectedCompressionFormat;
set => SetProperty(ref _selectedCompressionFormat, value);
}
private ELodFormat _selectedLodExportFormat; private ELodFormat _selectedLodExportFormat;
public ELodFormat SelectedLodExportFormat public ELodFormat SelectedLodExportFormat
{ {
@ -157,9 +151,7 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _selectedTextureExportFormat, value); set => SetProperty(ref _selectedTextureExportFormat, value);
} }
public bool SocketSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.ActorX; public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; }
public bool CompressionSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.UEFormat;
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; } public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; } public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; } public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
@ -168,7 +160,6 @@ public class SettingsViewModel : ViewModel
public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; } public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; }
public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { get; private set; } public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { get; private set; }
public ReadOnlyObservableCollection<ESocketFormat> SocketExportFormats { get; private set; } public ReadOnlyObservableCollection<ESocketFormat> SocketExportFormats { 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<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; }
@ -181,6 +172,7 @@ public class SettingsViewModel : ViewModel
private string _audioSnapshot; private string _audioSnapshot;
private string _modelSnapshot; private string _modelSnapshot;
private string _gameSnapshot; private string _gameSnapshot;
private EUpdateMode _updateModeSnapshot;
private ETexturePlatform _uePlatformSnapshot; private ETexturePlatform _uePlatformSnapshot;
private EGame _ueGameSnapshot; private EGame _ueGameSnapshot;
private IList<FCustomVersion> _customVersionsSnapshot; private IList<FCustomVersion> _customVersionsSnapshot;
@ -191,7 +183,6 @@ public class SettingsViewModel : ViewModel
private EIconStyle _cosmeticStyleSnapshot; private EIconStyle _cosmeticStyleSnapshot;
private EMeshFormat _meshExportFormatSnapshot; private EMeshFormat _meshExportFormatSnapshot;
private ESocketFormat _socketExportFormatSnapshot; private ESocketFormat _socketExportFormatSnapshot;
private EFileCompressionFormat _compressionFormatSnapshot;
private ELodFormat _lodExportFormatSnapshot; private ELodFormat _lodExportFormatSnapshot;
private EMaterialFormat _materialExportFormatSnapshot; private EMaterialFormat _materialExportFormatSnapshot;
private ETextureFormat _textureExportFormatSnapshot; private ETextureFormat _textureExportFormatSnapshot;
@ -212,6 +203,7 @@ public class SettingsViewModel : ViewModel
_audioSnapshot = UserSettings.Default.AudioDirectory; _audioSnapshot = UserSettings.Default.AudioDirectory;
_modelSnapshot = UserSettings.Default.ModelDirectory; _modelSnapshot = UserSettings.Default.ModelDirectory;
_gameSnapshot = UserSettings.Default.GameDirectory; _gameSnapshot = UserSettings.Default.GameDirectory;
_updateModeSnapshot = UserSettings.Default.UpdateMode;
_uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform; _uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform;
_ueGameSnapshot = UserSettings.Default.CurrentDir.UeVersion; _ueGameSnapshot = UserSettings.Default.CurrentDir.UeVersion;
_customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions; _customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions;
@ -231,11 +223,11 @@ public class SettingsViewModel : ViewModel
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle; _cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat; _meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
_socketExportFormatSnapshot = UserSettings.Default.SocketExportFormat; _socketExportFormatSnapshot = UserSettings.Default.SocketExportFormat;
_compressionFormatSnapshot = UserSettings.Default.CompressionFormat;
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat; _lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
_materialExportFormatSnapshot = UserSettings.Default.MaterialExportFormat; _materialExportFormatSnapshot = UserSettings.Default.MaterialExportFormat;
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat; _textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
SelectedUpdateMode = _updateModeSnapshot;
SelectedUePlatform = _uePlatformSnapshot; SelectedUePlatform = _uePlatformSnapshot;
SelectedUeGame = _ueGameSnapshot; SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot; SelectedCustomVersions = _customVersionsSnapshot;
@ -246,13 +238,13 @@ public class SettingsViewModel : ViewModel
SelectedCosmeticStyle = _cosmeticStyleSnapshot; SelectedCosmeticStyle = _cosmeticStyleSnapshot;
SelectedMeshExportFormat = _meshExportFormatSnapshot; SelectedMeshExportFormat = _meshExportFormatSnapshot;
SelectedSocketExportFormat = _socketExportFormatSnapshot; SelectedSocketExportFormat = _socketExportFormatSnapshot;
SelectedCompressionFormat = _selectedCompressionFormat;
SelectedLodExportFormat = _lodExportFormatSnapshot; SelectedLodExportFormat = _lodExportFormatSnapshot;
SelectedMaterialExportFormat = _materialExportFormatSnapshot; SelectedMaterialExportFormat = _materialExportFormatSnapshot;
SelectedTextureExportFormat = _textureExportFormatSnapshot; SelectedTextureExportFormat = _textureExportFormatSnapshot;
SelectedAesReload = UserSettings.Default.AesReload; SelectedAesReload = UserSettings.Default.AesReload;
SelectedDiscordRpc = UserSettings.Default.DiscordRpc; SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames())); UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages())); AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads())); AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
@ -261,7 +253,6 @@ public class SettingsViewModel : ViewModel
CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles())); CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles()));
MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat())); MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat()));
SocketExportFormats = new ReadOnlyObservableCollection<ESocketFormat>(new ObservableCollection<ESocketFormat>(EnumerateSocketExportFormat())); SocketExportFormats = new ReadOnlyObservableCollection<ESocketFormat>(new ObservableCollection<ESocketFormat>(EnumerateSocketExportFormat()));
CompressionFormats = new ReadOnlyObservableCollection<EFileCompressionFormat>(new ObservableCollection<EFileCompressionFormat>(EnumerateCompressionFormat()));
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat())); LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
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()));
@ -277,6 +268,8 @@ public class SettingsViewModel : ViewModel
whatShouldIDo.Add(SettingsOut.ReloadLocres); whatShouldIDo.Add(SettingsOut.ReloadLocres);
if (_mappingsUpdate) if (_mappingsUpdate)
whatShouldIDo.Add(SettingsOut.ReloadMappings); whatShouldIDo.Add(SettingsOut.ReloadMappings);
if (_updateModeSnapshot != SelectedUpdateMode)
whatShouldIDo.Add(SettingsOut.CheckForUpdates);
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions || if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox _uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
@ -290,6 +283,7 @@ public class SettingsViewModel : ViewModel
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox _gameSnapshot != UserSettings.Default.GameDirectory) // textbox
restart = true; restart = true;
UserSettings.Default.UpdateMode = SelectedUpdateMode;
UserSettings.Default.CurrentDir.UeVersion = SelectedUeGame; UserSettings.Default.CurrentDir.UeVersion = SelectedUeGame;
UserSettings.Default.CurrentDir.TexturePlatform = SelectedUePlatform; UserSettings.Default.CurrentDir.TexturePlatform = SelectedUePlatform;
UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions; UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions;
@ -301,7 +295,6 @@ public class SettingsViewModel : ViewModel
UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle; UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle;
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat; UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
UserSettings.Default.SocketExportFormat = SelectedSocketExportFormat; UserSettings.Default.SocketExportFormat = SelectedSocketExportFormat;
UserSettings.Default.CompressionFormat = SelectedCompressionFormat;
UserSettings.Default.LodExportFormat = SelectedLodExportFormat; UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
UserSettings.Default.MaterialExportFormat = SelectedMaterialExportFormat; UserSettings.Default.MaterialExportFormat = SelectedMaterialExportFormat;
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat; UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
@ -314,6 +307,7 @@ public class SettingsViewModel : ViewModel
return restart; return restart;
} }
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
private IEnumerable<EGame> EnumerateUeGames() private IEnumerable<EGame> EnumerateUeGames()
=> Enum.GetValues<EGame>() => Enum.GetValues<EGame>()
.GroupBy(value => (int)value) .GroupBy(value => (int)value)
@ -326,7 +320,6 @@ public class SettingsViewModel : ViewModel
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues<EIconStyle>(); private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues<EIconStyle>();
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues<EMeshFormat>(); private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues<EMeshFormat>();
private IEnumerable<ESocketFormat> EnumerateSocketExportFormat() => Enum.GetValues<ESocketFormat>(); private IEnumerable<ESocketFormat> EnumerateSocketExportFormat() => Enum.GetValues<ESocketFormat>();
private IEnumerable<EFileCompressionFormat> EnumerateCompressionFormat() => Enum.GetValues<EFileCompressionFormat>();
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>(); private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
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>();

View File

@ -68,13 +68,9 @@ public class TabImage : ViewModel
Image = null; Image = null;
return; return;
} }
_bmp = bitmap; _bmp = bitmap;
using var data = _bmp.Encode(NoAlpha ? ETextureFormat.Jpeg : UserSettings.Default.TextureExportFormat, 100); 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)
return;
var image = new BitmapImage(); var image = new BitmapImage();
image.BeginInit(); image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad; image.CacheOption = BitmapCacheOption.OnLoad;
@ -90,8 +86,6 @@ public class TabImage : ViewModel
public class TabItem : ViewModel public class TabItem : ViewModel
{ {
public string ParentExportType { get; private set; }
private string _header; private string _header;
public string Header public string Header
{ {
@ -217,58 +211,29 @@ 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(string header, string directory)
{ {
Header = header; Header = header;
Directory = directory; Directory = directory;
ParentExportType = parentExportType;
_images = new ObservableCollection<TabImage>(); _images = new ObservableCollection<TabImage>();
} }
public void SoftReset(string header, string directory) public void ClearImages()
{ {
Header = header;
Directory = directory;
ParentExportType = string.Empty;
ScrollTrigger = null;
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>
{ {
_images.Clear(); _images.Clear();
SelectedImage = null; SelectedImage = null;
RaisePropertyChanged("HasMultipleImages"); RaisePropertyChanged("HasMultipleImages");
Document ??= new TextDocument();
Document.Text = string.Empty;
}); });
} }
public void AddImage(UTexture texture, bool save, bool updateUi) public void AddImage(UTexture texture, bool save, bool updateUi)
{ => AddImage(texture.Name, texture.RenderNearestNeighbor, texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform), save, updateUi);
var appendLayerNumber = false;
var img = new SKBitmap[1];
if (texture is UTexture2DArray textureArray)
{
img = textureArray.DecodeTextureArray(UserSettings.Default.CurrentDir.TexturePlatform);
appendLayerNumber = true;
}
else
{
img[0] = texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform);
if (texture is UTextureCube)
{
img[0] = img[0]?.ToPanorama();
}
}
AddImage(texture.Name, texture.RenderNearestNeighbor, img, save, updateUi, appendLayerNumber); 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, bool appendLayerNumber = false)
{ {
for (var i = 0; i < img.Length; i++) foreach (var i in img) AddImage(name, rnn, i, save, updateUi);
{
AddImage($"{name}{(appendLayerNumber ? $"_{i}" : "")}", rnn, img[i], save, updateUi);
}
} }
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)
@ -301,20 +266,20 @@ public class TabItem : ViewModel
}); });
} }
public void ResetDocumentText()
{
Application.Current.Dispatcher.Invoke(() =>
{
Document ??= new TextDocument();
Document.Text = string.Empty;
});
}
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 fileName = $"{image.ExportName}.png";
var ext = UserSettings.Default.TextureExportFormat switch
{
ETextureFormat.Png => ".png",
ETextureFormat.Jpeg => ".jpg",
ETextureFormat.Tga => ".tga",
_ => ".png"
};
var fileName = image.ExportName + ext;
var path = Path.Combine(UserSettings.Default.TextureDirectory, var path = Path.Combine(UserSettings.Default.TextureDirectory,
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/'); UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
@ -395,13 +360,12 @@ public class TabControlViewModel : ViewModel
SelectedTab = TabsItems.FirstOrDefault(); SelectedTab = TabsItems.FirstOrDefault();
} }
public void AddTab(string header = null, string directory = null, string parentExportType = null) public void AddTab(string header = null, string directory = null)
{ {
if (!CanAddTabs) return; if (!CanAddTabs) return;
var h = header ?? "New Tab"; var h = header ?? "New Tab";
var d = directory ?? string.Empty; var d = directory ?? string.Empty;
var p = parentExportType ?? string.Empty;
if (SelectedTab is { Header : "New Tab" }) if (SelectedTab is { Header : "New Tab" })
{ {
SelectedTab.Header = h; SelectedTab.Header = h;
@ -411,7 +375,7 @@ public class TabControlViewModel : ViewModel
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>
{ {
_tabItems.Add(new TabItem(h, d, p)); _tabItems.Add(new TabItem(h, d));
SelectedTab = _tabItems.Last(); SelectedTab = _tabItems.Last();
}); });
} }
@ -473,6 +437,6 @@ public class TabControlViewModel : ViewModel
private static IEnumerable<TabItem> EnumerateTabs() private static IEnumerable<TabItem> EnumerateTabs()
{ {
yield return new TabItem("New Tab", string.Empty, string.Empty); yield return new TabItem("New Tab", string.Empty);
} }
} }

View File

@ -1,74 +0,0 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Data;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
using FModel.ViewModels.Commands;
using FModel.Views.Resources.Converters;
namespace FModel.ViewModels;
public class UpdateViewModel : ViewModel
{
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private RemindMeCommand _remindMeCommand;
public RemindMeCommand RemindMeCommand => _remindMeCommand ??= new RemindMeCommand(this);
public RangeObservableCollection<GitHubCommit> Commits { get; }
public ICollectionView CommitsView { get; }
public UpdateViewModel()
{
Commits = new RangeObservableCollection<GitHubCommit>();
CommitsView = new ListCollectionView(Commits)
{
GroupDescriptions = { new PropertyGroupDescription("Commit.Author.Date", new DateTimeToDateConverter()) }
};
if (UserSettings.Default.NextUpdateCheck < DateTime.Now)
RemindMeCommand.Execute(this, null);
}
public async Task Load()
{
Commits.AddRange(await _apiEndpointView.GitHubApi.GetCommitHistoryAsync());
var qa = await _apiEndpointView.GitHubApi.GetReleaseAsync("qa");
qa.Assets.OrderByDescending(x => x.CreatedAt).First().IsLatest = true;
foreach (var asset in qa.Assets)
{
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()
{
Commits.FirstOrDefault(x => x.Asset.IsLatest)?.Download();
}
}

View File

@ -1,7 +1,6 @@
<adonisControls:AdonisWindow x:Class="FModel.Views.DirectorySelector" <adonisControls:AdonisWindow x:Class="FModel.Views.DirectorySelector"
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:controls="clr-namespace:FModel.Views.Resources.Controls"
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:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI" xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
@ -52,29 +51,24 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Detected Game" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="0" Grid.Column="0" Text="Detected Game" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:FilterableComboBox Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3" <ComboBox Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding DetectedDirectories}" Margin="0 0 0 5"
ItemsSource="{Binding DetectedDirectories}" Margin="0 0 0 5" VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory, Mode=TwoWay}">
Style="{StaticResource UComboBox}"
adonisExtensions:WatermarkExtension.Watermark="Search for a game..."
VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory, Mode=TwoWay}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding GameName, Converter={x:Static converters:StringToGameConverter.Instance}}" /> <TextBlock Text="{Binding GameName, Converter={x:Static converters:StringToGameConverter.Instance}}" />
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</controls:FilterableComboBox> </ComboBox>
<TextBlock Grid.Row="1" Grid.Column="0" Text="UE Versions" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="UE Versions" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:FilterableComboBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5" <ComboBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding UeVersions}" Margin="0 0 0 5"
ItemsSource="{Binding UeGames}" VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory.UeVersion, Mode=TwoWay}">
Style="{StaticResource UComboBox}"
VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory.UeVersion, Mode=TwoWay}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" /> <TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</controls:FilterableComboBox> </ComboBox>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Directory" VerticalAlignment="Center" /> <TextBlock Grid.Row="2" Grid.Column="0" Text="Directory" VerticalAlignment="Center" />
<TextBox Grid.Row="2" Grid.Column="2" Text="{Binding SelectedDirectory.GameDirectory, Mode=TwoWay}" /> <TextBox Grid.Row="2" Grid.Column="2" Text="{Binding SelectedDirectory.GameDirectory, Mode=TwoWay}" />
@ -89,9 +83,7 @@
</Viewbox> </Viewbox>
</Button> </Button>
</Grid> </Grid>
<Separator Style="{StaticResource CustomSeparator}" Tag="ADD UNDETECTED GAME" /> <Separator Style="{StaticResource CustomSeparator}" Tag="ADD UNDETECTED GAME" />
<Expander ExpandDirection="Down" IsExpanded="False"> <Expander ExpandDirection="Down" IsExpanded="False">
<Grid MaxWidth="{Binding ActualWidth, ElementName=Hello}"> <Grid MaxWidth="{Binding ActualWidth, ElementName=Hello}">
<Grid.RowDefinitions> <Grid.RowDefinitions>

View File

@ -1,10 +1,6 @@
using System; using FModel.ViewModels;
using System.IO;
using System.Linq;
using FModel.ViewModels;
using Ookii.Dialogs.Wpf; using Ookii.Dialogs.Wpf;
using System.Windows; using System.Windows;
using CUE4Parse.Utils;
namespace FModel.Views; namespace FModel.Views;
@ -43,28 +39,6 @@ public partial class DirectorySelector
if (folderBrowser.ShowDialog() == true) if (folderBrowser.ShowDialog() == true)
{ {
HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath; HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath;
// install_folder/
// ├─ Engine/
// ├─ GameName/
// │ ├─ Binaries/
// │ ├─ Content/
// │ │ ├─ Paks/
// our goal is to get the GameName folder
var currentFolder = folderBrowser.SelectedPath.SubstringAfterLast('\\');
if (currentFolder.Equals("Paks", StringComparison.InvariantCulture))
{
var dir = new DirectoryInfo(folderBrowser.SelectedPath);
if (dir.Parent is { Parent: not null } &&
dir.Parent.Name.Equals("Content", StringComparison.InvariantCulture) &&
dir.Parent.Parent.GetDirectories().Any(x => x.Name == "Binaries"))
{
HelloMyNameIsGame.Text = dir.Parent.Parent.Name;
return;
}
}
HelloMyNameIsGame.Text = folderBrowser.SelectedPath.SubstringAfterLast('\\');
} }
} }

View File

@ -1,5 +1,4 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FModel.Extensions;
using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.AvalonEdit.Rendering;
namespace FModel.Views.Resources.Controls; namespace FModel.Views.Resources.Controls;
@ -30,10 +29,8 @@ public class GamePathElementGenerator : VisualLineElementGenerator
public override VisualLineElement ConstructElement(int offset) public override VisualLineElement ConstructElement(int offset)
{ {
var m = FindMatch(offset); var m = FindMatch(offset);
if (!m.Success || m.Index != 0 || if (!m.Success || m.Index != 0) return null;
!m.Groups.TryGetValue("target", out var g)) return null;
var parentExportType = CurrentContext.Document.GetParentExportType(offset); return m.Groups.TryGetValue("target", out var g) ? new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) : null;
return new GamePathVisualLineText(g.Value, parentExportType, CurrentContext.VisualLine, g.Length + g.Index + 1);
} }
} }

View File

@ -16,16 +16,14 @@ public class GamePathVisualLineText : VisualLineText
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public delegate void GamePathOnClick(string gamePath, string parentExportType); public delegate void GamePathOnClick(string gamePath);
public event GamePathOnClick OnGamePathClicked; public event GamePathOnClick OnGamePathClicked;
private readonly string _gamePath; private readonly string _gamePath;
private readonly string _parentExportType;
public GamePathVisualLineText(string gamePath, string parentExportType, VisualLine parentVisualLine, int length) : base(parentVisualLine, length) public GamePathVisualLineText(string gamePath, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
{ {
_gamePath = gamePath; _gamePath = gamePath;
_parentExportType = parentExportType;
} }
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
@ -58,14 +56,14 @@ public class GamePathVisualLineText : VisualLineText
if (e.Handled || OnGamePathClicked == null) if (e.Handled || OnGamePathClicked == null)
return; return;
OnGamePathClicked(_gamePath, _parentExportType); OnGamePathClicked(_gamePath);
e.Handled = true; e.Handled = true;
} }
protected override VisualLineText CreateInstance(int length) protected override VisualLineText CreateInstance(int length)
{ {
var a = new GamePathVisualLineText(_gamePath, _parentExportType, ParentVisualLine, length); var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length);
a.OnGamePathClicked += async (gamePath, parentExportType) => a.OnGamePathClicked += async gamePath =>
{ {
var obj = gamePath.SubstringAfterLast('.'); var obj = gamePath.SubstringAfterLast('.');
var package = gamePath.SubstringBeforeLast('.'); var package = gamePath.SubstringBeforeLast('.');
@ -82,17 +80,17 @@ public class GamePathVisualLineText : VisualLineText
} }
else else
{ {
lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumber(obj); lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj);
line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
} }
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length); AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber); AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
} }
else else
{ {
await _threadWorkerView.Begin(cancellationToken => await _threadWorkerView.Begin(cancellationToken =>
_applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType)); _applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj));
} }
}; };
return a; return a;

View File

@ -126,7 +126,7 @@ 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.GetLineNumber(tabItem.ScrollTrigger);
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);
@ -223,9 +223,10 @@ public partial class AvalonEditor
private void OnTabClose(object sender, EventArgs eventArgs) private void OnTabClose(object sender, EventArgs eventArgs)
{ {
if (eventArgs is not TabControlViewModel.TabEventArgs e || e.TabToRemove.Document?.FileName is not { } fileName) if (eventArgs is not TabControlViewModel.TabEventArgs e || e.TabToRemove.Document == null)
return; return;
var fileName = e.TabToRemove.Document.FileName;
if (_savedCarets.ContainsKey(fileName)) if (_savedCarets.ContainsKey(fileName))
_savedCarets.Remove(fileName); _savedCarets.Remove(fileName);
} }

View File

@ -1,131 +0,0 @@
<UserControl x:Class="FModel.Views.Resources.Controls.CommitControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
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"
BorderBrush="{DynamicResource {x:Static adonisUi:Brushes.Layer4BackgroundBrush}}">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Commit.Message, Converter={x:Static converters:CommitMessageConverter.Instance}, ConverterParameter=Title}" FontWeight="Bold" TextWrapping="Wrap" />
<TextBlock Grid.Row="1" Text="{Binding Commit.Message, Converter={x:Static converters:CommitMessageConverter.Instance}, ConverterParameter=Description}" TextWrapping="Wrap">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Commit.Message, Converter={x:Static converters:CommitMessageConverter.Instance}, ConverterParameter=Description}" Value="">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding Author.AvatarUrl}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="2" FontSize="11">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} committed {1}">
<Binding Path="Author.Login" />
<Binding Path="Commit.Author.Date" Converter="{x:Static converters:RelativeDateTimeConverter.Instance}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</Grid>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="15" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
BorderThickness="1"
CornerRadius="2.5"
Padding="5,2"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock FontSize="9" Foreground="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=Border}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Asset.IsLatest}" Value="True">
<Setter Property="Text" Value="Latest" />
</DataTrigger>
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
<Setter Property="Text" Value="Current" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Asset.IsLatest}" Value="True">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="BorderBrush" Value="#3fb950" />
<Setter Property="Background" Value="#0f3fb950" />
</DataTrigger>
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="BorderBrush" Value="#3f92b9" />
<Setter Property="Background" Value="#0f3f92b9" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<controls:CommitDownloaderControl Grid.Column="2" Commit="{Binding}">
<controls:CommitDownloaderControl.Style>
<Style TargetType="controls:CommitDownloaderControl">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsDownloadable}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</controls:CommitDownloaderControl.Style>
</controls:CommitDownloaderControl>
</Grid>
</Grid>
</Border>
</UserControl>

View File

@ -1,12 +0,0 @@
using System.Windows.Controls;
namespace FModel.Views.Resources.Controls;
public partial class CommitControl : UserControl
{
public CommitControl()
{
InitializeComponent();
}
}

View File

@ -1,55 +0,0 @@
<UserControl x:Class="FModel.Views.Resources.Controls.CommitDownloaderControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="15" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Viewbox Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" HorizontalAlignment="Center">
<Canvas Width="16" Height="16">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Data="{StaticResource ArchiveIcon}" />
</Canvas>
</Viewbox>
<StackPanel Grid.Column="2">
<TextBlock Text="Size" FontSize="10" />
<TextBlock FontSize="10" Text="{Binding Asset.Size, Converter={x:Static converters:SizeToStringConverter.Instance}}" />
</StackPanel>
</Grid>
<Button Grid.Column="2" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Download"
Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Grid}}"
Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
IsEnabled="{Binding IsCurrent, Converter={x:Static converters:InvertBooleanConverter.Instance}}"
Click="OnDownload">
<Viewbox Width="16" Height="16" VerticalAlignment="Center" HorizontalAlignment="Center">
<Canvas Width="16" Height="16">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Data="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z" />
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Data="M11.78 4.72a.749.749 0 1 1-1.06 1.06L8.75 3.811V9.5a.75.75 0 0 1-1.5 0V3.811L5.28 5.78a.749.749 0 1 1-1.06-1.06l3.25-3.25a.749.749 0 0 1 1.06 0l3.25 3.25Z" />
</Canvas>
</Viewbox>
</Button>
</Grid>
</UserControl>

View File

@ -1,28 +0,0 @@
using System.Windows;
using System.Windows.Controls;
using FModel.ViewModels.ApiEndpoints.Models;
namespace FModel.Views.Resources.Controls;
public partial class CommitDownloaderControl : UserControl
{
public CommitDownloaderControl()
{
InitializeComponent();
}
public static readonly DependencyProperty CommitProperty =
DependencyProperty.Register(nameof(Commit), typeof(GitHubCommit), typeof(CommitDownloaderControl), new PropertyMetadata(null));
public GitHubCommit Commit
{
get { return (GitHubCommit)GetValue(CommitProperty); }
set { SetValue(CommitProperty, value); }
}
private void OnDownload(object sender, RoutedEventArgs e)
{
Commit.Download();
}
}

View File

@ -1,273 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace FModel.Views.Resources.Controls;
/// <summary>
/// https://stackoverflow.com/a/58066259/13389331
/// </summary>
public class FilterableComboBox : ComboBox
{
/// <summary>
/// If true, on lost focus or enter key pressed, checks the text in the combobox. If the text is not present
/// in the list, it leaves it blank.
/// </summary>
public bool OnlyValuesInList {
get => (bool)GetValue(OnlyValuesInListProperty);
set => SetValue(OnlyValuesInListProperty, value);
}
public static readonly DependencyProperty OnlyValuesInListProperty =
DependencyProperty.Register(nameof(OnlyValuesInList), typeof(bool), typeof(FilterableComboBox));
/// <summary>
/// Selected item, changes only on lost focus or enter key pressed
/// </summary>
public object EffectivelySelectedItem {
get => (bool)GetValue(EffectivelySelectedItemProperty);
set => SetValue(EffectivelySelectedItemProperty, value);
}
public static readonly DependencyProperty EffectivelySelectedItemProperty =
DependencyProperty.Register(nameof(EffectivelySelectedItem), typeof(object), typeof(FilterableComboBox));
private string CurrentFilter = string.Empty;
private bool TextBoxFreezed;
protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox;
private UserChange<bool> IsDropDownOpenUC;
/// <summary>
/// Triggers on lost focus or enter key pressed, if the selected item changed since the last time focus was lost or enter was pressed.
/// </summary>
public event Action<FilterableComboBox, object> SelectionEffectivelyChanged;
public FilterableComboBox()
{
IsDropDownOpenUC = new UserChange<bool>(v => IsDropDownOpen = v);
DropDownOpened += FilteredComboBox_DropDownOpened;
Focusable = true;
IsEditable = true;
IsTextSearchEnabled = true;
StaysOpenOnEdit = true;
IsReadOnly = false;
Loaded += (s, e) => {
if (EditableTextBox != null)
new TextBoxBaseUserChangeTracker(EditableTextBox).UserTextChanged += FilteredComboBox_UserTextChange;
};
SelectionChanged += (_, __) => shouldTriggerSelectedItemChanged = true;
SelectionEffectivelyChanged += (_, o) => EffectivelySelectedItem = o;
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.Down && !IsDropDownOpen) {
IsDropDownOpen = true;
e.Handled = true;
}
else if (e.Key == Key.Escape) {
ClearFilter();
Text = "";
IsDropDownOpen = true;
}
else if (e.Key == Key.Enter || e.Key == Key.Tab) {
CheckSelectedItem();
TriggerSelectedItemChanged();
}
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnPreviewLostKeyboardFocus(e);
CheckSelectedItem();
if ((e.OldFocus == this || e.OldFocus == EditableTextBox) && e.NewFocus != this && e.NewFocus != EditableTextBox)
TriggerSelectedItemChanged();
}
private void CheckSelectedItem()
{
if (OnlyValuesInList)
Text = SelectedItem?.ToString() ?? "";
}
private bool shouldTriggerSelectedItemChanged = false;
private void TriggerSelectedItemChanged()
{
if (shouldTriggerSelectedItemChanged) {
SelectionEffectivelyChanged?.Invoke(this, SelectedItem);
shouldTriggerSelectedItemChanged = false;
}
}
public void ClearFilter()
{
if (string.IsNullOrEmpty(CurrentFilter)) return;
CurrentFilter = "";
CollectionViewSource.GetDefaultView(ItemsSource).Refresh();
}
private void FilteredComboBox_DropDownOpened(object sender, EventArgs e)
{
if (IsDropDownOpenUC.IsUserChange)
ClearFilter();
}
private void FilteredComboBox_UserTextChange(object sender, EventArgs e)
{
if (TextBoxFreezed) return;
var tb = EditableTextBox;
if (tb.SelectionStart + tb.SelectionLength == tb.Text.Length)
CurrentFilter = tb.Text.Substring(0, tb.SelectionStart).ToLower();
else
CurrentFilter = tb.Text.ToLower();
RefreshFilter();
}
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null) {
var view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += FilterItem;
}
if (oldValue != null) {
var view = CollectionViewSource.GetDefaultView(oldValue);
if (view != null) view.Filter -= FilterItem;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
private void RefreshFilter()
{
if (ItemsSource == null) return;
var view = CollectionViewSource.GetDefaultView(ItemsSource);
FreezTextBoxState(() => {
var isDropDownOpen = IsDropDownOpen;
//always hide because showing it enables the user to pick with up and down keys, otherwise it's not working because of the glitch in view.Refresh()
IsDropDownOpenUC.Set(false);
view.Refresh();
if (!string.IsNullOrEmpty(CurrentFilter) || isDropDownOpen)
IsDropDownOpenUC.Set(true);
if (SelectedItem == null) {
foreach (var itm in ItemsSource)
if (itm.ToString() == Text) {
SelectedItem = itm;
break;
}
}
});
}
private void FreezTextBoxState(Action action)
{
TextBoxFreezed = true;
var tb = EditableTextBox;
var text = Text;
var selStart = tb.SelectionStart;
var selLen = tb.SelectionLength;
action();
Text = text;
tb.SelectionStart = selStart;
tb.SelectionLength = selLen;
TextBoxFreezed = false;
}
private bool FilterItem(object value)
{
if (value == null) return false;
if (CurrentFilter.Length == 0) return true;
return value.ToString().ToLower().Contains(CurrentFilter);
}
private class TextBoxBaseUserChangeTracker
{
private bool IsTextInput { get; set; }
public TextBox TextBoxBase { get; set; }
private List<Key> PressedKeys = new List<Key>();
public event EventHandler UserTextChanged;
private string LastText;
public TextBoxBaseUserChangeTracker(TextBox textBoxBase)
{
TextBoxBase = textBoxBase;
LastText = TextBoxBase.ToString();
textBoxBase.PreviewTextInput += (s, e) => {
IsTextInput = true;
};
textBoxBase.TextChanged += (s, e) => {
var isUserChange = PressedKeys.Count > 0 || IsTextInput || LastText == TextBoxBase.ToString();
IsTextInput = false;
LastText = TextBoxBase.ToString();
if (isUserChange)
UserTextChanged?.Invoke(this, e);
};
textBoxBase.PreviewKeyDown += (s, e) => {
switch (e.Key) {
case Key.Back:
case Key.Space:
if (!PressedKeys.Contains(e.Key))
PressedKeys.Add(e.Key);
break;
}
if (e.Key == Key.Back) {
var textBox = textBoxBase as TextBox;
if (textBox.SelectionStart > 0 && textBox.SelectionLength > 0 && (textBox.SelectionStart + textBox.SelectionLength) == textBox.Text.Length) {
textBox.SelectionStart--;
textBox.SelectionLength++;
e.Handled = true;
UserTextChanged?.Invoke(this, e);
}
}
};
textBoxBase.PreviewKeyUp += (s, e) => {
if (PressedKeys.Contains(e.Key))
PressedKeys.Remove(e.Key);
};
textBoxBase.LostFocus += (s, e) => {
PressedKeys.Clear();
IsTextInput = false;
};
}
}
private class UserChange<T>
{
private Action<T> action;
public bool IsUserChange { get; private set; } = true;
public UserChange(Action<T> action)
{
this.action = action;
}
public void Set(T val)
{
try {
IsUserChange = false;
action(val);
}
finally {
IsUserChange = true;
}
}
}
}

View File

@ -1,25 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public class CommitMessageConverter : IValueConverter
{
public static readonly CommitMessageConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string commitMessage)
{
var parts = commitMessage.Split("\n\n");
return parameter?.ToString() == "Title" ? parts[0] : parts.Length > 1 ? parts[1] : string.Empty;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@ -1,24 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public class DateTimeToDateConverter : IValueConverter
{
public static readonly DateTimeToDateConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTime dateTime)
{
return DateOnly.FromDateTime(dateTime);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@ -1,24 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public class InvertBooleanConverter : IValueConverter
{
public static readonly InvertBooleanConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolean)
{
return !boolean;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@ -1,65 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public class RelativeDateTimeConverter : IValueConverter
{
public static readonly RelativeDateTimeConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTime dateTime)
{
var timeSpan = DateTime.Now - dateTime.ToLocalTime();
int time;
string unit;
if (timeSpan.TotalSeconds < 30)
return "Just now";
if (timeSpan.TotalMinutes < 1)
{
time = timeSpan.Seconds;
unit = "second";
}
else if (timeSpan.TotalHours < 1)
{
time = timeSpan.Minutes;
unit = "minute";
}
else switch (timeSpan.TotalDays)
{
case < 1:
time = timeSpan.Hours;
unit = "hour";
break;
case < 7:
time = timeSpan.Days;
unit = "day";
break;
case < 30:
time = timeSpan.Days / 7;
unit = "week";
break;
case < 365:
time = timeSpan.Days / 30;
unit = "month";
break;
default:
time = timeSpan.Days / 365;
unit = "year";
break;
}
return $"{time} {unit}{(time > 1 ? "s" : string.Empty)} ago";
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@ -68,8 +68,6 @@
<Geometry x:Key="UnfoldIcon">M12 5.83l2.46 2.46c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L12.7 3.7c-.39-.39-1.02-.39-1.41 0L8.12 6.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 5.83zm0 12.34l-2.46-2.46c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41l3.17 3.18c.39.39 1.02.39 1.41 0l3.17-3.17c.39-.39.39-1.02 0-1.41-.39-.39-1.02-.39-1.41 0L12 18.17z</Geometry> <Geometry x:Key="UnfoldIcon">M12 5.83l2.46 2.46c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L12.7 3.7c-.39-.39-1.02-.39-1.41 0L8.12 6.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 5.83zm0 12.34l-2.46-2.46c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41l3.17 3.18c.39.39 1.02.39 1.41 0l3.17-3.17c.39-.39.39-1.02 0-1.41-.39-.39-1.02-.39-1.41 0L12 18.17z</Geometry>
<Geometry x:Key="LocateMeIcon">M11.71,17.99C8.53,17.84,6,15.22,6,12c0-3.31,2.69-6,6-6c3.22,0,5.84,2.53,5.99,5.71l-2.1-0.63C15.48,9.31,13.89,8,12,8 c-2.21,0-4,1.79-4,4c0,1.89,1.31,3.48,3.08,3.89L11.71,17.99z M22,12c0,0.3-0.01,0.6-0.04,0.9l-1.97-0.59C20,12.21,20,12.1,20,12 c0-4.42-3.58-8-8-8s-8,3.58-8,8s3.58,8,8,8c0.1,0,0.21,0,0.31-0.01l0.59,1.97C12.6,21.99,12.3,22,12,22C6.48,22,2,17.52,2,12 C2,6.48,6.48,2,12,2S22,6.48,22,12z M18.23,16.26l2.27-0.76c0.46-0.15,0.45-0.81-0.01-0.95l-7.6-2.28 c-0.38-0.11-0.74,0.24-0.62,0.62l2.28,7.6c0.14,0.47,0.8,0.48,0.95,0.01l0.76-2.27l3.91,3.91c0.2,0.2,0.51,0.2,0.71,0l1.27-1.27 c0.2-0.2,0.2-0.51,0-0.71L18.23,16.26z</Geometry> <Geometry x:Key="LocateMeIcon">M11.71,17.99C8.53,17.84,6,15.22,6,12c0-3.31,2.69-6,6-6c3.22,0,5.84,2.53,5.99,5.71l-2.1-0.63C15.48,9.31,13.89,8,12,8 c-2.21,0-4,1.79-4,4c0,1.89,1.31,3.48,3.08,3.89L11.71,17.99z M22,12c0,0.3-0.01,0.6-0.04,0.9l-1.97-0.59C20,12.21,20,12.1,20,12 c0-4.42-3.58-8-8-8s-8,3.58-8,8s3.58,8,8,8c0.1,0,0.21,0,0.31-0.01l0.59,1.97C12.6,21.99,12.3,22,12,22C6.48,22,2,17.52,2,12 C2,6.48,6.48,2,12,2S22,6.48,22,12z M18.23,16.26l2.27-0.76c0.46-0.15,0.45-0.81-0.01-0.95l-7.6-2.28 c-0.38-0.11-0.74,0.24-0.62,0.62l2.28,7.6c0.14,0.47,0.8,0.48,0.95,0.01l0.76-2.27l3.91,3.91c0.2,0.2,0.51,0.2,0.71,0l1.27-1.27 c0.2-0.2,0.2-0.51,0-0.71L18.23,16.26z</Geometry>
<Geometry x:Key="MeshIcon">M1.8 6q-.525 0-.887-.35Q.55 5.3.55 4.8V4q0-1.425 1.012-2.438Q2.575.55 4 .55h.8q.5 0 .85.362.35.363.35.888 0 .5-.35.85T4.8 3H4q-.425 0-.712.287Q3 3.575 3 4v.8q0 .5-.35.85T1.8 6ZM4 23.45q-1.425 0-2.438-1.012Q.55 21.425.55 20v-.8q0-.5.363-.85.362-.35.887-.35.5 0 .85.35t.35.85v.8q0 .425.288.712Q3.575 21 4 21h.8q.5 0 .85.35t.35.85q0 .525-.35.887-.35.363-.85.363Zm15.2 0q-.5 0-.85-.363-.35-.362-.35-.887 0-.5.35-.85t.85-.35h.8q.425 0 .712-.288Q21 20.425 21 20v-.8q0-.5.35-.85t.85-.35q.525 0 .888.35.362.35.362.85v.8q0 1.425-1.012 2.438Q21.425 23.45 20 23.45ZM22.2 6q-.5 0-.85-.35T21 4.8V4q0-.425-.288-.713Q20.425 3 20 3h-.8q-.5 0-.85-.35T18 1.8q0-.525.35-.888.35-.362.85-.362h.8q1.425 0 2.438 1.012Q23.45 2.575 23.45 4v.8q0 .5-.362.85-.363.35-.888.35ZM12 17.35l1-.575v-4.1l3.55-2.075V9.425l-1-.575L12 10.925 8.45 8.85l-1 .575V10.6L11 12.675v4.1Zm-1.325 2.325-4.55-2.65q-.625-.35-.975-.963-.35-.612-.35-1.337V9.45q0-.725.35-1.337.35-.613.975-.963l4.55-2.65Q11.3 4.15 12 4.15t1.325.35l4.55 2.65q.625.35.975.963.35.612.35 1.337v5.275q0 .725-.35 1.337-.35.613-.975.963l-4.55 2.65q-.625.35-1.325.35t-1.325-.35Z</Geometry> <Geometry x:Key="MeshIcon">M1.8 6q-.525 0-.887-.35Q.55 5.3.55 4.8V4q0-1.425 1.012-2.438Q2.575.55 4 .55h.8q.5 0 .85.362.35.363.35.888 0 .5-.35.85T4.8 3H4q-.425 0-.712.287Q3 3.575 3 4v.8q0 .5-.35.85T1.8 6ZM4 23.45q-1.425 0-2.438-1.012Q.55 21.425.55 20v-.8q0-.5.363-.85.362-.35.887-.35.5 0 .85.35t.35.85v.8q0 .425.288.712Q3.575 21 4 21h.8q.5 0 .85.35t.35.85q0 .525-.35.887-.35.363-.85.363Zm15.2 0q-.5 0-.85-.363-.35-.362-.35-.887 0-.5.35-.85t.85-.35h.8q.425 0 .712-.288Q21 20.425 21 20v-.8q0-.5.35-.85t.85-.35q.525 0 .888.35.362.35.362.85v.8q0 1.425-1.012 2.438Q21.425 23.45 20 23.45ZM22.2 6q-.5 0-.85-.35T21 4.8V4q0-.425-.288-.713Q20.425 3 20 3h-.8q-.5 0-.85-.35T18 1.8q0-.525.35-.888.35-.362.85-.362h.8q1.425 0 2.438 1.012Q23.45 2.575 23.45 4v.8q0 .5-.362.85-.363.35-.888.35ZM12 17.35l1-.575v-4.1l3.55-2.075V9.425l-1-.575L12 10.925 8.45 8.85l-1 .575V10.6L11 12.675v4.1Zm-1.325 2.325-4.55-2.65q-.625-.35-.975-.963-.35-.612-.35-1.337V9.45q0-.725.35-1.337.35-.613.975-.963l4.55-2.65Q11.3 4.15 12 4.15t1.325.35l4.55 2.65q.625.35.975.963.35.612.35 1.337v5.275q0 .725-.35 1.337-.35.613-.975.963l-4.55 2.65q-.625.35-1.325.35t-1.325-.35Z</Geometry>
<Geometry x:Key="ArchiveIcon">M3.5 1.75v11.5c0 .09.048.173.126.217a.75.75 0 0 1-.752 1.298A1.748 1.748 0 0 1 2 13.25V1.75C2 .784 2.784 0 3.75 0h5.586c.464 0 .909.185 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0 1 12.25 15h-.5a.75.75 0 0 1 0-1.5h.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177L9.513 1.573a.25.25 0 0 0-.177-.073H7.25a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5h-3a.25.25 0 0 0-.25.25Zm3.75 8.75h.5c.966 0 1.75.784 1.75 1.75v3a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1-.75-.75v-3c0-.966.784-1.75 1.75-1.75ZM6 5.25a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 6 5.25Zm.75 2.25h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 6.75A.75.75 0 0 1 8.75 6h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 6.75ZM8.75 3h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 9.75A.75.75 0 0 1 8.75 9h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 9.75Zm-1 2.5v2.25h1v-2.25a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25Z</Geometry>
<Geometry x:Key="GitHubIcon">M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z</Geometry>
<Style x:Key="TabItemFillSpace" TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}"> <Style x:Key="TabItemFillSpace" TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="Width"> <Setter Property="Width">
@ -82,31 +80,6 @@
</Setter> </Setter>
</Style> </Style>
<Style x:Key="HighlightedCheckBox" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="White" />
<Style.Triggers>
<EventTrigger RoutedEvent="CheckBox.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation From="YellowGreen"
To="Goldenrod"
Storyboard.TargetProperty="BorderBrush.Color"
AutoReverse="True"
RepeatBehavior="Forever"
Duration="0:0:1" />
<ThicknessAnimation From="0"
To="2"
Storyboard.TargetProperty="BorderThickness"
AutoReverse="True"
RepeatBehavior="Forever"
Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
<Style x:Key="CustomSeparator" TargetType="Separator" BasedOn="{StaticResource {x:Type Separator}}"> <Style x:Key="CustomSeparator" TargetType="Separator" BasedOn="{StaticResource {x:Type Separator}}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.Layer1BorderBrush}}"/> <Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.Layer1BorderBrush}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"/>
@ -210,6 +183,9 @@
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</DataTrigger> </DataTrigger>
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Single}">
<Setter Property="SelectionMode" Value="Single" />
</DataTrigger>
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Multiple}"> <DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Multiple}">
<Setter Property="SelectionMode" Value="Extended" /> <Setter Property="SelectionMode" Value="Extended" />
</DataTrigger> </DataTrigger>
@ -874,7 +850,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Texture" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Textures"> <MenuItem Header="Save Texture (.png)" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Textures">
<MenuItem.Icon> <MenuItem.Icon>
<Viewbox Width="16" Height="16"> <Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24"> <Canvas Width="24" Height="24">
@ -883,7 +859,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Model" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Models"> <MenuItem Header="Save Model (.psk)" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Models">
<MenuItem.Icon> <MenuItem.Icon>
<Viewbox Width="16" Height="16"> <Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24"> <Canvas Width="24" Height="24">
@ -892,7 +868,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Animation" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Animations"> <MenuItem Header="Save Animation (.psa)" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Animations">
<MenuItem.Icon> <MenuItem.Icon>
<Viewbox Width="16" Height="16"> <Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24"> <Canvas Width="24" Height="24">
@ -1473,222 +1449,6 @@
<Setter Property="Focusable" Value="False" /> <Setter Property="Focusable" Value="False" />
</Style> </Style>
<Style x:Key="UComboBox" TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="adonisExtensions:WatermarkExtension.Watermark" Value="UE5 / UE4 / GameName..." />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<Grid>
<Border x:Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}"/>
<Border x:Name="SpotlightLayer"
Background="{TemplateBinding adonisExtensions:CursorSpotlightExtension.BackgroundBrush}"
BorderBrush="{TemplateBinding adonisExtensions:CursorSpotlightExtension.BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}"
adonisExtensions:CursorSpotlightExtension.MouseEventSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}}"
SnapsToDevicePixels="False"/>
<ToggleButton x:Name="ToggleButton"
ClickMode="Press"
Focusable="False"
Foreground="{TemplateBinding Foreground}"
adonisExtensions:CornerRadiusExtension.CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}"
IsChecked="{Binding Path=IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Template="{StaticResource ComboBoxToggleButtonTemplate}"/>
<Border Margin="0, 0, 11, 0">
<DockPanel Margin="{TemplateBinding Padding}">
<adonisControls:ValidationErrorIndicator x:Name="ErrorAlertHost"
ValidatedElement="{Binding ., RelativeSource={RelativeSource TemplatedParent}}"
IsValidatedElementFocused="False"
IsErrorMessageDisplayOnFocusEnabled="{TemplateBinding adonisExtensions:ValidationExtension.IsErrorMessageVisibleOnFocus}"
IsErrorMessageDisplayOnMouseOverEnabled="{TemplateBinding adonisExtensions:ValidationExtension.IsErrorMessageVisibleOnMouseOver}"
ErrorMessagePlacement="{TemplateBinding adonisExtensions:ValidationExtension.ErrorMessagePlacement}"
Visibility="Collapsed"
DockPanel.Dock="Left"
Margin="0, 0, 4, 0"/>
<ContentPresenter x:Name="ContentSite"
IsHitTestVisible="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding ComboBox.SelectionBoxItem}"
ContentTemplate="{TemplateBinding ComboBox.SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"/>
<TextBox x:Name="PART_EditableTextBox"
IsReadOnly="{TemplateBinding IsReadOnly}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Visibility="Hidden"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Focusable="True">
<TextBox.Template>
<ControlTemplate TargetType="TextBox" >
<Grid>
<ContentPresenter x:Name="PlaceholderHost"
Content="{Binding Path=(adonisExtensions:WatermarkExtension.Watermark), RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Opacity="0.5"
IsHitTestVisible="False"
Visibility="Collapsed"/>
<ScrollViewer Name="PART_ContentHost"
Focusable="False"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
Template="{StaticResource TextBoxScrollViewerTemplate}"/>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=(adonisExtensions:WatermarkExtension.IsWatermarkVisible), RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}}" Value="True">
<Setter Property="Visibility" TargetName="PlaceholderHost" Value="Visible"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</TextBox.Template>
</TextBox>
</DockPanel>
</Border>
<!-- Popup showing items -->
<Popup x:Name="PART_Popup"
Placement="Bottom"
Focusable="False"
AllowsTransparency="True"
IsOpen="{TemplateBinding ComboBox.IsDropDownOpen}"
PopupAnimation="Slide"
adonisExtensions:LayerExtension.IncreaseLayer="True">
<Grid x:Name="DropDown"
SnapsToDevicePixels="True"
MinWidth="{TemplateBinding FrameworkElement.ActualWidth}"
MaxHeight="{TemplateBinding ComboBox.MaxDropDownHeight}">
<Border x:Name="DropDownBorder"
Background="{TemplateBinding Background}"
Margin="0, 1, 0, 0"
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<ScrollViewer x:Name="DropDownScroller"
SnapsToDevicePixels="True"
adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode="{Binding Path=(adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode), RelativeSource={RelativeSource TemplatedParent}}"
adonisExtensions:ScrollViewerExtension.HorizontalScrollBarExpansionMode="{Binding Path=(adonisExtensions:ScrollViewerExtension.HorizontalScrollBarExpansionMode), RelativeSource={RelativeSource TemplatedParent}}"
adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement="{Binding Path=(adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement), RelativeSource={RelativeSource TemplatedParent}}"
adonisExtensions:ScrollViewerExtension.HorizontalScrollBarPlacement="{Binding Path=(adonisExtensions:ScrollViewerExtension.HorizontalScrollBarPlacement), RelativeSource={RelativeSource TemplatedParent}}"
adonisExtensions:ScrollViewerExtension.HideScrollBarsUntilMouseOver="{Binding Path=(adonisExtensions:ScrollViewerExtension.HideScrollBarsUntilMouseOver), RelativeSource={RelativeSource TemplatedParent}}">
<ItemsPresenter KeyboardNavigation.DirectionalNavigation="Contained" />
</ScrollViewer>
</Border>
<StackPanel x:Name="BlindIndicator"
Grid.ZIndex="1"
Focusable="False"
Margin="0 7 0 0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Visibility="Visible"
MinHeight="15">
<ContentControl ContentTemplate="{DynamicResource {x:Static adonisUi:Templates.Expander}}"
Foreground="{TemplateBinding Foreground}"
Focusable="False"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RenderTransformOrigin="0.5 0.5">
<ContentControl.RenderTransform>
<TransformGroup>
<RotateTransform Angle="180"/>
<TranslateTransform Y="0"/>
</TransformGroup>
</ContentControl.RenderTransform>
<ContentControl.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<BeginStoryboard.Storyboard>
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.Children[0].Angle"
From="190" To="170" Duration="0:0:0.3"
AutoReverse="True" />
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.Children[1].Y"
From="-1" To="1" Duration="0:0:0.2"
AutoReverse="True" />
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</EventTrigger>
</ContentControl.Triggers>
</ContentControl>
<!-- <TextBlock Text="Game-Specific Versions" -->
<!-- Foreground="{TemplateBinding Foreground}" -->
<!-- FontSize="9" -->
<!-- TextAlignment="Center"> -->
<!-- </TextBlock> -->
<!-- <StackPanel.Background> -->
<!-- <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> -->
<!-- <GradientStop Color="#35000000" Offset="0" /> -->
<!-- <GradientStop Color="#00000000" Offset="1" /> -->
<!-- </LinearGradientBrush> -->
<!-- </StackPanel.Background> -->
</StackPanel>
</Grid>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="FrameworkElement.MinHeight" TargetName="DropDownBorder" Value="95"/>
<Setter Property="Visibility" TargetName="BlindIndicator" Value="Collapsed" />
</Trigger>
<Trigger Property="IsGrouping" Value="True">
<Setter Property="ScrollViewer.CanContentScroll" Value="False"/>
</Trigger>
<Trigger Property="IsEditable" Value="True">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
<Setter Property="UIElement.Visibility" TargetName="PART_EditableTextBox" Value="Visible"/>
<Setter Property="UIElement.Visibility" TargetName="ContentSite" Value="Collapsed"/>
<Setter Property="HorizontalAlignment" TargetName="ToggleButton" Value="Right"/>
<Setter Property="HorizontalAlignment" TargetName="ToggleButton" Value="Right"/>
</Trigger>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Visibility" TargetName="ErrorAlertHost" Value="Visible"/>
<Setter Property="BorderBrush" TargetName="Border" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}"/>
<Setter Property="BorderBrush" TargetName="SpotlightLayer" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}"/>
</Trigger>
<Trigger Property="IsFocused" SourceName="PART_EditableTextBox" Value="True">
<Setter Property="IsValidatedElementFocused" TargetName="ErrorAlertHost" Value="True"/>
</Trigger>
<Trigger Property="IsDropDownOpen" Value="True">
<Setter Property="IsValidatedElementFocused" TargetName="ErrorAlertHost" Value="True"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Type Button}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}"> <Style x:Key="{x:Type Button}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Focusable" Value="False" /> <Setter Property="Focusable" Value="False" />
</Style> </Style>

View File

@ -185,7 +185,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Texture" Command="{Binding DataContext.RightClickMenuCommand}"> <MenuItem Header="Save Texture (.png)" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter> <MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}"> <MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Textures" /> <Binding Source="Assets_Save_Textures" />
@ -200,7 +200,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Model" Command="{Binding DataContext.RightClickMenuCommand}"> <MenuItem Header="Save Model (.psk)" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter> <MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}"> <MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Models" /> <Binding Source="Assets_Save_Models" />
@ -215,7 +215,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Save Animation" Command="{Binding DataContext.RightClickMenuCommand}"> <MenuItem Header="Save Animation (.psa)" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter> <MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}"> <MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Animations" /> <Binding Source="Assets_Save_Animations" />

View File

@ -42,6 +42,7 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@ -54,7 +55,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Output Directory *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Directory where log files, backups and other do-not-delete files will be put in." /> <TextBlock Grid.Row="0" Grid.Column="0" Text="Output Directory *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Directory where log files, backups and other do-not-delete files will be put in." />
<TextBox x:Name="ImJackedBro" Grid.Row="0" Grid.Column="2" IsReadOnly="True" Text="{Binding OutputDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" /> <TextBox x:Name="ImJackedBro" Grid.Row="0" Grid.Column="2" Text="{Binding OutputDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
<Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseOutput" Margin="0 0 0 5" /> <Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseOutput" Margin="0 0 0 5" />
<CheckBox Grid.Row="0" Grid.Column="6" Margin="5 0 0 5" ToolTip="Customize the directory of more output folders" <CheckBox Grid.Row="0" Grid.Column="6" Margin="5 0 0 5" ToolTip="Customize the directory of more output folders"
@ -78,24 +79,34 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Export Raw Data Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" /> <TextBlock Grid.Row="0" Grid.Column="0" Text="Export Raw Data Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" />
<TextBox Grid.Row="0" Grid.Column="2" IsReadOnly="True" Text="{Binding RawDataDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" /> <TextBox Grid.Row="0" Grid.Column="2" Text="{Binding RawDataDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
<Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseRawData" Margin="0 0 0 5" /> <Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseRawData" Margin="0 0 0 5" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Save Properties Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="Save Properties Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" />
<TextBox Grid.Row="1" Grid.Column="2" IsReadOnly="True" Text="{Binding PropertiesDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" /> <TextBox Grid.Row="1" Grid.Column="2" Text="{Binding PropertiesDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
<Button Grid.Row="1" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseProperties" Margin="0 0 0 5" /> <Button Grid.Row="1" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseProperties" Margin="0 0 0 5" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Save Texture Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" /> <TextBlock Grid.Row="2" Grid.Column="0" Text="Save Texture Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" />
<TextBox Grid.Row="2" Grid.Column="2" IsReadOnly="True" Text="{Binding TextureDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" /> <TextBox Grid.Row="2" Grid.Column="2" Text="{Binding TextureDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
<Button Grid.Row="2" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseTexture" Margin="0 0 0 5" /> <Button Grid.Row="2" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseTexture" Margin="0 0 0 5" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Save Audio Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" /> <TextBlock Grid.Row="3" Grid.Column="0" Text="Save Audio Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" />
<TextBox Grid.Row="3" Grid.Column="2" IsReadOnly="True" Text="{Binding AudioDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" /> <TextBox Grid.Row="3" Grid.Column="2" Text="{Binding AudioDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
<Button Grid.Row="3" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseAudio" Margin="0 0 0 5" /> <Button Grid.Row="3" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseAudio" Margin="0 0 0 5" />
</Grid> </Grid>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Discord Rich Presence" VerticalAlignment="Center" /> <TextBlock Grid.Row="2" Grid.Column="0" Text="Update Mode" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Receive updates each time a new release is pushed to GitHub&#10;Receive updates each time a new commit is pushed to GitHub" />
<ComboBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.DiscordRpcs}" SelectedItem="{Binding SettingsView.SelectedDiscordRpc, Mode=TwoWay}" <ComboBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.UpdateModes}" SelectedItem="{Binding SettingsView.SelectedUpdateMode, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Discord Rich Presence" VerticalAlignment="Center" />
<ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.DiscordRpcs}" SelectedItem="{Binding SettingsView.SelectedDiscordRpc, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
@ -104,27 +115,14 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<Separator Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="GAME"></Separator> <Separator Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="GAME"></Separator>
<TextBlock Grid.Row="4" Grid.Column="0" Text="Archive Directory *" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="5" Grid.Column="0" Text="Archive Directory *" VerticalAlignment="Center" Margin="0 0 0 5" />
<TextBox Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" IsReadOnly="True" Text="{Binding GameDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" /> <TextBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" Text="{Binding GameDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
<Button Grid.Row="4" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" Margin="0 0 0 5" /> <Button Grid.Row="5" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" Margin="0 0 0 5" />
<TextBlock Grid.Row="5" Grid.Column="0" Text="UE Versions *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the UE version to use when parsing packages" /> <TextBlock Grid.Row="6" Grid.Column="0" Text="UE Versions *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the UE version to use when parsing packages" />
<controls:FilterableComboBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="5" SelectedItem="{Binding SettingsView.SelectedUeGame, Mode=TwoWay}" <ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.UeGames}" SelectedItem="{Binding SettingsView.SelectedUeGame, Mode=TwoWay}"
ItemsSource="{Binding SettingsView.UeGames}"
Style="{StaticResource UComboBox}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</controls:FilterableComboBox>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Texture Platform *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the game's platform to ensure texture compatibility" />
<ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.Platforms}" SelectedItem="{Binding SettingsView.SelectedUePlatform, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Margin="0 0 0 5"> Margin="0 0 0 5">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
@ -134,8 +132,19 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<TextBlock Grid.Row="7" Grid.Column="0" Text="Compressed Audio" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="What to do when encountering a compressed audio file" /> <TextBlock Grid.Row="7" Grid.Column="0" Text="Texture Platform *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the game's platform to ensure texture compatibility" />
<ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.CompressedAudios}" SelectedItem="{Binding SettingsView.SelectedCompressedAudio, Mode=TwoWay}" <ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.Platforms}" SelectedItem="{Binding SettingsView.SelectedUePlatform, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="8" Grid.Column="0" Text="Compressed Audio" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="What to do when encountering a compressed audio file" />
<ComboBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.CompressedAudios}" SelectedItem="{Binding SettingsView.SelectedCompressedAudio, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
@ -144,8 +153,8 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<TextBlock Grid.Row="8" Grid.Column="0" Text="Packages Language" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Language used and shown when parsing packages" /> <TextBlock Grid.Row="9" Grid.Column="0" Text="Packages Language" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Language used and shown when parsing packages" />
<ComboBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.AssetLanguages}" SelectedItem="{Binding SettingsView.SelectedAssetLanguage, Mode=TwoWay}" <ComboBox Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.AssetLanguages}" SelectedItem="{Binding SettingsView.SelectedAssetLanguage, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
@ -154,14 +163,14 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<TextBlock Grid.Row="9" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 5 0 5" ToolTip="Auto-save packages following their game directory" /> <TextBlock Grid.Row="10" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 5 0 5" ToolTip="Auto-save packages following their game directory" />
<CheckBox Grid.Row="9" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="10" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 0"/> IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 0"/>
<Separator Grid.Row="10" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="ADVANCED"></Separator> <Separator Grid.Row="11" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="ADVANCED"></Separator>
<TextBlock Grid.Row="11" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="12" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" />
<Grid Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"> <Grid Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="5" /> <ColumnDefinition Width="5" />
@ -175,10 +184,10 @@
<Button Grid.Column="4" Content="MapStructTypes" Click="OpenMapStructTypes" /> <Button Grid.Column="4" Content="MapStructTypes" Click="OpenMapStructTypes" />
</Grid> </Grid>
<TextBlock Grid.Row="12" Grid.Column="0" Text="AES Reload at Launch" VerticalAlignment="Center" Margin="0 0 0 5" <TextBlock Grid.Row="13" Grid.Column="0" Text="AES Reload at Launch" VerticalAlignment="Center" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}" /> Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}" />
<ComboBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5" <ComboBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
ItemsSource="{Binding SettingsView.AesReloads}" SelectedItem="{Binding SettingsView.SelectedAesReload, Mode=TwoWay}" ItemsSource="{Binding SettingsView.AesReloads}" SelectedItem="{Binding SettingsView.SelectedAesReload, Mode=TwoWay}"
Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}"> Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}">
@ -189,8 +198,8 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<TextBlock Grid.Row="13" Grid.Column="0" Text="Endpoint Configuration" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="14" Grid.Column="0" Text="Endpoint Configuration" VerticalAlignment="Center" Margin="0 0 0 5" />
<Grid Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5" <Grid Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@ -202,24 +211,24 @@
<Button Grid.Column="2" Content="Mapping" Click="OpenMappingEndpoint" /> <Button Grid.Column="2" Content="Mapping" Click="OpenMappingEndpoint" />
</Grid> </Grid>
<TextBlock Grid.Row="14" Grid.Column="0" Text="Local Mapping File" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="15" Grid.Column="0" Text="Local Mapping File" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="14" Grid.Column="2" Margin="0 5 0 10" <CheckBox Grid.Row="15" Grid.Column="2" Margin="0 5 0 10"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SettingsView.MappingEndpoint.Overwrite, Mode=TwoWay}" /> IsChecked="{Binding SettingsView.MappingEndpoint.Overwrite, Mode=TwoWay}" />
<TextBlock Grid.Row="15" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 0 0 5" <TextBlock Grid.Row="16" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" /> Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBox Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}" <TextBox Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" /> Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
<Button Grid.Row="15" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 0 0 5" <Button Grid.Row="16" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" /> Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock Grid.Row="16" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="17" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="16" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="17" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/> IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
@ -307,7 +316,6 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@ -319,10 +327,10 @@
<TextBlock Grid.Row="0" Grid.Column="0" Text="Model Export Directory *" VerticalAlignment="Center" Margin="0 0 0 5" <TextBlock Grid.Row="0" Grid.Column="0" Text="Model Export Directory *" VerticalAlignment="Center" Margin="0 0 0 5"
ToolTip="This will be the directory where Meshes, Materials and Animations will be exported" /> ToolTip="This will be the directory where Meshes, Materials and Animations will be exported" />
<TextBox Grid.Row="0" Grid.Column="2" IsReadOnly="True" Text="{Binding ModelDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" /> <TextBox Grid.Row="0" Grid.Column="2" Text="{Binding ModelDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
<Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseModels" Margin="0 0 0 5" /> <Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseModels" Margin="0 0 0 5" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Mesh Format" VerticalAlignment="Center" Margin="0 0 0 5"/> <TextBlock Grid.Row="1" Grid.Column="0" Text="Mesh Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MeshExportFormats}" SelectedItem="{Binding SettingsView.SelectedMeshExportFormat, Mode=TwoWay}" <ComboBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MeshExportFormats}" SelectedItem="{Binding SettingsView.SelectedMeshExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
@ -332,28 +340,8 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Socket Format (ActorX)" VerticalAlignment="Center" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.SocketSettingsEnabled}"/> <TextBlock Grid.Row="2" Grid.Column="0" Text="Socket Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.SocketExportFormats}" SelectedItem="{Binding SettingsView.SelectedSocketExportFormat, Mode=TwoWay}" <ComboBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.SocketExportFormats}" SelectedItem="{Binding SettingsView.SelectedSocketExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.SocketSettingsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Compression Format (UEFormat)" VerticalAlignment="Center" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.CompressionSettingsEnabled}"/>
<ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.CompressionFormats}" SelectedItem="{Binding SettingsView.SelectedCompressionFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.CompressionSettingsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="4" Grid.Column="0" Text="Level Of Detail Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.LodExportFormats}" SelectedItem="{Binding SettingsView.SelectedLodExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
@ -362,52 +350,62 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<Separator Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" /> <TextBlock Grid.Row="3" Grid.Column="0" Text="Level Of Detail Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.LodExportFormats}" SelectedItem="{Binding SettingsView.SelectedLodExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Preview Max Texture Size" VerticalAlignment="Center" Margin="0 0 0 5" /> <Separator Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
<Slider Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" TickPlacement="None" Minimum="4" Maximum="4096" Ticks="4,8,16,32,64,128,256,512,1024,2048,4096"
<TextBlock Grid.Row="5" Grid.Column="0" Text="Preview Max Texture Size" VerticalAlignment="Center" Margin="0 0 0 5" />
<Slider Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" TickPlacement="None" Minimum="4" Maximum="4096" Ticks="4,8,16,32,64,128,256,512,1024,2048,4096"
AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 5 0 5" AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 5 0 5"
Value="{Binding PreviewMaxTextureSize, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/> Value="{Binding PreviewMaxTextureSize, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/>
<TextBlock Grid.Row="7" Grid.Column="0" Text="Preview Static Meshes" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="6" Grid.Column="0" Text="Preview Static Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewStaticMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" IsChecked="{Binding PreviewStaticMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="8" Grid.Column="0" Text="Preview Skeletal Meshes" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="7" Grid.Column="0" Text="Preview Skeletal Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewSkeletalMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" IsChecked="{Binding PreviewSkeletalMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="9" Grid.Column="0" Text="Preview Materials" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="8" Grid.Column="0" Text="Preview Materials" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" IsChecked="{Binding PreviewMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="10" Grid.Column="0" Text="Preview Levels (.umap)" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="9" Grid.Column="0" Text="Preview Levels (.umap)" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewWorlds, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" IsChecked="{Binding PreviewWorlds, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="11" Grid.Column="0" Text="Save Materials Embedded within Meshes" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="10" Grid.Column="0" Text="Save Materials Embedded within Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveEmbeddedMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" IsChecked="{Binding SaveEmbeddedMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="12" Grid.Column="0" Text="Save Morph Targets in Meshes" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="11" Grid.Column="0" Text="Save Morph Targets in Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveMorphTargets, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" IsChecked="{Binding SaveMorphTargets, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="13" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" /> <TextBlock Grid.Row="12" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" />
<CheckBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveSkeletonAsMesh, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" IsChecked="{Binding SaveSkeletonAsMesh, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 0"/> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 0"/>
<Separator Grid.Row="14" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" /> <Separator Grid.Row="13" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="15" Grid.Column="0" Text="Material Format" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="14" Grid.Column="0" Text="Material Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MaterialExportFormats}" SelectedItem="{Binding SettingsView.SelectedMaterialExportFormat, Mode=TwoWay}" <ComboBox Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MaterialExportFormats}" SelectedItem="{Binding SettingsView.SelectedMaterialExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
@ -416,8 +414,8 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<TextBlock Grid.Row="16" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="15" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}" <ComboBox Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>

View File

@ -51,6 +51,9 @@ public partial class SettingsView
case SettingsOut.ReloadMappings: case SettingsOut.ReloadMappings:
await _applicationView.CUE4Parse.InitMappings(); await _applicationView.CUE4Parse.InitMappings();
break; break;
case SettingsOut.CheckForUpdates:
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
break;
} }
} }

View File

@ -134,7 +134,7 @@ public class Animation : IDisposable
if (ImGui.MenuItem("Save")) if (ImGui.MenuItem("Save"))
{ {
s.WindowShouldFreeze(true); s.WindowShouldFreeze(true);
saver.Value = new Exporter(_export, UserSettings.Default.ExportOptions).TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out saver.Label, out saver.Path); saver.Value = new Exporter(_export).TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out saver.Label, out saver.Path);
s.WindowShouldFreeze(false); s.WindowShouldFreeze(false);
} }
ImGui.Separator(); ImGui.Separator();

View File

@ -20,7 +20,6 @@ public class TimeTracker : IDisposable
public bool IsActive; public bool IsActive;
public float ElapsedTime; public float ElapsedTime;
public float MaxElapsedTime; public float MaxElapsedTime;
public int TimeMultiplier;
public TimeTracker() public TimeTracker()
{ {
@ -30,7 +29,7 @@ public class TimeTracker : IDisposable
public void Update(float deltaSeconds) public void Update(float deltaSeconds)
{ {
if (IsPaused || IsActive) return; if (IsPaused || IsActive) return;
ElapsedTime += deltaSeconds * TimeMultiplier; ElapsedTime += deltaSeconds;
if (ElapsedTime >= MaxElapsedTime) Reset(false); if (ElapsedTime >= MaxElapsedTime) Reset(false);
} }
@ -48,11 +47,7 @@ public class TimeTracker : IDisposable
{ {
IsPaused = false; IsPaused = false;
ElapsedTime = 0.0f; ElapsedTime = 0.0f;
if (doMet) if (doMet) MaxElapsedTime = 0.01f;
{
MaxElapsedTime = 0.01f;
TimeMultiplier = 1;
}
} }
public void Dispose() public void Dispose()

View File

@ -26,8 +26,7 @@ public class VertexArrayObject<TVertexType, TIndexType> : IDisposable where TVer
switch (type) switch (type)
{ {
case VertexAttribPointerType.Int: case VertexAttribPointerType.Int:
case VertexAttribPointerType.UnsignedInt: GL.VertexAttribIPointer(index, count, VertexAttribIntegerType.Int, vertexSize * _sizeOfVertex, (IntPtr) (offset * _sizeOfVertex));
GL.VertexAttribIPointer(index, count, (VertexAttribIntegerType) type, vertexSize * _sizeOfVertex, offset * _sizeOfVertex);
break; break;
default: default:
GL.VertexAttribPointer(index, count, type, false, vertexSize * _sizeOfVertex, offset * _sizeOfVertex); GL.VertexAttribPointer(index, count, type, false, vertexSize * _sizeOfVertex, offset * _sizeOfVertex);

View File

@ -1,228 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.PhysicsEngine;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper.Models;
public class Collision : IDisposable
{
private const int Slices = 16;
private const int Stacks = 8;
private const float SectorStep = 2 * MathF.PI / Slices;
private const float StackStep = MathF.PI / Stacks;
private readonly int[] _indexData;
private readonly FVector[] _vertexData;
private readonly Transform _transform;
public readonly string LoweredBoneName;
private int _handle;
private BufferObject<int> _ebo { get; set; }
private BufferObject<FVector> _vbo { get; set; }
private VertexArrayObject<FVector, int> _vao { get; set; }
private Collision()
{
_indexData = [];
_vertexData = [];
_transform = Transform.Identity;
}
private Collision(FName boneName) : this()
{
LoweredBoneName = boneName.Text.ToLower();
}
public Collision(FKConvexElem convexElems, FName boneName = default) : this(boneName)
{
_indexData = convexElems.IndexData;
_vertexData = convexElems.VertexData;
_transform = new Transform
{
Position = convexElems.Transform.Translation * Constants.SCALE_DOWN_RATIO,
Rotation = convexElems.Transform.Rotation,
Scale = convexElems.Transform.Scale3D
};
}
public Collision(FKSphereElem sphereElem, FName boneName = default) : this(boneName)
{
_vertexData = new FVector[(Slices + 1) * (Stacks + 1)];
for (var i = 0; i <= Stacks; i++)
{
var stackAngle = MathF.PI / 2 - i * StackStep;
var xy = MathF.Cos(stackAngle);
var z = MathF.Sin(stackAngle);
for (var j = 0; j <= Slices; j++)
{
var sectorAngle = j * SectorStep;
var x = xy * MathF.Cos(sectorAngle);
var y = xy * MathF.Sin(sectorAngle);
_vertexData[i * (Slices + 1) + j] = new FVector(x, y, z);
}
}
_indexData = new int[Stacks * Slices * 6];
for (var i = 0; i < Stacks; i++)
{
for (var j = 0; j < Slices; j++)
{
var a = i * (Slices + 1) + j;
var b = a + Slices + 1;
_indexData[(i * Slices + j) * 6 + 0] = a;
_indexData[(i * Slices + j) * 6 + 1] = b;
_indexData[(i * Slices + j) * 6 + 2] = a + 1;
_indexData[(i * Slices + j) * 6 + 3] = b;
_indexData[(i * Slices + j) * 6 + 4] = b + 1;
_indexData[(i * Slices + j) * 6 + 5] = a + 1;
}
}
_transform = new Transform
{
Position = sphereElem.Center * Constants.SCALE_DOWN_RATIO,
Scale = new FVector(sphereElem.Radius)
};
}
public Collision(FKBoxElem boxElem, FName boneName = default) : this(boneName)
{
_vertexData =
[
new FVector(-boxElem.X, -boxElem.Y, -boxElem.Z),
new FVector(boxElem.X, -boxElem.Y, -boxElem.Z),
new FVector(boxElem.X, boxElem.Y, -boxElem.Z),
new FVector(-boxElem.X, boxElem.Y, -boxElem.Z),
new FVector(-boxElem.X, -boxElem.Y, boxElem.Z),
new FVector(boxElem.X, -boxElem.Y, boxElem.Z),
new FVector(boxElem.X, boxElem.Y, boxElem.Z),
new FVector(-boxElem.X, boxElem.Y, boxElem.Z)
];
_indexData =
[
0, 1, 2, 2, 3, 0,
1, 5, 6, 6, 2, 1,
5, 4, 7, 7, 6, 5,
4, 0, 3, 3, 7, 4,
3, 2, 6, 6, 7, 3,
4, 5, 1, 1, 0, 4
];
_transform = new Transform
{
Position = boxElem.Center * Constants.SCALE_DOWN_RATIO,
Rotation = boxElem.Rotation.Quaternion(),
Scale = new FVector(.5f)
};
}
public Collision(FKSphylElem sphylElem, FName boneName = default)
: this(sphylElem.Length, [sphylElem.Radius, sphylElem.Radius], sphylElem.Center, sphylElem.Rotation, boneName) {}
public Collision(FKTaperedCapsuleElem taperedCapsuleElem, FName boneName = default)
: this(taperedCapsuleElem.Length, [taperedCapsuleElem.Radius1, taperedCapsuleElem.Radius0], taperedCapsuleElem.Center, taperedCapsuleElem.Rotation, boneName) {}
private Collision(float length, float[] radius, FVector center = default, FRotator rotator = default, FName boneName = default) : this(boneName)
{
int vLength = 0;
int half = Slices / 2;
int k2 = (Slices + 1) * (Stacks + 1);
_vertexData = new FVector[k2 + Slices + 1];
for(int i = 0; i < 2; ++i)
{
float h = -length / 2.0f + i * length;
int start = i == 0 ? Stacks / 2 : 0;
int end = i == 0 ? Stacks : Stacks / 2;
for(int j = start; j <= end; ++j)
{
var stackAngle = MathF.PI / 2 - j * StackStep;
var xy = radius[i] * MathF.Cos(stackAngle);
var z = radius[i] * MathF.Sin(stackAngle) + h;
for(int k = 0; k <= Slices; ++k)
{
var sectorAngle = k * SectorStep;
var x = xy * MathF.Cos(sectorAngle);
var y = xy * MathF.Sin(sectorAngle);
_vertexData[vLength++] = new FVector(x, y, z);
}
}
}
var indices = new List<int>();
AddIndicesForSlices(indices, ref k2);
indices.AddRange(new[] {0, k2, k2, k2 - half, half, half});
AddIndicesForStacks(indices);
half /= 2;
indices.AddRange(new[] {half, k2 - half * 3, k2 - half * 3, half * 3, k2 - half, k2 - half});
AddIndicesForStacks(indices, Stacks / 2);
_indexData = indices.ToArray();
_transform = new Transform
{
Position = center * Constants.SCALE_DOWN_RATIO,
Rotation = rotator.Quaternion()
};
}
private void AddIndicesForSlices(List<int> indices, ref int k2)
{
for(int k1 = 0; k1 < Slices; ++k1, ++k2)
{
indices.AddRange(new[] {k1, k1 + 1, k1 + 1, k2, k2 + 1, k2 + 1});
}
}
private void AddIndicesForStacks(List<int> indices, int start = 0)
{
for (int k1 = start; k1 < Stacks * Slices + Slices; k1 += Slices + 1)
{
if (k1 == Stacks / 2 * (Slices + 1) + start) continue;
indices.AddRange(new[] {k1, k1 + Slices + 1, k1 + Slices + 1, k1 + Slices / 2, k1 + Slices / 2 + Slices + 1, k1 + Slices / 2 + Slices + 1});
}
}
public void Setup()
{
_handle = GL.CreateProgram();
_ebo = new BufferObject<int>(_indexData, BufferTarget.ElementArrayBuffer);
_vbo = new BufferObject<FVector>(_vertexData, BufferTarget.ArrayBuffer);
_vao = new VertexArrayObject<FVector, int>(_vbo, _ebo);
_vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 1, 0);
_vao.Unbind();
}
public void Render(Shader shader, Matrix4x4 boneMatrix)
{
shader.SetUniform("uCollisionMatrix", _transform.Matrix * boneMatrix);
_vao.Bind();
if (_indexData.Length > 0)
{
GL.DrawElements(PrimitiveType.Triangles, _ebo.Size, DrawElementsType.UnsignedInt, 0);
}
else
{
GL.DrawArrays(PrimitiveType.Points, 0, _vbo.Size);
}
_vao.Unbind();
}
public void Dispose()
{
_ebo?.Dispose();
_vbo?.Dispose();
_vao?.Dispose();
GL.DeleteProgram(_handle);
}
}

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports.Animation; using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Objects.Core.Math; using CUE4Parse.UE4.Objects.Core.Math;
using OpenTK.Graphics.OpenGL4; using OpenTK.Graphics.OpenGL4;
@ -45,9 +44,9 @@ public class Morph : IDisposable
Vertices[baseIndex + count++] = vertices[i + 1] + positionDelta.X * Constants.SCALE_DOWN_RATIO; Vertices[baseIndex + count++] = vertices[i + 1] + positionDelta.X * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vertices[i + 2] + positionDelta.Z * Constants.SCALE_DOWN_RATIO; Vertices[baseIndex + count++] = vertices[i + 2] + positionDelta.Z * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vertices[i + 3] + positionDelta.Y * Constants.SCALE_DOWN_RATIO; Vertices[baseIndex + count++] = vertices[i + 3] + positionDelta.Y * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vertices[i + 7] + tangentDelta.X; Vertices[baseIndex + count++] = vertices[i + 7] + tangentDelta.X * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vertices[i + 8] + tangentDelta.Z; Vertices[baseIndex + count++] = vertices[i + 8] + tangentDelta.Z * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vertices[i + 9] + tangentDelta.Y; Vertices[baseIndex + count++] = vertices[i + 9] + tangentDelta.Y * Constants.SCALE_DOWN_RATIO;
} }
else else
{ {
@ -61,27 +60,6 @@ public class Morph : IDisposable
} }
} }
public Morph(float[] vertices, Dictionary<uint, int> dict, UMorphTarget morphTarget)
{
Name = morphTarget.Name;
Vertices = new float[vertices.Length];
Array.Copy(vertices, Vertices, vertices.Length);
foreach (var vert in morphTarget.MorphLODModels[0].Vertices)
{
var count = 0;
if (dict.TryGetValue(vert.SourceIdx, out var baseIndex))
{
Vertices[baseIndex + count++] += vert.PositionDelta.X * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] += vert.PositionDelta.Z * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] += vert.PositionDelta.Y * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] += vert.TangentZDelta.X;
Vertices[baseIndex + count++] += vert.TangentZDelta.Z;
Vertices[baseIndex + count++] += vert.TangentZDelta.Y;
}
}
}
public void Setup() public void Setup()
{ {
_handle = GL.CreateProgram(); _handle = GL.CreateProgram();

View File

@ -16,11 +16,12 @@ public class Section
public Section(int index, int facesCount, int firstFaceIndex) public Section(int index, int facesCount, int firstFaceIndex)
{ {
MaterialIndex = Math.Max(0, index); MaterialIndex = index;
FacesCount = facesCount; FacesCount = facesCount;
FirstFaceIndex = firstFaceIndex; FirstFaceIndex = firstFaceIndex;
FirstFaceIndexPtr = new IntPtr(FirstFaceIndex * sizeof(uint)); FirstFaceIndexPtr = new IntPtr(FirstFaceIndex * sizeof(uint));
Color = Constants.COLOR_PALETTE[MaterialIndex % Constants.PALETTE_LENGTH]; Color = Constants.COLOR_PALETTE[index % Constants.PALETTE_LENGTH];
Show = true;
} }
public void SetupMaterial(Material material) public void SetupMaterial(Material material)

View File

@ -1,11 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics;
using CUE4Parse_Conversion.Meshes.PSK; using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse.UE4.Assets.Exports.Animation; using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Objects.Core.Math; using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.PhysicsEngine;
using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.UE4.Objects.UObject;
using FModel.Views.Snooper.Animations; using FModel.Views.Snooper.Animations;
using FModel.Views.Snooper.Buffers; using FModel.Views.Snooper.Buffers;
@ -46,73 +44,23 @@ public class SkeletalModel : UModel
Sockets.Add(new Socket(socket)); Sockets.Add(new Socket(socket));
} }
if (export.PhysicsAsset.TryLoad(out UPhysicsAsset physicsAsset)) Morphs = new List<Morph>();
for (var i = 0; i < export.MorphTargets.Length; i++)
{ {
foreach (var skeletalBodySetup in physicsAsset.SkeletalBodySetups) if (!export.MorphTargets[i].TryLoad(out UMorphTarget morphTarget) ||
{ morphTarget.MorphLODModels.Length < 1 || morphTarget.MorphLODModels[0].Vertices.Length < 1)
if (!skeletalBodySetup.TryLoad(out USkeletalBodySetup bodySetup) || bodySetup.AggGeom == null) continue;
foreach (var convexElem in bodySetup.AggGeom.ConvexElems)
{
Collisions.Add(new Collision(convexElem, bodySetup.BoneName));
}
foreach (var sphereElem in bodySetup.AggGeom.SphereElems)
{
Collisions.Add(new Collision(sphereElem, bodySetup.BoneName));
}
foreach (var boxElem in bodySetup.AggGeom.BoxElems)
{
Collisions.Add(new Collision(boxElem, bodySetup.BoneName));
}
foreach (var sphylElem in bodySetup.AggGeom.SphylElems)
{
Collisions.Add(new Collision(sphylElem, bodySetup.BoneName));
}
foreach (var taperedCapsuleElem in bodySetup.AggGeom.TaperedCapsuleElems)
{
Collisions.Add(new Collision(taperedCapsuleElem, bodySetup.BoneName));
}
}
}
Morphs = [];
if (export.MorphTargets.Length == 0) return;
export.PopulateMorphTargetVerticesData();
var verticesCount = Vertices.Length / VertexSize;
var cachedVertices = new float[verticesCount * Morph.VertexSize];
var vertexLookup = new Dictionary<uint, int>(verticesCount);
for (int i = 0; i < Vertices.Length; i += VertexSize)
{
var count = 0;
var baseIndex = i / VertexSize * Morph.VertexSize;
vertexLookup[(uint) Vertices[i]] = baseIndex;
{
cachedVertices[baseIndex + count++] = Vertices[i + 1];
cachedVertices[baseIndex + count++] = Vertices[i + 2];
cachedVertices[baseIndex + count++] = Vertices[i + 3];
cachedVertices[baseIndex + count++] = Vertices[i + 7];
cachedVertices[baseIndex + count++] = Vertices[i + 8];
cachedVertices[baseIndex + count++] = Vertices[i + 9];
}
}
foreach (var morph in export.MorphTargets)
{
if (!morph.TryLoad(out UMorphTarget morphTarget) || morphTarget.MorphLODModels.Length < 1 ||
morphTarget.MorphLODModels[0].Vertices.Length < 1)
continue; continue;
Morphs.Add(new Morph(cachedVertices, vertexLookup, morphTarget)); Morphs.Add(new Morph(Vertices, VertexSize, morphTarget));
} }
} }
public SkeletalModel(USkeleton export, FBox box) : base(export) public SkeletalModel(USkeleton export, FBox box) : base(export)
{ {
Indices = []; Indices = Array.Empty<uint>();
Materials = []; Materials = Array.Empty<Material>();
Vertices = []; Vertices = Array.Empty<float>();
Sections = []; Sections = Array.Empty<Section>();
AddInstance(Transform.Identity); AddInstance(Transform.Identity);
Box = box * Constants.SCALE_DOWN_RATIO; Box = box * Constants.SCALE_DOWN_RATIO;
@ -147,26 +95,6 @@ public class SkeletalModel : UModel
Vao.Unbind(); Vao.Unbind();
} }
public override void RenderCollision(Shader shader)
{
base.RenderCollision(shader);
GL.Disable(EnableCap.DepthTest);
GL.Disable(EnableCap.CullFace);
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
foreach (var collision in Collisions)
{
var boneMatrix = Matrix4x4.Identity;
if (Skeleton.BonesByLoweredName.TryGetValue(collision.LoweredBoneName, out var bone))
boneMatrix = Skeleton.GetBoneMatrix(bone);
collision.Render(shader, boneMatrix);
}
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
GL.Enable(EnableCap.CullFace);
GL.Enable(EnableCap.DepthTest);
}
public void Render(Shader shader) public void Render(Shader shader)
{ {
shader.SetUniform("uMorphTime", MorphTime); shader.SetUniform("uMorphTime", MorphTime);

View File

@ -1,13 +1,7 @@
using System; using CUE4Parse_Conversion.Meshes.PSK;
using System.Numerics;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.StaticMesh; using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.PhysicsEngine;
using FModel.Views.Snooper.Shading; using FModel.Views.Snooper.Shading;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper.Models; namespace FModel.Views.Snooper.Models;
@ -55,86 +49,9 @@ public class StaticModel : UModel
Box = staticMesh.BoundingBox * 1.5f * Constants.SCALE_DOWN_RATIO; Box = staticMesh.BoundingBox * 1.5f * Constants.SCALE_DOWN_RATIO;
} }
public StaticModel(UPaperSprite paperSprite, UTexture2D texture) : base(paperSprite)
{
Indices = new uint[paperSprite.BakedRenderData.Length];
for (int i = 0; i < Indices.Length; i++)
{
Indices[i] = (uint) i;
}
Vertices = new float[paperSprite.BakedRenderData.Length * VertexSize];
for (int i = 0; i < paperSprite.BakedRenderData.Length; i++)
{
var count = 0;
var baseIndex = i * VertexSize;
var vert = paperSprite.BakedRenderData[i];
var u = vert.Z;
var v = vert.W;
Vertices[baseIndex + count++] = i;
Vertices[baseIndex + count++] = vert.X * paperSprite.PixelsPerUnrealUnit * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vert.Y * paperSprite.PixelsPerUnrealUnit * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = 0;
Vertices[baseIndex + count++] = 0;
Vertices[baseIndex + count++] = 0;
Vertices[baseIndex + count++] = 0;
Vertices[baseIndex + count++] = 0;
Vertices[baseIndex + count++] = 0;
Vertices[baseIndex + count++] = 0;
Vertices[baseIndex + count++] = u;
Vertices[baseIndex + count++] = v;
Vertices[baseIndex + count++] = .5f;
}
Materials = new Material[1];
if (paperSprite.DefaultMaterial?.TryLoad(out UMaterialInstance unrealMaterial) ?? false)
{
Materials[0] = new Material(unrealMaterial);
}
else
{
Materials[0] = new Material();
}
Materials[0].Parameters.Textures[CMaterialParams2.FallbackDiffuse] = texture;
Materials[0].IsUsed = true;
Sections = new Section[1];
Sections[0] = new Section(0, Indices.Length, 0);
AddInstance(Transform.Identity);
var backward = new FVector(0, Math.Max(paperSprite.BakedSourceDimension.X, paperSprite.BakedSourceDimension.Y) / 2, 0);
Box = new FBox(-backward, backward) * Constants.SCALE_DOWN_RATIO;
}
public StaticModel(UStaticMesh export, CStaticMesh staticMesh, Transform transform = null) public StaticModel(UStaticMesh export, CStaticMesh staticMesh, Transform transform = null)
: base(export, staticMesh.LODs[LodLevel], export.Materials, staticMesh.LODs[LodLevel].Verts, staticMesh.LODs.Count, transform) : base(export, staticMesh.LODs[LodLevel], export.Materials, staticMesh.LODs[LodLevel].Verts, staticMesh.LODs.Count, transform)
{ {
if (export.BodySetup.TryLoad(out UBodySetup bodySetup) && bodySetup.AggGeom != null)
{
foreach (var convexElem in bodySetup.AggGeom.ConvexElems)
{
Collisions.Add(new Collision(convexElem));
}
foreach (var sphereElem in bodySetup.AggGeom.SphereElems)
{
Collisions.Add(new Collision(sphereElem));
}
foreach (var boxElem in bodySetup.AggGeom.BoxElems)
{
Collisions.Add(new Collision(boxElem));
}
foreach (var sphylElem in bodySetup.AggGeom.SphylElems)
{
Collisions.Add(new Collision(sphylElem));
}
foreach (var taperedCapsuleElem in bodySetup.AggGeom.TaperedCapsuleElems)
{
Collisions.Add(new Collision(taperedCapsuleElem));
}
}
Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO; Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
for (int i = 0; i < export.Sockets.Length; i++) for (int i = 0; i < export.Sockets.Length; i++)
{ {
@ -142,18 +59,4 @@ public class StaticModel : UModel
Sockets.Add(new Socket(socket)); Sockets.Add(new Socket(socket));
} }
} }
public override void RenderCollision(Shader shader)
{
base.RenderCollision(shader);
GL.Disable(EnableCap.CullFace);
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
foreach (var collision in Collisions)
{
collision.Render(shader, Matrix4x4.Identity);
}
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
GL.Enable(EnableCap.CullFace);
}
} }

View File

@ -20,7 +20,6 @@ namespace FModel.Views.Snooper.Models;
public class VertexAttribute public class VertexAttribute
{ {
public int Size; public int Size;
public VertexAttribPointerType Type;
public bool Enabled; public bool Enabled;
} }
@ -29,18 +28,18 @@ public abstract class UModel : IRenderableModel
protected const int LodLevel = 0; protected const int LodLevel = 0;
private readonly UObject _export; private readonly UObject _export;
private readonly List<VertexAttribute> _vertexAttributes = private readonly List<VertexAttribute> _vertexAttributes = new()
[ {
new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Int, Enabled = false }, // VertexIndex new VertexAttribute { Size = 1, Enabled = false }, // VertexIndex
new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = true }, // Position new VertexAttribute { Size = 3, Enabled = true }, // Position
new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Normal new VertexAttribute { Size = 3, Enabled = false }, // Normal
new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Tangent new VertexAttribute { Size = 3, Enabled = false }, // Tangent
new VertexAttribute { Size = 2, Type = VertexAttribPointerType.Float, Enabled = false }, // UV new VertexAttribute { Size = 2, Enabled = false }, // UV
new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // TextureLayer new VertexAttribute { Size = 1, Enabled = false }, // TextureLayer
new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // Colors new VertexAttribute { Size = 4, Enabled = false }, // Colors
new VertexAttribute { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false }, // BoneIds new VertexAttribute { Size = 4, Enabled = false }, // BoneIds
new VertexAttribute { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false } // BoneWeights new VertexAttribute { Size = 4, Enabled = false } // BoneWeights
]; };
public int Handle { get; set; } public int Handle { get; set; }
public BufferObject<uint> Ebo { get; set; } public BufferObject<uint> Ebo { get; set; }
@ -60,7 +59,6 @@ public abstract class UModel : IRenderableModel
public FBox Box; public FBox Box;
public readonly List<Socket> Sockets; public readonly List<Socket> Sockets;
public readonly List<Collision> Collisions;
public Material[] Materials; public Material[] Materials;
public bool IsTwoSided; public bool IsTwoSided;
public bool IsProp; public bool IsProp;
@ -68,14 +66,12 @@ public abstract class UModel : IRenderableModel
public int VertexSize => _vertexAttributes.Where(x => x.Enabled).Sum(x => x.Size); public int VertexSize => _vertexAttributes.Where(x => x.Enabled).Sum(x => x.Size);
public bool HasVertexColors => _vertexAttributes[(int) EAttribute.Colors].Enabled; public bool HasVertexColors => _vertexAttributes[(int) EAttribute.Colors].Enabled;
public bool HasSockets => Sockets.Count > 0; public bool HasSockets => Sockets.Count > 0;
public bool HasCollisions => Collisions.Count > 0;
public int TransformsCount => Transforms.Count; public int TransformsCount => Transforms.Count;
public bool IsSetup { get; set; } public bool IsSetup { get; set; }
public bool IsVisible { get; set; } public bool IsVisible { get; set; }
public bool IsSelected { get; set; } public bool IsSelected { get; set; }
public bool ShowWireframe { get; set; } public bool ShowWireframe { get; set; }
public bool ShowCollisions { get; set; }
public int SelectedInstance; public int SelectedInstance;
protected UModel() protected UModel()
@ -85,7 +81,6 @@ public abstract class UModel : IRenderableModel
Box = new FBox(new FVector(-2f), new FVector(2f)); Box = new FBox(new FVector(-2f), new FVector(2f));
Sockets = new List<Socket>(); Sockets = new List<Socket>();
Collisions = new List<Collision>();
Transforms = new List<Transform>(); Transforms = new List<Transform>();
} }
@ -99,7 +94,6 @@ public abstract class UModel : IRenderableModel
Box = new FBox(new FVector(-2f), new FVector(2f)); Box = new FBox(new FVector(-2f), new FVector(2f));
Sockets = new List<Socket>(); Sockets = new List<Socket>();
Collisions = new List<Collision>();
Transforms = new List<Transform>(); Transforms = new List<Transform>();
Attachments = new Attachment(Name); Attachments = new Attachment(Name);
@ -151,24 +145,28 @@ public abstract class UModel : IRenderableModel
Vertices[baseIndex + count++] = vert.Tangent.Y; Vertices[baseIndex + count++] = vert.Tangent.Y;
Vertices[baseIndex + count++] = vert.UV.U; Vertices[baseIndex + count++] = vert.UV.U;
Vertices[baseIndex + count++] = vert.UV.V; Vertices[baseIndex + count++] = vert.UV.V;
Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUV.Value[0][i].U - 1 : .5f; Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUV.Value[0][i].U : .5f;
if (HasVertexColors) if (HasVertexColors)
{ {
Vertices[baseIndex + count++] = lod.VertexColors[i].ToPackedARGB(); var color = lod.VertexColors[i];
Vertices[baseIndex + count++] = color.R;
Vertices[baseIndex + count++] = color.G;
Vertices[baseIndex + count++] = color.B;
Vertices[baseIndex + count++] = color.A;
} }
if (vert is CSkelMeshVertex skelVert) if (vert is CSkelMeshVertex skelVert)
{ {
int max = skelVert.Influences.Count; var weightsHash = skelVert.UnpackWeights();
for (int j = 0; j < 8; j++) Vertices[baseIndex + count++] = skelVert.Bone[0];
{ Vertices[baseIndex + count++] = skelVert.Bone[1];
var boneID = j < max ? skelVert.Influences[j].Bone : (short) 0; Vertices[baseIndex + count++] = skelVert.Bone[2];
var weight = j < max ? skelVert.Influences[j].RawWeight : (byte) 0; Vertices[baseIndex + count++] = skelVert.Bone[3];
Vertices[baseIndex + count++] = weightsHash[0];
// Pack bone ID and weight Vertices[baseIndex + count++] = weightsHash[1];
Vertices[baseIndex + count++] = (boneID << 16) | weight; Vertices[baseIndex + count++] = weightsHash[2];
} Vertices[baseIndex + count++] = weightsHash[3];
} }
} }
@ -199,9 +197,8 @@ public abstract class UModel : IRenderableModel
if (i != 5 || !broken) if (i != 5 || !broken)
{ {
Vao.VertexAttributePointer((uint) i, attribute.Size, attribute.Type, VertexSize, offset); Vao.VertexAttributePointer((uint) i, attribute.Size, i == 0 ? VertexAttribPointerType.Int : VertexAttribPointerType.Float, VertexSize, offset);
} }
offset += attribute.Size; offset += attribute.Size;
} }
@ -214,11 +211,6 @@ public abstract class UModel : IRenderableModel
Materials[i].Setup(options, broken ? 1 : UvCount); Materials[i].Setup(options, broken ? 1 : UvCount);
} }
foreach (var collision in Collisions)
{
collision.Setup();
}
if (options.Models.Count == 1 && Sections.All(x => !x.Show)) if (options.Models.Count == 1 && Sections.All(x => !x.Show))
{ {
IsVisible = true; IsVisible = true;
@ -250,7 +242,6 @@ public abstract class UModel : IRenderableModel
if (!outline) if (!outline)
{ {
shader.SetUniform("uUvCount", UvCount); shader.SetUniform("uUvCount", UvCount);
shader.SetUniform("uOpacity", ShowCollisions && IsSelected ? 0.75f : 1f);
shader.SetUniform("uHasVertexColors", HasVertexColors); shader.SetUniform("uHasVertexColors", HasVertexColors);
} }
@ -305,12 +296,6 @@ public abstract class UModel : IRenderableModel
if (IsTwoSided) GL.Enable(EnableCap.CullFace); if (IsTwoSided) GL.Enable(EnableCap.CullFace);
} }
public virtual void RenderCollision(Shader shader)
{
shader.SetUniform("uInstanceMatrix", GetTransform().Matrix);
shader.SetUniform("uScaleDown", Constants.SCALE_DOWN_RATIO);
}
public void Update(Options options) public void Update(Options options)
{ {
MatrixVbo.Bind(); MatrixVbo.Bind();
@ -372,7 +357,18 @@ public abstract class UModel : IRenderableModel
public bool Save(out string label, out string savedFilePath) public bool Save(out string label, out string savedFilePath)
{ {
var toSave = new Exporter(_export, UserSettings.Default.ExportOptions); var exportOptions = new ExporterOptions
{
LodFormat = UserSettings.Default.LodExportFormat,
MeshFormat = UserSettings.Default.MeshExportFormat,
MaterialFormat = UserSettings.Default.MaterialExportFormat,
TextureFormat = UserSettings.Default.TextureExportFormat,
SocketFormat = UserSettings.Default.SocketExportFormat,
Platform = UserSettings.Default.CurrentDir.TexturePlatform,
ExportMorphTargets = UserSettings.Default.SaveMorphTargets,
ExportMaterials = UserSettings.Default.SaveEmbeddedMaterials
};
var toSave = new Exporter(_export, exportOptions);
return toSave.TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out label, out savedFilePath); return toSave.TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out label, out savedFilePath);
} }
@ -387,11 +383,6 @@ public abstract class UModel : IRenderableModel
socket?.Dispose(); socket?.Dispose();
} }
Sockets.Clear(); Sockets.Clear();
foreach (var collision in Collisions)
{
collision?.Dispose();
}
Collisions.Clear();
GL.DeleteProgram(Handle); GL.DeleteProgram(Handle);
} }

View File

@ -42,12 +42,6 @@ public class Options
Icons = new Dictionary<string, Texture> Icons = new Dictionary<string, Texture>
{ {
["material"] = new ("materialicon"), ["material"] = new ("materialicon"),
["square"] = new ("square"),
["square_off"] = new ("square_off"),
["cube"] = new ("cube"),
["cube_off"] = new ("cube_off"),
["light"] = new ("light"),
["light_off"] = new ("light_off"),
["noimage"] = new ("T_Placeholder_Item_Image"), ["noimage"] = new ("T_Placeholder_Item_Image"),
["checker"] = new ("checker"), ["checker"] = new ("checker"),
["pointlight"] = new ("pointlight"), ["pointlight"] = new ("pointlight"),

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Threading; using System.Threading;
@ -9,7 +8,6 @@ using CUE4Parse_Conversion.Meshes;
using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Animation; using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Assets.Exports.Component.StaticMesh; using CUE4Parse.UE4.Assets.Exports.Component.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.GeometryCollection;
using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Assets.Exports.StaticMesh; using CUE4Parse.UE4.Assets.Exports.StaticMesh;
@ -48,7 +46,6 @@ public class Renderer : IDisposable
private Shader _outline; private Shader _outline;
private Shader _light; private Shader _light;
private Shader _bone; private Shader _bone;
private Shader _collision;
private bool _saveCameraMode; private bool _saveCameraMode;
public bool ShowSkybox; public bool ShowSkybox;
@ -80,8 +77,8 @@ public class Renderer : IDisposable
public void Load(CancellationToken cancellationToken, UObject export) public void Load(CancellationToken cancellationToken, UObject export)
{ {
ShowLights = false; ShowLights = false;
Color = VertexColor.Default; _saveCameraMode = export is not UWorld;
_saveCameraMode = export is not UWorld and not UBlueprintGeneratedClass; CameraOp.Mode = _saveCameraMode ? UserSettings.Default.CameraMode : Camera.WorldMode.FlyCam;
switch (export) switch (export)
{ {
case UStaticMesh st: case UStaticMesh st:
@ -99,15 +96,7 @@ public class Renderer : IDisposable
case UWorld wd: case UWorld wd:
LoadWorld(cancellationToken, wd, Transform.Identity); LoadWorld(cancellationToken, wd, Transform.Identity);
break; break;
case UBlueprintGeneratedClass bp:
LoadJunoWorld(cancellationToken, bp, Transform.Identity);
Color = VertexColor.Colors;
break;
case UPaperSprite ps:
LoadPaperSprite(ps);
break;
} }
CameraOp.Mode = _saveCameraMode ? UserSettings.Default.CameraMode : Camera.WorldMode.FlyCam;
SetupCamera(); SetupCamera();
} }
@ -238,7 +227,6 @@ public class Renderer : IDisposable
_outline = new Shader("outline"); _outline = new Shader("outline");
_light = new Shader("light"); _light = new Shader("light");
_bone = new Shader("bone"); _bone = new Shader("bone");
_collision = new Shader("collision", "bone");
Picking.Setup(); Picking.Setup();
Options.SetupModelsAndLights(); Options.SetupModelsAndLights();
@ -284,11 +272,6 @@ public class Renderer : IDisposable
_bone.Render(viewMatrix, projMatrix); _bone.Render(viewMatrix, projMatrix);
skeletalModel.RenderBones(_bone); skeletalModel.RenderBones(_bone);
} }
else if (selected.ShowCollisions)
{
_collision.Render(viewMatrix, projMatrix);
selected.RenderCollision(_collision);
}
_outline.Render(viewMatrix, CameraOp.Position, projMatrix); _outline.Render(viewMatrix, CameraOp.Position, projMatrix);
selected.Render(_outline, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null, true); selected.Render(_outline, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null, true);
@ -400,23 +383,6 @@ public class Renderer : IDisposable
Options.SelectModel(guid); Options.SelectModel(guid);
} }
private void LoadPaperSprite(UPaperSprite original)
{
if (!(original.BakedSourceTexture?.TryLoad(out UTexture2D texture) ?? false))
return;
var guid = texture.LightingGuid;
if (Options.TryGetModel(guid, out var model))
{
model.AddInstance(Transform.Identity);
Application.Current.Dispatcher.Invoke(() => model.SetupInstances());
return;
}
Options.Models[guid] = new StaticModel(original, texture);
Options.SelectModel(guid);
}
private void SetupCamera() private void SetupCamera()
{ {
if (Options.TryGetModel(out var model)) if (Options.TryGetModel(out var model))
@ -456,49 +422,6 @@ public class Renderer : IDisposable
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}"); Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}");
} }
private void LoadJunoWorld(CancellationToken cancellationToken, UBlueprintGeneratedClass original, Transform transform)
{
CameraOp.Setup(new FBox(FVector.ZeroVector, new FVector(0, 10, 10)));
var length = 0;
FPackageIndex[] allNodes = [];
IPropertyHolder[] records = [];
if (original.TryGetValue(out FPackageIndex simpleConstructionScript, "SimpleConstructionScript") &&
simpleConstructionScript.TryLoad(out var scs) && scs.TryGetValue(out allNodes, "AllNodes"))
length = allNodes.Length;
else if (original.TryGetValue(out FPackageIndex inheritableComponentHandler, "InheritableComponentHandler") &&
inheritableComponentHandler.TryLoad(out var ich) && ich.TryGetValue(out records, "Records"))
length = records.Length;
for (var i = 0; i < length; i++)
{
cancellationToken.ThrowIfCancellationRequested();
IPropertyHolder actor;
if (allNodes is {Length: > 0} && allNodes[i].TryLoad(out UObject node))
{
actor = node;
}
else if (records is {Length: > 0})
{
actor = records[i];
}
else continue;
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {i}/{length}");
WorldMesh(actor, transform, true);
}
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}");
if (Options.Models.Count == 1)
{
var (guid, model) = Options.Models.First();
Options.SelectModel(guid);
CameraOp.Setup(model.Box);
_saveCameraMode = true;
}
}
private void WorldCamera(UObject actor) private void WorldCamera(UObject actor)
{ {
if (actor.ExportType != "LevelBounds" || !actor.TryGetValue(out FPackageIndex boxComponent, "BoxComponent") || if (actor.ExportType != "LevelBounds" || !actor.TryGetValue(out FPackageIndex boxComponent, "BoxComponent") ||
@ -529,79 +452,36 @@ public class Renderer : IDisposable
} }
} }
private void WorldMesh(IPropertyHolder actor, Transform transform, bool forceShow = false) private void WorldMesh(UObject actor, Transform transform)
{ {
if (actor.TryGetValue(out FPackageIndex[] instanceComponents, "InstanceComponents")) if (!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "StaticMesh", "Mesh", "LightMesh") ||
{ !staticMeshComponent.TryLoad(out UStaticMeshComponent staticMeshComp) ||
foreach (var component in instanceComponents) !staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) || m.Materials.Length < 1)
{ return;
if (!component.TryLoad(out UInstancedStaticMeshComponent staticMeshComp) ||
!staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) || m.Materials.Length < 1)
continue;
if (staticMeshComp.PerInstanceSMData is { Length: > 0 })
{
var relation = CalculateTransform(staticMeshComp, transform);
foreach (var perInstanceData in staticMeshComp.PerInstanceSMData)
{
ProcessMesh(actor, staticMeshComp, m, new Transform
{
Relation = relation.Matrix,
Position = perInstanceData.TransformData.Translation * Constants.SCALE_DOWN_RATIO,
Rotation = perInstanceData.TransformData.Rotation,
Scale = perInstanceData.TransformData.Scale3D
});
}
}
else ProcessMesh(actor, staticMeshComp, m, CalculateTransform(staticMeshComp, transform));
}
}
else if (actor.TryGetValue(out FPackageIndex componentTemplate, "ComponentTemplate") &&
componentTemplate.TryLoad(out UObject compTemplate))
{
UGeometryCollection geometryCollection = null;
if (!compTemplate.TryGetValue(out UStaticMesh m, "StaticMesh") &&
compTemplate.TryGetValue(out FPackageIndex restCollection, "RestCollection") &&
restCollection.TryLoad(out geometryCollection) && geometryCollection.RootProxyData is { ProxyMeshes.Length: > 0 } rootProxyData)
{
rootProxyData.ProxyMeshes[0].TryLoad(out m);
}
if (m is { Materials.Length: > 0 })
{
OverrideJunoVertexColors(m, geometryCollection);
ProcessMesh(actor, compTemplate, m, CalculateTransform(compTemplate, transform), forceShow);
}
}
else if (actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "ComponentTemplate", "StaticMesh", "Mesh", "LightMesh") &&
staticMeshComponent.TryLoad(out UStaticMeshComponent staticMeshComp) &&
staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) && m.Materials.Length > 0)
{
ProcessMesh(actor, staticMeshComp, m, CalculateTransform(staticMeshComp, transform));
}
}
private void ProcessMesh(IPropertyHolder actor, UStaticMeshComponent staticMeshComp, UStaticMesh m, Transform transform)
{
OverrideVertexColors(staticMeshComp, m);
ProcessMesh(actor, staticMeshComp, m, transform, false);
}
private void ProcessMesh(IPropertyHolder actor, UObject staticMeshComp, UStaticMesh m, Transform transform, bool forceShow)
{
var guid = m.LightingGuid; var guid = m.LightingGuid;
var t = new Transform
{
Relation = transform.Matrix,
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion(),
Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector)
};
OverrideVertexColors(staticMeshComp, m);
if (Options.TryGetModel(guid, out var model)) if (Options.TryGetModel(guid, out var model))
{ {
model.AddInstance(transform); model.AddInstance(t);
} }
else if (m.TryConvert(out var mesh)) else if (m.TryConvert(out var mesh))
{ {
model = new StaticModel(m, mesh, transform); model = new StaticModel(m, mesh, t);
model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided)); model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided));
if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData")) if (actor.TryGetValue(out FPackageIndex baseMaterial, "BaseMaterial") &&
actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
{ {
var material = model.Materials.FirstOrDefault(); var material = model.Materials.FirstOrDefault(x => x.Name == baseMaterial.Name);
if (material is { IsUsed: true }) if (material is { IsUsed: true })
{ {
for (int j = 0; j < textureData.Length; j++) for (int j = 0; j < textureData.Length; j++)
@ -640,97 +520,23 @@ public class Renderer : IDisposable
for (var j = 0; j < overrideMaterials.Length && j < model.Sections.Length; j++) for (var j = 0; j < overrideMaterials.Length && j < model.Sections.Length; j++)
{ {
var matIndex = model.Sections[j].MaterialIndex; var matIndex = model.Sections[j].MaterialIndex;
if (matIndex < 0 || matIndex >= model.Materials.Length || matIndex >= overrideMaterials.Length || if (!(model.Materials[matIndex].IsUsed && overrideMaterials[matIndex].Load() is UMaterialInterface unrealMaterial)) continue;
overrideMaterials[matIndex].Load() is not UMaterialInterface unrealMaterial) continue;
model.Materials[matIndex].SwapMaterial(unrealMaterial); model.Materials[matIndex].SwapMaterial(unrealMaterial);
} }
} }
if (forceShow)
{
foreach (var section in model.Sections)
{
section.Show = true;
}
}
Options.Models[guid] = model; Options.Models[guid] = model;
} }
if (actor.TryGetValue(out FPackageIndex treasureLight, "PointLight", "TreasureLight") && if (actor.TryGetValue(out FPackageIndex treasureLight, "PointLight", "TreasureLight") &&
treasureLight.TryLoad(out var pl1) && pl1.Template.TryLoad(out var pl2)) treasureLight.TryLoad(out var pl1) && pl1.Template.TryLoad(out var pl2))
{ {
Options.Lights.Add(new PointLight(guid, Options.Icons["pointlight"], pl1, pl2, transform)); Options.Lights.Add(new PointLight(guid, Options.Icons["pointlight"], pl1, pl2, t));
} }
if (actor.TryGetValue(out FPackageIndex spotLight, "SpotLight") && if (actor.TryGetValue(out FPackageIndex spotLight, "SpotLight") &&
spotLight.TryLoad(out var sl1) && sl1.Template.TryLoad(out var sl2)) spotLight.TryLoad(out var sl1) && sl1.Template.TryLoad(out var sl2))
{ {
Options.Lights.Add(new SpotLight(guid, Options.Icons["spotlight"], sl1, sl2, transform)); Options.Lights.Add(new SpotLight(guid, Options.Icons["spotlight"], sl1, sl2, t));
}
}
private Transform CalculateTransform(IPropertyHolder staticMeshComp, Transform relation)
{
return new Transform
{
Relation = relation.Matrix,
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion(),
Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector)
};
}
private void OverrideJunoVertexColors(UStaticMesh staticMesh, UGeometryCollection geometryCollection = null)
{
if (staticMesh.RenderData is not { LODs.Length: > 0 } || staticMesh.RenderData.LODs[0].ColorVertexBuffer == null)
return;
var dico = new Dictionary<byte, FColor>();
if (geometryCollection?.Materials is not { Length: > 0 })
{
var distinctReds = new HashSet<byte>();
for (int i = 0; i < staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data.Length; i++)
{
ref var vertexColor = ref staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data[i];
var indexAsByte = vertexColor.R;
if (indexAsByte == 255) indexAsByte = vertexColor.A;
distinctReds.Add(indexAsByte);
}
foreach (var indexAsByte in distinctReds)
{
var path = string.Concat("/JunoAtomAssets/Materials/MI_LegoStandard_", indexAsByte, ".MI_LegoStandard_", indexAsByte);
if (!Utils.TryLoadObject(path, out UMaterialInterface unrealMaterial))
continue;
var parameters = new CMaterialParams2();
unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer);
if (!parameters.TryGetLinearColor(out var color, "Color"))
color = FLinearColor.Gray;
dico[indexAsByte] = color.ToFColor(true);
}
}
else foreach (var material in geometryCollection.Materials)
{
if (!material.TryLoad(out UMaterialInterface unrealMaterial)) continue;
var parameters = new CMaterialParams2();
unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer);
if (!byte.TryParse(material.Name.SubstringAfterLast("_"), out var indexAsByte))
indexAsByte = byte.MaxValue;
if (!parameters.TryGetLinearColor(out var color, "Color"))
color = FLinearColor.Gray;
dico[indexAsByte] = color.ToFColor(true);
}
for (int i = 0; i < staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data.Length; i++)
{
ref var vertexColor = ref staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data[i];
vertexColor = dico.TryGetValue(vertexColor.R, out var color) ? color : FColor.Gray;
} }
} }
@ -800,7 +606,6 @@ public class Renderer : IDisposable
_outline?.Dispose(); _outline?.Dispose();
_light?.Dispose(); _light?.Dispose();
_bone?.Dispose(); _bone?.Dispose();
_collision?.Dispose();
Picking?.Dispose(); Picking?.Dispose();
Options?.Dispose(); Options?.Dispose();
} }

View File

@ -76,12 +76,12 @@ public class Material : IDisposable
if (uvCount < 1 || Parameters.IsNull) if (uvCount < 1 || Parameters.IsNull)
{ {
Diffuse = [new Texture(FLinearColor.Gray)]; Diffuse = new[] { options.Icons["checker"] };
Normals = [new Texture(new FLinearColor(0.5f, 0.5f, 1f, 1f))]; Normals = new[] { new Texture(new FLinearColor(0.498f, 0.498f, 0.996f, 1f)) };
SpecularMasks = [new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f))]; SpecularMasks = new [] { new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f)) };
Emissive = new Texture[1]; Emissive = new Texture[1];
DiffuseColor = [Vector4.One]; DiffuseColor = new[] { Vector4.One };
EmissiveColor = [Vector4.One]; EmissiveColor = new[] { Vector4.One };
} }
else else
{ {
@ -98,11 +98,11 @@ public class Material : IDisposable
} }
{ // ambient occlusion + color boost { // ambient occlusion + color boost
if (Parameters.TryGetTexture2d(out var original, "M", "AEM", "AO") && if (Parameters.TryGetTexture2d(out var original, "M", "AEM", "AO", "Mask") &&
!original.Name.Equals("T_BlackMask") && options.TryGetTexture(original, false, out var transformed)) !original.Name.Equals("T_BlackMask") && options.TryGetTexture(original, false, out var transformed))
{ {
HasAo = true; HasAo = true;
Ao = new AoParams { Texture = transformed }; Ao = new AoParams { Texture = transformed, AmbientOcclusion = 0.7f };
if (Parameters.TryGetLinearColor(out var l, "Skin Boost Color And Exponent")) if (Parameters.TryGetLinearColor(out var l, "Skin Boost Color And Exponent"))
{ {
Ao.HasColorBoost = true; Ao.HasColorBoost = true;
@ -242,6 +242,7 @@ public class Material : IDisposable
shader.SetUniform("uParameters.Ao.HasColorBoost", Ao.HasColorBoost); shader.SetUniform("uParameters.Ao.HasColorBoost", Ao.HasColorBoost);
shader.SetUniform("uParameters.Ao.ColorBoost.Color", Ao.ColorBoost.Color); shader.SetUniform("uParameters.Ao.ColorBoost.Color", Ao.ColorBoost.Color);
shader.SetUniform("uParameters.Ao.ColorBoost.Exponent", Ao.ColorBoost.Exponent); shader.SetUniform("uParameters.Ao.ColorBoost.Exponent", Ao.ColorBoost.Exponent);
shader.SetUniform("uParameters.Ao.AmbientOcclusion", Ao.AmbientOcclusion);
shader.SetUniform("uParameters.HasAo", HasAo); shader.SetUniform("uParameters.HasAo", HasAo);
shader.SetUniform("uParameters.EmissiveRegion", EmissiveRegion); shader.SetUniform("uParameters.EmissiveRegion", EmissiveRegion);
@ -268,13 +269,18 @@ public class Material : IDisposable
ImGui.DragFloat("", ref EmissiveMult, _step, _zero, _infinite, _mult, _clamp); ImGui.DragFloat("", ref EmissiveMult, _step, _zero, _infinite, _mult, _clamp);
ImGui.PopID(); ImGui.PopID();
if (HasAo && Ao.HasColorBoost) if (HasAo)
{ {
SnimGui.Layout("Color Boost");ImGui.PushID(id++); SnimGui.Layout("Ambient Occlusion");ImGui.PushID(id++);
ImGui.ColorEdit3("", ref Ao.ColorBoost.Color);ImGui.PopID(); ImGui.DragFloat("", ref Ao.AmbientOcclusion, _step, _zero, 1.0f, _mult, _clamp);ImGui.PopID();
SnimGui.Layout("Color Boost Exponent");ImGui.PushID(id++); if (Ao.HasColorBoost)
ImGui.DragFloat("", ref Ao.ColorBoost.Exponent, _step, _zero, _infinite, _mult, _clamp); {
ImGui.PopID(); SnimGui.Layout("Color Boost");ImGui.PushID(id++);
ImGui.ColorEdit3("", ref Ao.ColorBoost.Color);ImGui.PopID();
SnimGui.Layout("Color Boost Exponent");ImGui.PushID(id++);
ImGui.DragFloat("", ref Ao.ColorBoost.Exponent, _step, _zero, _infinite, _mult, _clamp);
ImGui.PopID();
}
} }
ImGui.EndTable(); ImGui.EndTable();
} }
@ -323,12 +329,12 @@ public class Material : IDisposable
switch (SelectedTexture) switch (SelectedTexture)
{ {
case 0 when DiffuseColor.Length > 0: case 0:
SnimGui.Layout("Color");ImGui.PushID(3); SnimGui.Layout("Color");ImGui.PushID(3);
ImGui.ColorEdit4("", ref DiffuseColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha); ImGui.ColorEdit4("", ref DiffuseColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha);
ImGui.PopID(); ImGui.PopID();
break; break;
case 4 when EmissiveColor.Length > 0: case 4:
SnimGui.Layout("Color");ImGui.PushID(3); SnimGui.Layout("Color");ImGui.PushID(3);
ImGui.ColorEdit4("", ref EmissiveColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha); ImGui.ColorEdit4("", ref EmissiveColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha);
ImGui.PopID();SnimGui.Layout("Region");ImGui.PushID(4); ImGui.PopID();SnimGui.Layout("Region");ImGui.PushID(4);
@ -351,11 +357,11 @@ public class Material : IDisposable
{ {
return SelectedTexture switch return SelectedTexture switch
{ {
0 when Diffuse.Length > 0 => Diffuse[SelectedChannel], 0 => Diffuse[SelectedChannel],
1 when Normals.Length > 0 => Normals[SelectedChannel], 1 => Normals[SelectedChannel],
2 when SpecularMasks.Length > 0 => SpecularMasks[SelectedChannel], 2 => SpecularMasks[SelectedChannel],
3 => Ao.Texture, 3 => Ao.Texture,
4 when Emissive.Length > 0 => Emissive[SelectedChannel], 4 => Emissive[SelectedChannel],
_ => null _ => null
}; };
} }
@ -395,6 +401,7 @@ public class Material : IDisposable
public struct AoParams public struct AoParams
{ {
public Texture Texture; public Texture Texture;
public float AmbientOcclusion;
public Boost ColorBoost; public Boost ColorBoost;
public bool HasColorBoost; public bool HasColorBoost;

View File

@ -10,38 +10,28 @@ namespace FModel.Views.Snooper.Shading;
public class Shader : IDisposable public class Shader : IDisposable
{ {
private readonly int _handle; private readonly int _handle;
private readonly int _vHandle;
private readonly int _fHandle;
private readonly Dictionary<string, int> _uniformsLocation = new (); private readonly Dictionary<string, int> _uniformsLocation = new ();
public Shader() : this("default") {} public Shader() : this("default") {}
public Shader(string name1, string name2 = null) public Shader(string name)
{ {
_handle = GL.CreateProgram(); _handle = GL.CreateProgram();
_vHandle = LoadShader(ShaderType.VertexShader, $"{name1}.vert");
_fHandle = LoadShader(ShaderType.FragmentShader, $"{name2 ?? name1}.frag");
Attach();
}
private void Attach() var v = LoadShader(ShaderType.VertexShader, $"{name}.vert");
{ var f = LoadShader(ShaderType.FragmentShader, $"{name}.frag");
GL.AttachShader(_handle, _vHandle); GL.AttachShader(_handle, v);
GL.AttachShader(_handle, _fHandle); GL.AttachShader(_handle, f);
GL.LinkProgram(_handle); GL.LinkProgram(_handle);
GL.GetProgram(_handle, GetProgramParameterName.LinkStatus, out var status); GL.GetProgram(_handle, GetProgramParameterName.LinkStatus, out var status);
if (status == 0) if (status == 0)
{ {
throw new Exception($"Program failed to link with error: {GL.GetProgramInfoLog(_handle)}"); throw new Exception($"Program failed to link with error: {GL.GetProgramInfoLog(_handle)}");
} }
} GL.DetachShader(_handle, v);
GL.DetachShader(_handle, f);
private void Detach() GL.DeleteShader(v);
{ GL.DeleteShader(f);
GL.DetachShader(_handle, _vHandle);
GL.DetachShader(_handle, _fHandle);
GL.DeleteShader(_vHandle);
GL.DeleteShader(_fHandle);
} }
private int LoadShader(ShaderType type, string file) private int LoadShader(ShaderType type, string file)
@ -140,7 +130,6 @@ public class Shader : IDisposable
public void Dispose() public void Dispose()
{ {
Detach();
GL.DeleteProgram(_handle); GL.DeleteProgram(_handle);
} }
} }

View File

@ -29,17 +29,17 @@ public class Texture : IDisposable
public int Height; public int Height;
private const int DisabledChannel = (int)BlendingFactor.Zero; private const int DisabledChannel = (int)BlendingFactor.Zero;
private readonly bool[] _values = [true, true, true, true]; private readonly bool[] _values = { true, true, true, true };
private readonly string[] _labels = ["R", "G", "B", "A"]; private readonly string[] _labels = { "R", "G", "B", "A" };
public int[] SwizzleMask = public int[] SwizzleMask =
[ {
(int) PixelFormat.Red, (int) PixelFormat.Red,
(int) PixelFormat.Green, (int) PixelFormat.Green,
(int) PixelFormat.Blue, (int) PixelFormat.Blue,
(int) PixelFormat.Alpha (int) PixelFormat.Alpha
]; };
private Texture(TextureType type) public Texture(TextureType type)
{ {
_handle = GL.GenTexture(); _handle = GL.GenTexture();
_type = type; _type = type;
@ -92,19 +92,7 @@ public class Texture : IDisposable
Height = bitmap.Height; Height = bitmap.Height;
Bind(TextureUnit.Texture0); Bind(TextureUnit.Texture0);
var internalFormat = Format switch GL.TexImage2D(_target, 0, texture2D.SRGB ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, bitmap.Bytes);
{
EPixelFormat.PF_G8 => PixelInternalFormat.R8,
_ => texture2D.SRGB ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb
};
var pixelFormat = Format switch
{
EPixelFormat.PF_G8 => PixelFormat.Red,
_ => PixelFormat.Rgba
};
GL.TexImage2D(_target, 0, internalFormat, Width, Height, 0, pixelFormat, PixelType.UnsignedByte, bitmap.Bytes);
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear); GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0); GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);

View File

@ -16,21 +16,18 @@ public static class TextureHelper
// R: Whatever (AO / S / E / ...) // R: Whatever (AO / S / E / ...)
// G: Roughness // G: Roughness
// B: Metallic // B: Metallic
case "GAMEFACE":
case "HK_PROJECT": case "HK_PROJECT":
case "COSMICSHAKE": case "COSMICSHAKE":
case "PHOENIX": case "PHOENIX":
case "ATOMICHEART": case "ATOMICHEART":
case "MULTIVERSUS":
case "BODYCAM":
{ {
texture.SwizzleMask = texture.SwizzleMask = new []
[ {
(int) PixelFormat.Red, (int) PixelFormat.Red,
(int) PixelFormat.Blue, (int) PixelFormat.Blue,
(int) PixelFormat.Green, (int) PixelFormat.Green,
(int) PixelFormat.Alpha (int) PixelFormat.Alpha
]; };
break; break;
} }
// R: Metallic // R: Metallic
@ -40,13 +37,13 @@ public static class TextureHelper
case "DIVINEKNOCKOUT": case "DIVINEKNOCKOUT":
case "MOONMAN": case "MOONMAN":
{ {
texture.SwizzleMask = texture.SwizzleMask = new []
[ {
(int) PixelFormat.Blue, (int) PixelFormat.Blue,
(int) PixelFormat.Red, (int) PixelFormat.Red,
(int) PixelFormat.Green, (int) PixelFormat.Green,
(int) PixelFormat.Alpha (int) PixelFormat.Alpha
]; };
break; break;
} }
// R: Roughness // R: Roughness
@ -55,13 +52,13 @@ public static class TextureHelper
case "CCFF7R": case "CCFF7R":
case "PJ033": case "PJ033":
{ {
texture.SwizzleMask = texture.SwizzleMask = new []
[ {
(int) PixelFormat.Blue, (int) PixelFormat.Blue,
(int) PixelFormat.Green, (int) PixelFormat.Green,
(int) PixelFormat.Red, (int) PixelFormat.Red,
(int) PixelFormat.Alpha (int) PixelFormat.Alpha
]; };
break; break;
} }
} }

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