Compare commits
No commits in common. "master" and "4.4.3.0" have entirely different histories.
6
.github/workflows/main.yml
vendored
|
|
@ -21,16 +21,16 @@ jobs:
|
|||
- name: Fetch Submodules Recursively
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: .NET 8 Setup
|
||||
- name: .NET 6 Setup
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: .NET Restore
|
||||
run: dotnet restore FModel
|
||||
|
||||
- name: .NET Publish
|
||||
run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f 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
|
||||
uses: papeloto/action-zip@v1
|
||||
|
|
|
|||
65
.github/workflows/qa.yml
vendored
|
|
@ -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
|
|
@ -1,3 +1,6 @@
|
|||
[submodule "CUE4Parse"]
|
||||
path = CUE4Parse
|
||||
url = https://github.com/FabianFG/CUE4Parse
|
||||
[submodule "EpicManifestParser"]
|
||||
path = EpicManifestParser
|
||||
url = https://github.com/FModel/EpicManifestParser
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 455b72e5e38bfe9476b5823bc642fc8ef488347f
|
||||
Subproject commit 2d5e2bdd35db42759d8320076778737fcf9d53a2
|
||||
1
EpicManifestParser
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 21df8a55d474f14148a35bc943e06f3fdc20c997
|
||||
|
|
@ -50,14 +50,7 @@ public partial class App
|
|||
var createMe = false;
|
||||
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
|
||||
{
|
||||
var currentDir = Directory.GetCurrentDirectory();
|
||||
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");
|
||||
UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
|
||||
|
|
@ -97,16 +90,14 @@ public partial class App
|
|||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
||||
|
||||
#if DEBUG
|
||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
|
||||
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();
|
||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).CreateLogger();
|
||||
#else
|
||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
|
||||
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
|
||||
#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("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
|
||||
Log.Information("Culture {SysLang}", CultureInfo.CurrentCulture);
|
||||
|
|
@ -142,7 +133,7 @@ public partial class App
|
|||
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
|
||||
{
|
||||
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
|
||||
UserSettings.Delete();
|
||||
UserSettings.Default = new UserSettings();
|
||||
|
||||
ApplicationService.ApplicationView.Restart();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,12 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Extensions;
|
||||
|
||||
namespace FModel;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public static readonly string APP_PATH = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
|
||||
public static readonly string APP_VERSION = FileVersionInfo.GetVersionInfo(APP_PATH).FileVersion;
|
||||
public static readonly string APP_COMMIT_ID = FileVersionInfo.GetVersionInfo(APP_PATH).ProductVersion.SubstringAfter('+');
|
||||
public static readonly string APP_SHORT_COMMIT_ID = APP_COMMIT_ID[..7];
|
||||
|
||||
public static readonly string APP_VERSION = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
|
||||
public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
public static readonly FGuid ZERO_GUID = new(0U);
|
||||
|
||||
|
|
@ -28,10 +20,7 @@ public static class Constants
|
|||
public const string YELLOW = "#E5C07B";
|
||||
public const string BLUE = "#528BCC";
|
||||
|
||||
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 ISSUE_LINK = "https://github.com/iAmAsval/FModel/issues/new/choose";
|
||||
public const string DONATE_LINK = "https://fmodel.app/donate";
|
||||
public const string DISCORD_LINK = "https://fmodel.app/discord";
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class BaseBundle : UCreator
|
|||
{
|
||||
_quests = new List<BaseQuest>();
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName"))
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :)
|
||||
|
|
@ -124,7 +124,8 @@ public class BaseBundle : UCreator
|
|||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_headerPaint.Typeface);
|
||||
c.DrawShapedText(shaper, DisplayName, Width / 2f, _headerHeight / 2f + _headerPaint.TextSize / 2 - 10, _headerPaint);
|
||||
var shapedText = shaper.Shape(DisplayName, _headerPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint);
|
||||
}
|
||||
|
||||
private void DrawQuests(SKCanvas c)
|
||||
|
|
@ -136,4 +137,4 @@ public class BaseBundle : UCreator
|
|||
y += quest.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
using System.Linq;
|
||||
using CUE4Parse.GameTypes.FN.Enums;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.GameplayTags;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
|
|
@ -34,27 +32,10 @@ public class BaseCommunity : BaseIcon
|
|||
{
|
||||
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
|
||||
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series"))
|
||||
{
|
||||
_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;
|
||||
}
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export))
|
||||
_rarityName = export.Name;
|
||||
else
|
||||
{
|
||||
_rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription();
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||
CheckGameplayTags(gameplayTags);
|
||||
|
|
@ -174,9 +155,10 @@ public class BaseCommunity : BaseIcon
|
|||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
var x = font.Alignment switch
|
||||
{
|
||||
SKTextAlign.Center => Width / 2f,
|
||||
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
|
||||
_ => font.X
|
||||
};
|
||||
|
||||
|
|
@ -210,9 +192,10 @@ public class BaseCommunity : BaseIcon
|
|||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
|
||||
var shapedText = shaper.Shape(Description, DescriptionPaint);
|
||||
var x = font.Alignment switch
|
||||
{
|
||||
SKTextAlign.Center => Width / 2f,
|
||||
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
|
||||
_ => font.X
|
||||
};
|
||||
|
||||
|
|
@ -253,9 +236,11 @@ public class BaseCommunity : BaseIcon
|
|||
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(text, DisplayNamePaint);
|
||||
var x = font.Alignment switch
|
||||
{
|
||||
SKTextAlign.Center => Width / 2f,
|
||||
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
|
||||
SKTextAlign.Right => font.X - DisplayNamePaint.MeasureText(text),
|
||||
_ => font.X
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,325 +1,298 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using CUE4Parse.GameTypes.FN.Enums;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.GameplayTags;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using FModel.Settings;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseIcon : UCreator
|
||||
{
|
||||
public SKBitmap SeriesBackground { get; protected set; }
|
||||
protected string ShortDescription { get; set; }
|
||||
protected string CosmeticSource { get; set; }
|
||||
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
|
||||
|
||||
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) { }
|
||||
|
||||
public void ParseForReward(bool isUsingDisplayAsset)
|
||||
{
|
||||
// rarity
|
||||
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
|
||||
|
||||
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
|
||||
{
|
||||
GetSeries(dataList);
|
||||
Preview = Utils.GetBitmap(dataList);
|
||||
}
|
||||
|
||||
// preview
|
||||
if (Preview is null)
|
||||
{
|
||||
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
|
||||
Preview = preview;
|
||||
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
|
||||
Preview = Utils.GetBitmap(itemDefinition);
|
||||
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "BundleImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
|
||||
Preview = Utils.GetBitmap(largePreview);
|
||||
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
|
||||
Preview = Utils.GetBitmap(s);
|
||||
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
|
||||
Preview = Utils.GetBitmap(otherPreview);
|
||||
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
|
||||
Preview = Utils.GetBitmap(materialInstancePreview);
|
||||
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
|
||||
Preview = Utils.GetBitmap(res);
|
||||
}
|
||||
|
||||
// text
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description", "ItemDescription", "BundleDescription", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
|
||||
Description = description.Text;
|
||||
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
|
||||
Description = string.Join('\n', descriptions.Select(x => x.Text));
|
||||
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
|
||||
ShortDescription = shortDescription.Text;
|
||||
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
|
||||
ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
|
||||
|
||||
// Only works on non-cataba designs
|
||||
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
|
||||
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
|
||||
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
|
||||
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
|
||||
{
|
||||
Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) };
|
||||
Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) };
|
||||
}
|
||||
|
||||
Description = Utils.RemoveHtmlTags(Description);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||
CheckGameplayTags(gameplayTags);
|
||||
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
||||
CosmeticSource = cosmeticItem.Name;
|
||||
}
|
||||
|
||||
protected void Draw(SKCanvas c)
|
||||
{
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawUserFacingFlags(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
|
||||
if (Description != ShortDescription)
|
||||
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
|
||||
DrawUserFacingFlags(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
Draw(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private void GetSeries(FPackageIndex s)
|
||||
{
|
||||
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
|
||||
|
||||
GetSeries(export);
|
||||
}
|
||||
|
||||
private void GetSeries(FInstancedStruct[] s)
|
||||
{
|
||||
if (s.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
|
||||
GetSeries(dl.NonConstStruct?.Get<FPackageIndex>("Series"));
|
||||
}
|
||||
|
||||
private void GetSeries(FStructFallback s)
|
||||
{
|
||||
if (!s.TryGetValue(out FPackageIndex[] components, "Components")) return;
|
||||
if (components.FirstOrDefault(c => c.Name.Contains("Series")) is not { } seriesDef ||
|
||||
!seriesDef.TryLoad(out var seriesDefObj) || seriesDefObj is null ||
|
||||
!seriesDefObj.TryGetValue(out UObject series, "Series")) return;
|
||||
|
||||
GetSeries(series);
|
||||
}
|
||||
|
||||
protected void GetSeries(UObject uObject)
|
||||
{
|
||||
if (uObject is UTexture2D texture2D)
|
||||
{
|
||||
SeriesBackground = texture2D.Decode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
|
||||
{
|
||||
SeriesBackground = Utils.GetBitmap(backgroundTexture);
|
||||
}
|
||||
|
||||
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
|
||||
colors.TryGetValue(out FLinearColor color1, "Color1") &&
|
||||
colors.TryGetValue(out FLinearColor color2, "Color2") &&
|
||||
colors.TryGetValue(out FLinearColor color3, "Color3"))
|
||||
{
|
||||
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
|
||||
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
|
||||
}
|
||||
|
||||
if (uObject.Name.Equals("PlatformSeries") &&
|
||||
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
|
||||
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
|
||||
{
|
||||
foreach (var vectorParameter in material.VectorParameterValues)
|
||||
{
|
||||
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
|
||||
continue;
|
||||
|
||||
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetRarity(EFortRarity r)
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
|
||||
|
||||
if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
|
||||
data.TryGetValue(out FLinearColor color1, "Color1") &&
|
||||
data.TryGetValue(out FLinearColor color2, "Color2") &&
|
||||
data.TryGetValue(out FLinearColor color3, "Color3"))
|
||||
{
|
||||
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
|
||||
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
|
||||
}
|
||||
}
|
||||
|
||||
protected string GetCosmeticSet(string setName)
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
|
||||
return string.Empty;
|
||||
|
||||
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
|
||||
return string.Empty;
|
||||
|
||||
var name = string.Empty;
|
||||
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
|
||||
name = displayName.Text;
|
||||
|
||||
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
|
||||
return string.Format(format, name);
|
||||
}
|
||||
|
||||
protected (int, int) GetInternalSID(int number)
|
||||
{
|
||||
static int GetSeasonsInChapter(int chapter) => chapter switch
|
||||
{
|
||||
1 => 10,
|
||||
2 => 8,
|
||||
3 => 4,
|
||||
4 => 5,
|
||||
_ => 10
|
||||
};
|
||||
|
||||
var chapterIdx = 0;
|
||||
var seasonIdx = 0;
|
||||
while (number > 0)
|
||||
{
|
||||
var seasonsInChapter = GetSeasonsInChapter(++chapterIdx);
|
||||
if (number > seasonsInChapter)
|
||||
number -= seasonsInChapter;
|
||||
else
|
||||
{
|
||||
seasonIdx = number;
|
||||
number = 0;
|
||||
}
|
||||
}
|
||||
return (chapterIdx, seasonIdx);
|
||||
}
|
||||
|
||||
protected string GetCosmeticSeason(string seasonNumber)
|
||||
{
|
||||
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
||||
var initial = int.Parse(s);
|
||||
(int chapterIdx, int seasonIdx) = GetInternalSID(initial);
|
||||
|
||||
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
|
||||
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
|
||||
if (initial <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
|
||||
|
||||
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
|
||||
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
|
||||
var d = string.Format(chapterFormat, string.Format(chapter, chapterIdx), string.Format(season, seasonIdx));
|
||||
return Utils.RemoveHtmlTags(string.Format(introduced, d));
|
||||
}
|
||||
|
||||
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
||||
{
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
|
||||
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
|
||||
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
|
||||
CosmeticSource = action.Text["Athena.ItemAction.".Length..];
|
||||
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
|
||||
Description += GetCosmeticSet(set.Text);
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
|
||||
Description += GetCosmeticSeason(season.Text);
|
||||
|
||||
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
|
||||
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
|
||||
}
|
||||
|
||||
protected void GetUserFacingFlags(IList<string> userFacingFlags)
|
||||
{
|
||||
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
|
||||
return;
|
||||
|
||||
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
|
||||
return;
|
||||
|
||||
UserFacingFlags = new Dictionary<string, SKBitmap>(userFacingFlags.Count);
|
||||
foreach (var flag in userFacingFlags)
|
||||
{
|
||||
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
|
||||
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
|
||||
else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var category in tertiaryCategories)
|
||||
{
|
||||
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
|
||||
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
|
||||
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using CUE4Parse.GameTypes.FN.Enums;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.GameplayTags;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using FModel.Settings;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseIcon : UCreator
|
||||
{
|
||||
public SKBitmap SeriesBackground { get; protected set; }
|
||||
protected string ShortDescription { get; set; }
|
||||
protected string CosmeticSource { get; set; }
|
||||
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
|
||||
|
||||
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) { }
|
||||
|
||||
public void ParseForReward(bool isUsingDisplayAsset)
|
||||
{
|
||||
// rarity
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
|
||||
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
|
||||
|
||||
// preview
|
||||
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
|
||||
Preview = preview;
|
||||
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
|
||||
Preview = Utils.GetBitmap(itemDefinition);
|
||||
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
|
||||
Preview = Utils.GetBitmap(largePreview);
|
||||
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
|
||||
Preview = Utils.GetBitmap(s);
|
||||
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
|
||||
Preview = Utils.GetBitmap(otherPreview);
|
||||
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
|
||||
Preview = Utils.GetBitmap(materialInstancePreview);
|
||||
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
|
||||
Preview = Utils.GetBitmap(res);
|
||||
|
||||
// text
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
|
||||
Description = description.Text;
|
||||
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
|
||||
Description = string.Join('\n', descriptions.Select(x => x.Text));
|
||||
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
|
||||
ShortDescription = shortDescription.Text;
|
||||
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
|
||||
ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
|
||||
|
||||
// Only works on non-cataba designs
|
||||
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
|
||||
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
|
||||
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
|
||||
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
|
||||
{
|
||||
Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) };
|
||||
Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) };
|
||||
}
|
||||
|
||||
Description = Utils.RemoveHtmlTags(Description);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||
CheckGameplayTags(gameplayTags);
|
||||
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
||||
CosmeticSource = cosmeticItem.Name;
|
||||
}
|
||||
|
||||
protected void Draw(SKCanvas c)
|
||||
{
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawUserFacingFlags(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
|
||||
if (Description != ShortDescription)
|
||||
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
|
||||
DrawUserFacingFlags(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
Draw(c);
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
|
||||
private void GetSeries(FPackageIndex s)
|
||||
{
|
||||
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
|
||||
|
||||
GetSeries(export);
|
||||
}
|
||||
|
||||
protected void GetSeries(UObject uObject)
|
||||
{
|
||||
if (uObject is UTexture2D texture2D)
|
||||
{
|
||||
SeriesBackground = texture2D.Decode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
|
||||
{
|
||||
SeriesBackground = Utils.GetBitmap(backgroundTexture);
|
||||
}
|
||||
|
||||
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
|
||||
colors.TryGetValue(out FLinearColor color1, "Color1") &&
|
||||
colors.TryGetValue(out FLinearColor color2, "Color2") &&
|
||||
colors.TryGetValue(out FLinearColor color3, "Color3"))
|
||||
{
|
||||
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
|
||||
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
|
||||
}
|
||||
|
||||
if (uObject.Name.Equals("PlatformSeries") &&
|
||||
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
|
||||
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
|
||||
{
|
||||
foreach (var vectorParameter in material.VectorParameterValues)
|
||||
{
|
||||
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
|
||||
continue;
|
||||
|
||||
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetRarity(EFortRarity r)
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
|
||||
|
||||
if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
|
||||
data.TryGetValue(out FLinearColor color1, "Color1") &&
|
||||
data.TryGetValue(out FLinearColor color2, "Color2") &&
|
||||
data.TryGetValue(out FLinearColor color3, "Color3"))
|
||||
{
|
||||
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
|
||||
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
|
||||
}
|
||||
}
|
||||
|
||||
protected string GetCosmeticSet(string setName)
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
|
||||
return string.Empty;
|
||||
|
||||
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
|
||||
return string.Empty;
|
||||
|
||||
var name = string.Empty;
|
||||
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
|
||||
name = displayName.Text;
|
||||
|
||||
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
|
||||
return string.Format(format, name);
|
||||
}
|
||||
|
||||
protected (int, int) GetInternalSID(int number)
|
||||
{
|
||||
static int GetSeasonsInChapter(int chapter) => chapter switch
|
||||
{
|
||||
1 => 10,
|
||||
2 => 8,
|
||||
3 => 4,
|
||||
_ => 10
|
||||
};
|
||||
|
||||
var chapterIdx = 0;
|
||||
var seasonIdx = 0;
|
||||
while (number > 0)
|
||||
{
|
||||
var seasonsInChapter = GetSeasonsInChapter(++chapterIdx);
|
||||
if (number > seasonsInChapter)
|
||||
number -= seasonsInChapter;
|
||||
else
|
||||
{
|
||||
seasonIdx = number;
|
||||
number = 0;
|
||||
}
|
||||
}
|
||||
return (chapterIdx, seasonIdx);
|
||||
}
|
||||
|
||||
protected string GetCosmeticSeason(string seasonNumber)
|
||||
{
|
||||
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
||||
var initial = int.Parse(s);
|
||||
(int chapterIdx, int seasonIdx) = GetInternalSID(initial);
|
||||
|
||||
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
|
||||
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
|
||||
if (initial <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
|
||||
|
||||
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
|
||||
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
|
||||
var d = string.Format(chapterFormat, string.Format(chapter, chapterIdx), string.Format(season, seasonIdx));
|
||||
return Utils.RemoveHtmlTags(string.Format(introduced, d));
|
||||
}
|
||||
|
||||
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
||||
{
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
|
||||
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
|
||||
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
|
||||
CosmeticSource = action.Text["Athena.ItemAction.".Length..];
|
||||
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
|
||||
Description += GetCosmeticSet(set.Text);
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
|
||||
Description += GetCosmeticSeason(season.Text);
|
||||
|
||||
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
|
||||
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
|
||||
}
|
||||
|
||||
protected void GetUserFacingFlags(IList<string> userFacingFlags)
|
||||
{
|
||||
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
|
||||
return;
|
||||
|
||||
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
|
||||
return;
|
||||
|
||||
UserFacingFlags = new Dictionary<string, SKBitmap>(userFacingFlags.Count);
|
||||
foreach (var flag in userFacingFlags)
|
||||
{
|
||||
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
|
||||
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
|
||||
else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var category in tertiaryCategories)
|
||||
{
|
||||
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
|
||||
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
|
||||
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ public class BaseIconStats : BaseIcon
|
|||
foreach (var poi in challengeMapPoiData)
|
||||
{
|
||||
if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") ||
|
||||
tagName != location.TagName || !poi.TryGetValue(out FText text, "Text")) continue;
|
||||
!tagName.Text.Equals(location.Text, StringComparison.OrdinalIgnoreCase) || !poi.TryGetValue(out FText text, "Text")) continue;
|
||||
|
||||
locationName = text.Text;
|
||||
break;
|
||||
|
|
@ -92,11 +92,7 @@ public class BaseIconStats : BaseIcon
|
|||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200));
|
||||
}
|
||||
|
||||
if (weaponRowValue.TryGetValue(out float mdpc, "MaxDamagePerCartridge") && mdpc >= 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), mdpc, 200));
|
||||
}
|
||||
else if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"))
|
||||
if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"))
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200));
|
||||
}
|
||||
|
|
@ -221,7 +217,8 @@ public class BaseIconStats : BaseIcon
|
|||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_informationPaint.Typeface);
|
||||
c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2f + _informationPaint.TextSize / 3, _informationPaint);
|
||||
shaper.Shape(DisplayName, _informationPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2 + _informationPaint.TextSize / 3, _informationPaint);
|
||||
}
|
||||
|
||||
private void DrawStatistics(SKCanvas c)
|
||||
|
|
@ -273,6 +270,7 @@ public class IconStat
|
|||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_statPaint.Typeface);
|
||||
shaper.Shape(_statName, _statPaint);
|
||||
c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint);
|
||||
|
||||
_statPaint.TextAlign = SKTextAlign.Right;
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@ public class BaseItemAccessToken : UCreator
|
|||
_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;
|
||||
else
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ public class BaseItemAccessToken : UCreator
|
|||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
c.DrawShapedText(shaper, DisplayName, left - shapedText.Width / 2, _icon.Margin * 8 + size, DisplayNamePaint);
|
||||
c.DrawShapedText(shaper, DisplayName, left - shapedText.Points[^1].X / 2, _icon.Margin * 8 + size, DisplayNamePaint);
|
||||
|
||||
float topBase = _icon.Margin + size * 2;
|
||||
if (!string.IsNullOrEmpty(_unlockedDescription))
|
||||
|
|
@ -96,4 +96,4 @@ public class BaseItemAccessToken : UCreator
|
|||
var h = Width - _icon.Margin - topBase;
|
||||
c.DrawBitmap(_icon.Preview ?? _icon.DefaultPreview, new SKRect(left - h / 2, topBase, left + h / 2, Width - _icon.Margin), ImagePaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,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();
|
||||
}
|
||||
|
|
@ -31,7 +31,6 @@ public class BaseMaterialInstance : BaseIcon
|
|||
case "TextureA":
|
||||
case "TextureB":
|
||||
case "OfferImage":
|
||||
case "CarTexture":
|
||||
Preview = Utils.GetBitmap(texture);
|
||||
break;
|
||||
}
|
||||
|
|
@ -89,4 +88,4 @@ public class BaseMaterialInstance : BaseIcon
|
|||
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,9 +17,10 @@ public class BaseMtxOffer : UCreator
|
|||
|
||||
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") &&
|
||||
|
|
@ -80,4 +81,4 @@ public class BaseMtxOffer : UCreator
|
|||
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +1,39 @@
|
|||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseOfferDisplayData : UCreator
|
||||
{
|
||||
private readonly List<BaseMaterialInstance> _offerImages;
|
||||
private BaseMaterialInstance[] _offerImages;
|
||||
|
||||
public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
_offerImages = new List<BaseMaterialInstance>();
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (!Object.TryGetValue(out FStructFallback[] contextualPresentations, "ContextualPresentations"))
|
||||
if (!Object.TryGetValue(out UMaterialInterface[] presentations, "Presentations"))
|
||||
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") ||
|
||||
!material.TryLoad(out UMaterialInterface presentation)) continue;
|
||||
|
||||
var offerImage = new BaseMaterialInstance(presentation, Style);
|
||||
var offerImage = new BaseMaterialInstance(presentations[i], Style);
|
||||
offerImage.ParseForInfo();
|
||||
_offerImages.Add(offerImage);
|
||||
_offerImages[i] = offerImage;
|
||||
}
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap[_offerImages.Count];
|
||||
var ret = new SKBitmap[_offerImages.Length];
|
||||
for (var i = 0; i < ret.Length; i++)
|
||||
{
|
||||
ret[i] = _offerImages[i]?.Draw()[0];
|
||||
ret[i] = _offerImages[i].Draw()[0];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ public class BasePlaylist : UCreator
|
|||
return;
|
||||
|
||||
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))
|
||||
return;
|
||||
|
||||
|
|
@ -74,4 +74,4 @@ public class BasePlaylist : UCreator
|
|||
if (_missionIcon == null) return;
|
||||
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,19 +69,13 @@ public class BaseQuest : BaseIcon
|
|||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ShortDescription))
|
||||
Description = ShortDescription;
|
||||
if (string.IsNullOrEmpty(DisplayName) && !string.IsNullOrEmpty(Description))
|
||||
DisplayName = Description;
|
||||
if (DisplayName == Description)
|
||||
Description = string.Empty;
|
||||
|
||||
if ((Object.TryGetValue(out FSoftObjectPath icon, "QuestGiverWidgetIcon", "NotificationIconOverride") &&
|
||||
Utils.TryLoadObject(icon.AssetPathName.Text, out UObject iconObject)) ||
|
||||
(Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
|
||||
Description = ShortDescription;
|
||||
if (Object.TryGetValue(out FText completionText, "CompletionText"))
|
||||
Description += "\n" + completionText.Text;
|
||||
if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
|
||||
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
|
||||
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") &&
|
||||
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out iconObject)))
|
||||
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out UObject iconObject))
|
||||
{
|
||||
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 (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 (rewardsTable.TryGetDataTableRow("Default", StringComparison.InvariantCulture, out var row))
|
||||
{
|
||||
if (row.TryGetValue(out FName templateId, "TemplateId") &&
|
||||
row.TryGetValue(out int quantity, "Quantity"))
|
||||
|
|
@ -246,6 +233,7 @@ public class BaseQuest : BaseIcon
|
|||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_informationPaint.Typeface);
|
||||
shaper.Shape(DisplayName, _informationPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint);
|
||||
}
|
||||
|
||||
|
|
@ -275,4 +263,4 @@ public class BaseQuest : BaseIcon
|
|||
|
||||
_reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ public class BaseSeason : UCreator
|
|||
{
|
||||
_bookXpSchedule = Array.Empty<Page>();
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName"))
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&
|
||||
|
|
@ -136,7 +136,8 @@ public class BaseSeason : UCreator
|
|||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_headerPaint.Typeface);
|
||||
c.DrawShapedText(shaper, DisplayName, Width / 2f, _headerHeight / 2f + _headerPaint.TextSize / 2 - 10, _headerPaint);
|
||||
var shapedText = shaper.Shape(DisplayName, _headerPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint);
|
||||
}
|
||||
|
||||
private void DrawBookSchedule(SKCanvas c)
|
||||
|
|
@ -155,4 +156,4 @@ public class BaseSeason : UCreator
|
|||
x = 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -146,6 +146,7 @@ public class BaseUserControl : UCreator
|
|||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_displayNamePaint.Typeface);
|
||||
shaper.Shape(DisplayName, _displayNamePaint);
|
||||
c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint);
|
||||
#if DEBUG
|
||||
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Margin + _displayNamePaint.TextSize), new SKPaint { Color = SKColors.Blue, IsStroke = true });
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public class Reward
|
|||
_rewardPaint.TextSize = 50;
|
||||
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.Typeface = _rewardQuantity.StartsWith("x") ? Utils.Typefaces.BundleNumber : Utils.Typefaces.Bundle;
|
||||
|
|
@ -75,6 +75,7 @@ public class Reward
|
|||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_rewardPaint.Typeface);
|
||||
shaper.Shape(_rewardQuantity, _rewardPaint);
|
||||
c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Height + 25, rect.MidY + 20, _rewardPaint);
|
||||
}
|
||||
else
|
||||
|
|
@ -88,7 +89,7 @@ public class Reward
|
|||
public void DrawSeasonWin(SKCanvas c, int size)
|
||||
{
|
||||
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)
|
||||
|
|
@ -115,37 +116,37 @@ public class Reward
|
|||
{
|
||||
switch (trigger.ToLower())
|
||||
{
|
||||
// case "athenabattlestar":
|
||||
// _theReward = new BaseIcon(null, EIconStyle.Default);
|
||||
// _theReward.Border[0] = SKColor.Parse("FFDB67");
|
||||
// _theReward.Background[0] = SKColor.Parse("8F4A20");
|
||||
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/Athena/UI/Frontend/Art/T_UI_BP_BattleStar_L.T_UI_BP_BattleStar_L");
|
||||
// break;
|
||||
// case "athenaseasonalxp":
|
||||
// _theReward = new BaseIcon(null, EIconStyle.Default);
|
||||
// _theReward.Border[0] = SKColor.Parse("E6FDB1");
|
||||
// _theReward.Background[0] = SKColor.Parse("51830F");
|
||||
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPUncommon-L.T-FNBR-XPUncommon-L");
|
||||
// break;
|
||||
// case "mtxgiveaway":
|
||||
// _theReward = new BaseIcon(null, EIconStyle.Default);
|
||||
// _theReward.Border[0] = SKColor.Parse("DCE6FF");
|
||||
// _theReward.Background[0] = SKColor.Parse("64A0AF");
|
||||
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX");
|
||||
// break;
|
||||
case "athenabattlestar":
|
||||
_theReward = new BaseIcon(null, EIconStyle.Default);
|
||||
_theReward.Border[0] = SKColor.Parse("FFDB67");
|
||||
_theReward.Background[0] = SKColor.Parse("8F4A20");
|
||||
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/Athena/UI/Frontend/Art/T_UI_BP_BattleStar_L.T_UI_BP_BattleStar_L");
|
||||
break;
|
||||
case "athenaseasonalxp":
|
||||
_theReward = new BaseIcon(null, EIconStyle.Default);
|
||||
_theReward.Border[0] = SKColor.Parse("E6FDB1");
|
||||
_theReward.Background[0] = SKColor.Parse("51830F");
|
||||
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPUncommon-L.T-FNBR-XPUncommon-L");
|
||||
break;
|
||||
case "mtxgiveaway":
|
||||
_theReward = new BaseIcon(null, EIconStyle.Default);
|
||||
_theReward.Border[0] = SKColor.Parse("DCE6FF");
|
||||
_theReward.Background[0] = SKColor.Parse("64A0AF");
|
||||
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX");
|
||||
break;
|
||||
default:
|
||||
{
|
||||
var path = Utils.GetFullPath($"FortniteGame/(?:Content/Athena|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))
|
||||
{
|
||||
_theReward = new BaseIcon(d, EIconStyle.Default);
|
||||
_theReward.ParseForReward(false);
|
||||
_theReward.Border[0] = SKColors.White;
|
||||
_rewardQuantity = $"{_theReward.DisplayName} ({_rewardQuantity})";
|
||||
_rewardQuantity = _theReward.DisplayName;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -166,7 +166,7 @@ public abstract class UCreator
|
|||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
var x = Width / 2f;
|
||||
var x = (Width - shapedText.Points[^1].X) / 2;
|
||||
var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
|
||||
|
||||
switch (Style)
|
||||
|
|
@ -174,15 +174,14 @@ public abstract class UCreator
|
|||
case EIconStyle.Flat:
|
||||
{
|
||||
DisplayNamePaint.TextAlign = SKTextAlign.Right;
|
||||
x = Width - Margin * 2;
|
||||
x = Width - Margin * 2 - shapedText.Points[^1].X;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
var halfWidth = shapedText.Width / 2f;
|
||||
c.DrawLine(x - halfWidth, 0, x - halfWidth, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true });
|
||||
c.DrawLine(x + halfWidth, 0, x + halfWidth, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true });
|
||||
c.DrawLine(x, 0, x, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true });
|
||||
c.DrawLine(x + shapedText.Points[^1].X, 0, x + shapedText.Points[^1].X, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true });
|
||||
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, y), new SKPaint { Color = SKColors.Blue, IsStroke = true });
|
||||
#endif
|
||||
|
||||
|
|
@ -217,6 +216,8 @@ public abstract class UCreator
|
|||
case SKTextAlign.Left:
|
||||
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
|
||||
var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface);
|
||||
shaper.Shape(text, _shortDescriptionPaint);
|
||||
|
||||
c.DrawShapedText(shaper, text, Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
|
||||
break;
|
||||
case SKTextAlign.Right:
|
||||
|
|
|
|||
|
|
@ -1,256 +1,281 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using FModel.Creator.Bases;
|
||||
using FModel.Creator.Bases.BB;
|
||||
using FModel.Creator.Bases.FN;
|
||||
using FModel.Creator.Bases.MV;
|
||||
using FModel.Creator.Bases.SB;
|
||||
|
||||
namespace FModel.Creator;
|
||||
|
||||
public class CreatorPackage : IDisposable
|
||||
{
|
||||
private UObject _object;
|
||||
private EIconStyle _style;
|
||||
|
||||
public CreatorPackage(UObject uObject, EIconStyle style)
|
||||
{
|
||||
_object = uObject;
|
||||
_style = style;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UCreator ConstructCreator()
|
||||
{
|
||||
TryConstructCreator(out var creator);
|
||||
return creator;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryConstructCreator(out UCreator creator)
|
||||
{
|
||||
switch (_object.ExportType)
|
||||
{
|
||||
// Fortnite
|
||||
case "FortCreativeWeaponMeleeItemDefinition":
|
||||
case "AthenaConsumableEmoteItemDefinition":
|
||||
case "AthenaSkyDiveContrailItemDefinition":
|
||||
case "AthenaLoadingScreenItemDefinition":
|
||||
case "AthenaVictoryPoseItemDefinition":
|
||||
case "AthenaPetCarrierItemDefinition":
|
||||
case "AthenaMusicPackItemDefinition":
|
||||
case "AthenaBattleBusItemDefinition":
|
||||
case "AthenaCharacterItemDefinition":
|
||||
case "AthenaMapMarkerItemDefinition":
|
||||
case "AthenaBackpackItemDefinition":
|
||||
case "AthenaPickaxeItemDefinition":
|
||||
case "AthenaGadgetItemDefinition":
|
||||
case "AthenaGliderItemDefinition":
|
||||
case "AthenaSprayItemDefinition":
|
||||
case "AthenaDanceItemDefinition":
|
||||
case "AthenaEmojiItemDefinition":
|
||||
case "AthenaItemWrapDefinition":
|
||||
case "AthenaToyItemDefinition":
|
||||
case "FortHeroType":
|
||||
case "FortTokenType":
|
||||
case "FortAbilityKit":
|
||||
case "FortWorkerType":
|
||||
case "RewardGraphToken":
|
||||
case "JunoKnowledgeBundle":
|
||||
case "FortBannerTokenType":
|
||||
case "FortVariantTokenType":
|
||||
case "FortDecoItemDefinition":
|
||||
case "FortStatItemDefinition":
|
||||
case "FortAmmoItemDefinition":
|
||||
case "FortEmoteItemDefinition":
|
||||
case "FortBadgeItemDefinition":
|
||||
case "SparksMicItemDefinition":
|
||||
case "FortAwardItemDefinition":
|
||||
case "FortStackItemDefinition":
|
||||
case "FortWorldItemDefinition":
|
||||
case "SparksAuraItemDefinition":
|
||||
case "SparksDrumItemDefinition":
|
||||
case "SparksBassItemDefinition":
|
||||
case "FortGadgetItemDefinition":
|
||||
case "AthenaCharmItemDefinition":
|
||||
case "FortPlaysetItemDefinition":
|
||||
case "FortGiftBoxItemDefinition":
|
||||
case "FortOutpostItemDefinition":
|
||||
case "FortVehicleItemDefinition":
|
||||
case "FortMissionItemDefinition":
|
||||
case "FortAccountItemDefinition":
|
||||
case "SparksGuitarItemDefinition":
|
||||
case "FortCardPackItemDefinition":
|
||||
case "FortDefenderItemDefinition":
|
||||
case "FortCurrencyItemDefinition":
|
||||
case "FortResourceItemDefinition":
|
||||
case "FortBackpackItemDefinition":
|
||||
case "FortEventQuestMapDataAsset":
|
||||
case "FortBuildingItemDefinition":
|
||||
case "FortWeaponModItemDefinition":
|
||||
case "FortCodeTokenItemDefinition":
|
||||
case "FortSchematicItemDefinition":
|
||||
case "FortAlterableItemDefinition":
|
||||
case "SparksKeyboardItemDefinition":
|
||||
case "FortWorldMultiItemDefinition":
|
||||
case "FortAlterationItemDefinition":
|
||||
case "FortExpeditionItemDefinition":
|
||||
case "FortIngredientItemDefinition":
|
||||
case "FortConsumableItemDefinition":
|
||||
case "StWFortAccoladeItemDefinition":
|
||||
case "FortAccountBuffItemDefinition":
|
||||
case "FortWeaponMeleeItemDefinition":
|
||||
case "FortPlayerPerksItemDefinition":
|
||||
case "FortPlaysetPropItemDefinition":
|
||||
case "FortPrerollDataItemDefinition":
|
||||
case "JunoRecipeBundleItemDefinition":
|
||||
case "FortHomebaseNodeItemDefinition":
|
||||
case "FortNeverPersistItemDefinition":
|
||||
case "FortPlayerAugmentItemDefinition":
|
||||
case "FortSmartBuildingItemDefinition":
|
||||
case "FortGiftBoxUnlockItemDefinition":
|
||||
case "FortWeaponModItemDefinitionOptic":
|
||||
case "RadioContentSourceItemDefinition":
|
||||
case "FortPlaysetGrenadeItemDefinition":
|
||||
case "JunoWeaponCreatureItemDefinition":
|
||||
case "FortEventDependentItemDefinition":
|
||||
case "FortPersonalVehicleItemDefinition":
|
||||
case "FortGameplayModifierItemDefinition":
|
||||
case "FortHardcoreModifierItemDefinition":
|
||||
case "FortWeaponModItemDefinitionMagazine":
|
||||
case "FortConsumableAccountItemDefinition":
|
||||
case "FortConversionControlItemDefinition":
|
||||
case "FortAccountBuffCreditItemDefinition":
|
||||
case "JunoBuildInstructionsItemDefinition":
|
||||
case "FortCharacterCosmeticItemDefinition":
|
||||
case "JunoBuildingSetAccountItemDefinition":
|
||||
case "FortEventCurrencyItemDefinitionRedir":
|
||||
case "FortPersistentResourceItemDefinition":
|
||||
case "FortWeaponMeleeOffhandItemDefinition":
|
||||
case "FortHomebaseBannerIconItemDefinition":
|
||||
case "FortVehicleCosmeticsVariantTokenType":
|
||||
case "JunoBuildingPropAccountItemDefinition":
|
||||
case "FortCampaignHeroLoadoutItemDefinition":
|
||||
case "FortConditionalResourceItemDefinition":
|
||||
case "FortChallengeBundleScheduleDefinition":
|
||||
case "FortWeaponMeleeDualWieldItemDefinition":
|
||||
case "FortDailyRewardScheduleTokenDefinition":
|
||||
case "FortCreativeWeaponRangedItemDefinition":
|
||||
case "FortVehicleCosmeticsItemDefinition_Body":
|
||||
case "FortVehicleCosmeticsItemDefinition_Skin":
|
||||
case "FortVehicleCosmeticsItemDefinition_Wheel":
|
||||
case "FortCreativeRealEstatePlotItemDefinition":
|
||||
case "FortDeployableBaseCloudSaveItemDefinition":
|
||||
case "FortVehicleCosmeticsItemDefinition_Booster":
|
||||
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
|
||||
case "FortVehicleCosmeticsItemDefinition_DriftSmoke":
|
||||
case "FortVehicleCosmeticsItemDefinition_EngineAudio":
|
||||
creator = _style switch
|
||||
{
|
||||
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
|
||||
_ => new BaseIcon(_object, _style)
|
||||
};
|
||||
return true;
|
||||
case "JunoAthenaCharacterItemOverrideDefinition":
|
||||
case "JunoAthenaDanceItemOverrideDefinition":
|
||||
creator = new BaseJuno(_object, _style);
|
||||
return true;
|
||||
case "FortTandemCharacterData":
|
||||
creator = new BaseTandem(_object, _style);
|
||||
return true;
|
||||
case "FortTrapItemDefinition":
|
||||
case "FortSpyTechItemDefinition":
|
||||
case "FortAccoladeItemDefinition":
|
||||
case "FortContextTrapItemDefinition":
|
||||
case "FortWeaponRangedItemDefinition":
|
||||
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
|
||||
creator = new BaseIconStats(_object, _style);
|
||||
return true;
|
||||
case "FortItemSeriesDefinition":
|
||||
creator = new BaseSeries(_object, _style);
|
||||
return true;
|
||||
case "MaterialInstanceConstant"
|
||||
when _object.Owner != null &&
|
||||
(_object.Owner.Name.Contains("/MI_OfferImages/", StringComparison.OrdinalIgnoreCase) ||
|
||||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
|
||||
_object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)):
|
||||
creator = new BaseMaterialInstance(_object, _style);
|
||||
return true;
|
||||
case "AthenaItemShopOfferDisplayData":
|
||||
creator = new BaseOfferDisplayData(_object, _style);
|
||||
return true;
|
||||
case "FortMtxOfferData":
|
||||
creator = new BaseMtxOffer(_object, _style);
|
||||
return true;
|
||||
case "FortPlaylistAthena":
|
||||
creator = new BasePlaylist(_object, _style);
|
||||
return true;
|
||||
case "FortFeatItemDefinition":
|
||||
case "FortQuestItemDefinition":
|
||||
case "FortQuestItemDefinition_Athena":
|
||||
case "FortQuestItemDefinition_Campaign":
|
||||
case "AthenaDailyQuestDefinition":
|
||||
case "FortUrgentQuestItemDefinition":
|
||||
creator = new Bases.FN.BaseQuest(_object, _style);
|
||||
return true;
|
||||
case "FortCompendiumItemDefinition":
|
||||
case "FortChallengeBundleItemDefinition":
|
||||
creator = new BaseBundle(_object, _style);
|
||||
return true;
|
||||
// case "AthenaSeasonItemDefinition":
|
||||
// creator = new BaseSeason(_object, _style);
|
||||
// return true;
|
||||
case "FortItemAccessTokenType":
|
||||
creator = new BaseItemAccessToken(_object, _style);
|
||||
return true;
|
||||
case "FortCreativeOption":
|
||||
case "PlaylistUserOptionEnum":
|
||||
case "PlaylistUserOptionBool":
|
||||
case "PlaylistUserOptionString":
|
||||
case "PlaylistUserOptionIntEnum":
|
||||
case "PlaylistUserOptionIntRange":
|
||||
case "PlaylistUserOptionColorEnum":
|
||||
case "PlaylistUserOptionFloatEnum":
|
||||
case "PlaylistUserOptionFloatRange":
|
||||
case "PlaylistUserTintedIconIntEnum":
|
||||
case "PlaylistUserOptionPrimaryAsset":
|
||||
case "PlaylistUserOptionCollisionProfileEnum":
|
||||
creator = new BaseUserControl(_object, _style);
|
||||
return true;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using FModel.Creator.Bases;
|
||||
using FModel.Creator.Bases.BB;
|
||||
using FModel.Creator.Bases.FN;
|
||||
using FModel.Creator.Bases.MV;
|
||||
using FModel.Creator.Bases.SB;
|
||||
|
||||
namespace FModel.Creator;
|
||||
|
||||
public class CreatorPackage : IDisposable
|
||||
{
|
||||
private UObject _object;
|
||||
private EIconStyle _style;
|
||||
|
||||
public CreatorPackage(UObject uObject, EIconStyle style)
|
||||
{
|
||||
_object = uObject;
|
||||
_style = style;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UCreator ConstructCreator()
|
||||
{
|
||||
TryConstructCreator(out var creator);
|
||||
return creator;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryConstructCreator(out UCreator creator)
|
||||
{
|
||||
switch (_object.ExportType)
|
||||
{
|
||||
// Fortnite
|
||||
case "FortCreativeWeaponMeleeItemDefinition":
|
||||
case "AthenaConsumableEmoteItemDefinition":
|
||||
case "AthenaSkyDiveContrailItemDefinition":
|
||||
case "AthenaLoadingScreenItemDefinition":
|
||||
case "AthenaVictoryPoseItemDefinition":
|
||||
case "AthenaPetCarrierItemDefinition":
|
||||
case "AthenaMusicPackItemDefinition":
|
||||
case "AthenaBattleBusItemDefinition":
|
||||
case "AthenaCharacterItemDefinition":
|
||||
case "AthenaMapMarkerItemDefinition":
|
||||
case "AthenaBackpackItemDefinition":
|
||||
case "AthenaPickaxeItemDefinition":
|
||||
case "AthenaGadgetItemDefinition":
|
||||
case "AthenaGliderItemDefinition":
|
||||
case "AthenaSprayItemDefinition":
|
||||
case "AthenaDanceItemDefinition":
|
||||
case "AthenaEmojiItemDefinition":
|
||||
case "AthenaItemWrapDefinition":
|
||||
case "AthenaToyItemDefinition":
|
||||
case "FortHeroType":
|
||||
case "FortTokenType":
|
||||
case "FortAbilityKit":
|
||||
case "FortWorkerType":
|
||||
case "RewardGraphToken":
|
||||
case "FortBannerTokenType":
|
||||
case "FortVariantTokenType":
|
||||
case "FortDecoItemDefinition":
|
||||
case "FortStatItemDefinition":
|
||||
case "FortAmmoItemDefinition":
|
||||
case "FortEmoteItemDefinition":
|
||||
case "FortBadgeItemDefinition":
|
||||
case "FortAwardItemDefinition":
|
||||
case "FortGadgetItemDefinition":
|
||||
case "AthenaCharmItemDefinition":
|
||||
case "FortPlaysetItemDefinition":
|
||||
case "FortGiftBoxItemDefinition":
|
||||
case "FortOutpostItemDefinition":
|
||||
case "FortVehicleItemDefinition":
|
||||
case "FortCardPackItemDefinition":
|
||||
case "FortDefenderItemDefinition":
|
||||
case "FortCurrencyItemDefinition":
|
||||
case "FortResourceItemDefinition":
|
||||
case "FortBackpackItemDefinition":
|
||||
case "FortEventQuestMapDataAsset":
|
||||
case "FortWeaponModItemDefinition":
|
||||
case "FortCodeTokenItemDefinition":
|
||||
case "FortSchematicItemDefinition":
|
||||
case "FortWorldMultiItemDefinition":
|
||||
case "FortAlterationItemDefinition":
|
||||
case "FortExpeditionItemDefinition":
|
||||
case "FortIngredientItemDefinition":
|
||||
case "FortAccountBuffItemDefinition":
|
||||
case "FortWeaponMeleeItemDefinition":
|
||||
case "FortPlayerPerksItemDefinition":
|
||||
case "FortPlaysetPropItemDefinition":
|
||||
case "FortHomebaseNodeItemDefinition":
|
||||
case "FortNeverPersistItemDefinition":
|
||||
case "FortPlayerAugmentItemDefinition":
|
||||
case "RadioContentSourceItemDefinition":
|
||||
case "FortPlaysetGrenadeItemDefinition":
|
||||
case "FortPersonalVehicleItemDefinition":
|
||||
case "FortGameplayModifierItemDefinition":
|
||||
case "FortHardcoreModifierItemDefinition":
|
||||
case "FortConsumableAccountItemDefinition":
|
||||
case "FortConversionControlItemDefinition":
|
||||
case "FortAccountBuffCreditItemDefinition":
|
||||
case "FortEventCurrencyItemDefinitionRedir":
|
||||
case "FortPersistentResourceItemDefinition":
|
||||
case "FortHomebaseBannerIconItemDefinition":
|
||||
case "FortCampaignHeroLoadoutItemDefinition":
|
||||
case "FortConditionalResourceItemDefinition":
|
||||
case "FortChallengeBundleScheduleDefinition":
|
||||
case "FortWeaponMeleeDualWieldItemDefinition":
|
||||
case "FortDailyRewardScheduleTokenDefinition":
|
||||
case "FortCreativeWeaponRangedItemDefinition":
|
||||
case "FortCreativeRealEstatePlotItemDefinition":
|
||||
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
|
||||
case "StWFortAccoladeItemDefinition":
|
||||
case "FortSmartBuildingItemDefinition":
|
||||
creator = _style switch
|
||||
{
|
||||
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
|
||||
_ => new BaseIcon(_object, _style)
|
||||
};
|
||||
return true;
|
||||
case "FortTandemCharacterData":
|
||||
creator = new BaseTandem(_object, _style);
|
||||
return true;
|
||||
case "FortTrapItemDefinition":
|
||||
case "FortSpyTechItemDefinition":
|
||||
case "FortAccoladeItemDefinition":
|
||||
case "FortContextTrapItemDefinition":
|
||||
case "FortWeaponRangedItemDefinition":
|
||||
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
|
||||
creator = new BaseIconStats(_object, _style);
|
||||
return true;
|
||||
case "FortItemSeriesDefinition":
|
||||
creator = new BaseSeries(_object, _style);
|
||||
return true;
|
||||
case "MaterialInstanceConstant"
|
||||
when _object.Owner != null &&
|
||||
(_object.Owner.Name.EndsWith($"/MI_OfferImages/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
|
||||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
|
||||
_object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)):
|
||||
creator = new BaseMaterialInstance(_object, _style);
|
||||
return true;
|
||||
case "AthenaItemShopOfferDisplayData":
|
||||
creator = new BaseOfferDisplayData(_object, _style);
|
||||
return true;
|
||||
case "FortMtxOfferData":
|
||||
creator = new BaseMtxOffer(_object, _style);
|
||||
return true;
|
||||
case "FortPlaylistAthena":
|
||||
creator = new BasePlaylist(_object, _style);
|
||||
return true;
|
||||
case "FortFeatItemDefinition":
|
||||
case "FortQuestItemDefinition":
|
||||
case "FortQuestItemDefinition_Athena":
|
||||
case "FortQuestItemDefinition_Campaign":
|
||||
case "AthenaDailyQuestDefinition":
|
||||
case "FortUrgentQuestItemDefinition":
|
||||
creator = new Bases.FN.BaseQuest(_object, _style);
|
||||
return true;
|
||||
case "FortCompendiumItemDefinition":
|
||||
case "FortChallengeBundleItemDefinition":
|
||||
creator = new BaseBundle(_object, _style);
|
||||
return true;
|
||||
case "AthenaSeasonItemDefinition":
|
||||
creator = new BaseSeason(_object, _style);
|
||||
return true;
|
||||
case "FortItemAccessTokenType":
|
||||
creator = new BaseItemAccessToken(_object, _style);
|
||||
return true;
|
||||
case "FortCreativeOption":
|
||||
case "PlaylistUserOptionEnum":
|
||||
case "PlaylistUserOptionBool":
|
||||
case "PlaylistUserOptionString":
|
||||
case "PlaylistUserOptionIntEnum":
|
||||
case "PlaylistUserOptionIntRange":
|
||||
case "PlaylistUserOptionColorEnum":
|
||||
case "PlaylistUserOptionFloatEnum":
|
||||
case "PlaylistUserOptionFloatRange":
|
||||
case "PlaylistUserTintedIconIntEnum":
|
||||
case "PlaylistUserOptionPrimaryAsset":
|
||||
case "PlaylistUserOptionCollisionProfileEnum":
|
||||
creator = new BaseUserControl(_object, _style);
|
||||
return true;
|
||||
// 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;
|
||||
// Battle Breakers
|
||||
case "WExpGenericAccountItemDefinition":
|
||||
case "WExpGearAccountItemDefinition":
|
||||
case "WExpHQWorkerLodgesDefinition":
|
||||
case "WExpPersonalEventDefinition":
|
||||
case "WExpUpgradePotionDefinition":
|
||||
case "WExpAccountRewardDefinition":
|
||||
case "WExpHQBlacksmithDefinition":
|
||||
case "WExpHQSecretShopDefinition":
|
||||
case "WExpHQMonsterPitDefinition":
|
||||
case "WExpHQHeroTowerDefinition":
|
||||
case "WExpVoucherItemDefinition":
|
||||
case "WExpTreasureMapDefinition":
|
||||
case "WExpHammerChestDefinition":
|
||||
case "WExpHQWorkshopDefinition":
|
||||
case "WExpUnlockableDefinition":
|
||||
case "WExpHQSmelterDefinition":
|
||||
case "WExpContainerDefinition":
|
||||
case "WExpCharacterDefinition":
|
||||
case "WExpHQMarketDefinition":
|
||||
case "WExpGiftboxDefinition":
|
||||
case "WExpStandInDefinition":
|
||||
case "WExpRegionDefinition":
|
||||
case "WExpHQMineDefinition":
|
||||
case "WExpXpBookDefinition":
|
||||
case "WExpTokenDefinition":
|
||||
case "WExpItemDefinition":
|
||||
case "WExpZoneDefinition":
|
||||
creator = new BaseBreakersIcon(_object, EIconStyle.Default);
|
||||
return true;
|
||||
// Spellbreak
|
||||
case "GTargetedTeleportActiveSkill":
|
||||
case "GChronomasterV2ActiveSkill":
|
||||
case "GShadowstepActiveSkill":
|
||||
case "GGatewayActiveSkill":
|
||||
case "GStealthActiveSkill":
|
||||
case "GFeatherActiveSkill":
|
||||
case "GCosmeticDropTrail":
|
||||
case "GFlightActiveSkill":
|
||||
case "GCosmeticRunTrail":
|
||||
case "GCosmeticArtifact":
|
||||
case "GCosmeticTriumph":
|
||||
case "GWolfsbloodSkill":
|
||||
case "GDashActiveSkill":
|
||||
case "GCharacterPerk":
|
||||
case "GCosmeticTitle":
|
||||
case "GCosmeticBadge":
|
||||
case "GRMTStoreOffer":
|
||||
case "GCosmeticEmote":
|
||||
case "GCosmeticCard":
|
||||
case "GCosmeticSkin":
|
||||
case "GStoreOffer":
|
||||
case "GAccolade":
|
||||
case "GRuneItem":
|
||||
case "GQuest":
|
||||
creator = new BaseSpellIcon(_object, EIconStyle.Default);
|
||||
return true;
|
||||
case "GLeagueTier":
|
||||
creator = new BaseLeague(_object, EIconStyle.Default);
|
||||
return true;
|
||||
case "GLeagueDivision":
|
||||
creator = new BaseDivision(_object, EIconStyle.Default);
|
||||
return true;
|
||||
default:
|
||||
creator = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"{_object.ExportType} | {_style}";
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_object = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ public class Typefaces
|
|||
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
|
||||
private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
|
||||
private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset";
|
||||
private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite
|
||||
private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji";
|
||||
private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
|
||||
private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
|
||||
|
|
@ -31,7 +32,7 @@ public class Typefaces
|
|||
private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite
|
||||
private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold";
|
||||
private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
|
||||
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold"; // japanese fortnite
|
||||
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold";
|
||||
private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
|
||||
private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite
|
||||
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
|
||||
|
|
@ -48,6 +49,23 @@ public class Typefaces
|
|||
private const string _XIANGHEHEI_SC_PRO_BLACK = "XiangHeHei_SC/MXiangHeHeiSCPro-Black";
|
||||
private const string _XIANGHEHEI_SC_PRO_HEAVY = "XiangHeHei_SC/MXiangHeHeiSCPro-Heavy";
|
||||
|
||||
// Spellbreak
|
||||
private const string _SPELLBREAK_BASE_PATH = "/Game/UI/Fonts/";
|
||||
private const string _MONTSERRAT_SEMIBOLD = "Montserrat-Semibold";
|
||||
private const string _MONTSERRAT_SEMIBOLD_ITALIC = "Montserrat-SemiBoldItalic";
|
||||
private const string _NANUM_GOTHIC = "NanumGothic";
|
||||
private const string _QUADRAT_BOLD = "Quadrat_Bold";
|
||||
private const string _SEGOE_BOLD_ITALIC = "Segoe_Bold_Italic";
|
||||
|
||||
// WorldExplorers
|
||||
private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/";
|
||||
private const string _HEMIHEAD426 = "HemiHead426";
|
||||
private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular";
|
||||
private const string _LATO_BLACK = "Lato-Black";
|
||||
private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic";
|
||||
private const string _LATO_LIGHT = "Lato-Light";
|
||||
private const string _LATO_MEDIUM = "Lato-Medium";
|
||||
|
||||
private readonly CUE4ParseViewModel _viewModel;
|
||||
|
||||
public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...)
|
||||
|
|
@ -67,16 +85,16 @@ public class Typefaces
|
|||
|
||||
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
|
||||
|
||||
switch (viewModel.Provider.InternalGameName.ToUpperInvariant())
|
||||
switch (viewModel.Game)
|
||||
{
|
||||
case "FORTNITEGAME":
|
||||
case FGame.FortniteGame:
|
||||
{
|
||||
DisplayName = OnTheFly(_FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
|
|
@ -112,7 +130,7 @@ public class Typefaces
|
|||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
|
|
@ -124,7 +142,7 @@ public class Typefaces
|
|||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
|
|
@ -135,7 +153,7 @@ public class Typefaces
|
|||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
|
|
@ -146,7 +164,7 @@ public class Typefaces
|
|||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
|
|
@ -154,7 +172,36 @@ public class Typefaces
|
|||
} + _EXT);
|
||||
break;
|
||||
}
|
||||
case "MULTIVERSUS":
|
||||
case FGame.WorldExplorers:
|
||||
{
|
||||
DisplayName = OnTheFly(_BATTLE_BREAKERS_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
|
||||
ELanguage.Russian => _LATO_BLACK,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
|
||||
_ => _HEMIHEAD426
|
||||
} + _EXT);
|
||||
|
||||
Description = OnTheFly(_BATTLE_BREAKERS_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
|
||||
ELanguage.Russian => _LATO_BLACK,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
|
||||
_ => _HEMIHEAD426
|
||||
} + _EXT);
|
||||
break;
|
||||
}
|
||||
case FGame.g3:
|
||||
{
|
||||
DisplayName = OnTheFly(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD + _EXT);
|
||||
Description = OnTheFly(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD + _EXT);
|
||||
break;
|
||||
}
|
||||
case FGame.MultiVersus:
|
||||
{
|
||||
DisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ using CUE4Parse.UE4.Assets.Exports.Texture;
|
|||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using FModel.Framework;
|
||||
using FModel.Extensions;
|
||||
using FModel.Services;
|
||||
|
|
@ -72,7 +71,6 @@ public static class Utils
|
|||
return GetBitmap(material);
|
||||
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 string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
|
||||
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)
|
||||
{
|
||||
if (material == null) return null;
|
||||
|
|
@ -129,7 +112,7 @@ public static class Utils
|
|||
public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) { Position = 0 });
|
||||
public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text);
|
||||
public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null;
|
||||
public static SKBitmap GetBitmap(UTexture2D texture) => texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform);
|
||||
public static SKBitmap GetBitmap(UTexture2D texture) => texture.IsVirtual ? null : texture.Decode(UserSettings.Default.OverridedPlatform);
|
||||
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
|
||||
|
||||
public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height)
|
||||
|
|
@ -221,6 +204,9 @@ public static class Utils
|
|||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string FixPath(string weirdPath) =>
|
||||
_applicationView.CUE4Parse.Provider.FixPath(weirdPath, StringComparison.Ordinal);
|
||||
|
||||
public static void DrawCenteredMultilineText(SKCanvas c, string text, int maxCount, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint)
|
||||
{
|
||||
var lineHeight = paint.TextSize * 1.2f;
|
||||
|
|
@ -247,8 +233,8 @@ public static class Utils
|
|||
y += lineHeight;
|
||||
var x = side switch
|
||||
{
|
||||
SKTextAlign.Center => area.MidX - shapedText.Width / 2,
|
||||
SKTextAlign.Right => size - margin - shapedText.Width,
|
||||
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
|
||||
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
|
||||
SKTextAlign.Left => margin,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
|
@ -280,8 +266,8 @@ public static class Utils
|
|||
|
||||
var x = side switch
|
||||
{
|
||||
SKTextAlign.Center => area.MidX - shapedText.Width / 2,
|
||||
SKTextAlign.Right => size - margin - shapedText.Width,
|
||||
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
|
||||
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
|
||||
SKTextAlign.Left => area.Left,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
|
@ -417,4 +403,4 @@ public static class Utils
|
|||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ public enum EErrorKind
|
|||
public enum SettingsOut
|
||||
{
|
||||
ReloadLocres,
|
||||
ReloadMappings
|
||||
ReloadMappings,
|
||||
CheckForUpdates
|
||||
}
|
||||
|
||||
public enum EStatusKind
|
||||
|
|
@ -51,8 +52,60 @@ public enum EDiscordRpc
|
|||
Never
|
||||
}
|
||||
|
||||
public enum FGame
|
||||
{
|
||||
[Description("Unknown")]
|
||||
Unknown,
|
||||
[Description("Fortnite")]
|
||||
FortniteGame,
|
||||
[Description("Valorant")]
|
||||
ShooterGame,
|
||||
[Description("Dead By Daylight")]
|
||||
DeadByDaylight,
|
||||
[Description("Borderlands 3")]
|
||||
OakGame,
|
||||
[Description("Minecraft Dungeons")]
|
||||
Dungeons,
|
||||
[Description("Battle Breakers")]
|
||||
WorldExplorers,
|
||||
[Description("Spellbreak")]
|
||||
g3,
|
||||
[Description("State Of Decay 2")]
|
||||
StateOfDecay2,
|
||||
[Description("The Cycle")]
|
||||
Prospect,
|
||||
[Description("The Outer Worlds")]
|
||||
Indiana,
|
||||
[Description("Rogue Company")]
|
||||
RogueCompany,
|
||||
[Description("Star Wars: Jedi Fallen Order")]
|
||||
SwGame,
|
||||
[Description("Core")]
|
||||
Platform,
|
||||
[Description("Days Gone")]
|
||||
BendGame,
|
||||
[Description("PLAYERUNKNOWN'S BATTLEGROUNDS")]
|
||||
TslGame,
|
||||
[Description("Splitgate")]
|
||||
PortalWars,
|
||||
[Description("GTA: The Trilogy - Definitive Edition")]
|
||||
Gameface,
|
||||
[Description("Sea of Thieves")]
|
||||
Athena,
|
||||
[Description("DEPRECATED")]
|
||||
PandaGame,
|
||||
[Description("MultiVersus")]
|
||||
MultiVersus,
|
||||
[Description("Tower of Fantasy")]
|
||||
Hotta,
|
||||
[Description("eFootball 2023")]
|
||||
eFootball
|
||||
}
|
||||
|
||||
public enum ELoadingMode
|
||||
{
|
||||
[Description("Single")]
|
||||
Single,
|
||||
[Description("Multiple")]
|
||||
Multiple,
|
||||
[Description("All")]
|
||||
|
|
@ -63,15 +116,13 @@ public enum ELoadingMode
|
|||
AllButModified
|
||||
}
|
||||
|
||||
// public enum EUpdateMode
|
||||
// {
|
||||
// [Description("Stable")]
|
||||
// Stable,
|
||||
// [Description("Beta")]
|
||||
// Beta,
|
||||
// [Description("QA Testing")]
|
||||
// Qa
|
||||
// }
|
||||
public enum EUpdateMode
|
||||
{
|
||||
[Description("Stable")]
|
||||
Stable,
|
||||
[Description("Beta")]
|
||||
Beta
|
||||
}
|
||||
|
||||
public enum ECompressedAudio
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,21 +11,8 @@ public static class EnumExtensions
|
|||
{
|
||||
var fi = value.GetType().GetField(value.ToString());
|
||||
if (fi == null) return $"{value} ({value:D})";
|
||||
|
||||
var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
if (attributes.Length > 0) return attributes[0].Description;
|
||||
|
||||
|
||||
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})";
|
||||
return attributes.Length > 0 ? attributes[0].Description : $"{value} ({value:D})";
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
|
@ -56,4 +43,4 @@ public static class EnumExtensions
|
|||
var i = Array.IndexOf(values, value) - 1;
|
||||
return i == -1 ? (T) values.GetValue(values.Length - 1) : (T) values.GetValue(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
|
||||
namespace FModel.Extensions;
|
||||
|
||||
|
|
@ -95,7 +94,7 @@ public static class StringExtensions
|
|||
}
|
||||
|
||||
[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))
|
||||
return s.GetLineNumber(index);
|
||||
|
|
@ -114,25 +113,6 @@ public static class StringExtensions
|
|||
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)]
|
||||
public static int GetKismetLineNumber(this string s, string input)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon>FModel.ico</ApplicationIcon>
|
||||
<Version>4.4.4.0</Version>
|
||||
<AssemblyVersion>4.4.4.0</AssemblyVersion>
|
||||
<FileVersion>4.4.4.0</FileVersion>
|
||||
<Version>4.4.3.0</Version>
|
||||
<AssemblyVersion>4.4.3.0</AssemblyVersion>
|
||||
<FileVersion>4.4.3.0</FileVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsPublishable>true</IsPublishable>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
|
@ -45,12 +45,6 @@
|
|||
<None Remove="Resources\gear.png" />
|
||||
<None Remove="Resources\localization.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\puzzle.png" />
|
||||
<None Remove="Resources\roguecompany.png" />
|
||||
|
|
@ -83,7 +77,6 @@
|
|||
<None Remove="Resources\linux.png" />
|
||||
<None Remove="Resources\stateofdecay2.png" />
|
||||
<None Remove="Resources\T_Placeholder_Item_Image.png" />
|
||||
<None Remove="Resources\checker.png" />
|
||||
<None Remove="Resources\T_ClipSize_Weapon_Stats.png" />
|
||||
<None Remove="Resources\T_DamagePerBullet_Weapon_Stats.png" />
|
||||
<None Remove="Resources\T_ReloadTime_Weapon_Stats.png" />
|
||||
|
|
@ -114,9 +107,6 @@
|
|||
<None Remove="Resources\picking.vert" />
|
||||
<None Remove="Resources\light.frag" />
|
||||
<None Remove="Resources\light.vert" />
|
||||
<None Remove="Resources\bone.frag" />
|
||||
<None Remove="Resources\bone.vert" />
|
||||
<None Remove="Resources\collision.vert" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -140,36 +130,34 @@
|
|||
<EmbeddedResource Include="Resources\picking.vert" />
|
||||
<EmbeddedResource Include="Resources\light.frag" />
|
||||
<EmbeddedResource Include="Resources\light.vert" />
|
||||
<EmbeddedResource Include="Resources\bone.frag" />
|
||||
<EmbeddedResource Include="Resources\bone.vert" />
|
||||
<EmbeddedResource Include="Resources\collision.vert" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AdonisUI" Version="1.17.1" />
|
||||
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
|
||||
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.6" />
|
||||
<PackageReference Include="AvalonEdit" Version="6.2.0.78" />
|
||||
<PackageReference Include="CSCore" Version="1.2.1.2" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageReference Include="EpicManifestParser" Version="2.3.3" />
|
||||
<PackageReference Include="ImGui.NET" Version="1.91.0.1" />
|
||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||
<PackageReference Include="ImGui.NET" Version="1.89.4" />
|
||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.5" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<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="OpenTK" Version="4.8.2" />
|
||||
<PackageReference Include="RestSharp" Version="112.1.0" />
|
||||
<PackageReference Include="Serilog" Version="4.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.8" />
|
||||
<PackageReference Include="OpenTK" Version="4.7.7" />
|
||||
<PackageReference Include="RestSharp" Version="108.0.3" />
|
||||
<PackageReference Include="Serilog" Version="2.12.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.0" />
|
||||
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj" />
|
||||
<ProjectReference Include="..\CUE4Parse\CUE4Parse\CUE4Parse.csproj" />
|
||||
<ProjectReference Include="..\EpicManifestParser\src\EpicManifestParser\EpicManifestParser.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -189,12 +177,6 @@
|
|||
<Resource Include="Resources\gear.png" />
|
||||
<Resource Include="Resources\localization.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\puzzle.png" />
|
||||
<Resource Include="Resources\roguecompany.png" />
|
||||
|
|
@ -221,7 +203,6 @@
|
|||
<Resource Include="Resources\linux.png" />
|
||||
<Resource Include="Resources\stateofdecay2.png" />
|
||||
<Resource Include="Resources\T_Placeholder_Item_Image.png" />
|
||||
<Resource Include="Resources\checker.png" />
|
||||
<Resource Include="Resources\T_ClipSize_Weapon_Stats.png" />
|
||||
<Resource Include="Resources\T_DamagePerBullet_Weapon_Stats.png" />
|
||||
<Resource Include="Resources\T_ReloadTime_Weapon_Stats.png" />
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse", "..\CUE4Parse\C
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Conversion", "..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj", "{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EpicManifestParser", "..\EpicManifestParser\src\EpicManifestParser\EpicManifestParser.csproj", "{D4958A8B-815B-421D-A988-2A4E8E2B582D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public class CustomSKShaper : SKShaper
|
|||
points[i] = new SKPoint(xOffset + pos[i].XOffset * textSizeX, yOffset - pos[i].YOffset * textSizeY);
|
||||
}
|
||||
|
||||
return new Result(codepoints, clusters, points, points[^1].X);
|
||||
return new Result(codepoints, clusters, points);
|
||||
}
|
||||
|
||||
public new Result Shape(string text, SKPaint paint) => Shape(text, 0, 0, paint);
|
||||
|
|
@ -87,4 +87,4 @@ public class CustomSKShaper : SKShaper
|
|||
buffer.GuessSegmentProperties();
|
||||
return Shape(buffer, xOffset, yOffset, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +1,92 @@
|
|||
using System.Linq;
|
||||
using FModel.Framework;
|
||||
using FModel.ViewModels.ApiEndpoints;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace FModel.Settings;
|
||||
|
||||
public class EndpointSettings : ViewModel
|
||||
{
|
||||
public static EndpointSettings[] Default(string gameName)
|
||||
{
|
||||
switch (gameName)
|
||||
{
|
||||
case "Fortnite":
|
||||
case "Fortnite [LIVE]":
|
||||
return new EndpointSettings[]
|
||||
{
|
||||
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)
|
||||
};
|
||||
default:
|
||||
return new EndpointSettings[] { new(), new() };
|
||||
}
|
||||
}
|
||||
|
||||
private string _url;
|
||||
public string Url
|
||||
{
|
||||
get => _url;
|
||||
set => SetProperty(ref _url, value);
|
||||
}
|
||||
|
||||
private string _path;
|
||||
public string Path
|
||||
{
|
||||
get => _path;
|
||||
set => SetProperty(ref _path, value);
|
||||
}
|
||||
|
||||
private bool _overwrite;
|
||||
public bool Overwrite
|
||||
{
|
||||
get => _overwrite;
|
||||
set => SetProperty(ref _overwrite, value);
|
||||
}
|
||||
|
||||
private string _filePath;
|
||||
public string FilePath
|
||||
{
|
||||
get => _filePath;
|
||||
set => SetProperty(ref _filePath, value);
|
||||
}
|
||||
|
||||
private bool _isValid;
|
||||
public bool IsValid
|
||||
{
|
||||
get => _isValid;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _isValid, value);
|
||||
RaisePropertyChanged(nameof(Label));
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string Label => IsValid ?
|
||||
"Your endpoint configuration is valid! Please, avoid any unnecessary modifications!" :
|
||||
"Your endpoint configuration DOES NOT seem to be valid yet! Please, test it out!";
|
||||
|
||||
public EndpointSettings() {}
|
||||
public EndpointSettings(string url, string path)
|
||||
{
|
||||
Url = url;
|
||||
Path = path;
|
||||
IsValid = !string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(path); // be careful with this
|
||||
}
|
||||
|
||||
public void TryValidate(DynamicApiEndpoint endpoint, EEndpointType type, out JToken response)
|
||||
{
|
||||
response = null;
|
||||
if (string.IsNullOrEmpty(Url) || string.IsNullOrEmpty(Path))
|
||||
{
|
||||
IsValid = false;
|
||||
}
|
||||
else switch (type)
|
||||
{
|
||||
case EEndpointType.Aes:
|
||||
{
|
||||
var r = endpoint.GetAesKeys(default, Url, Path);
|
||||
response = JToken.FromObject(r);
|
||||
IsValid = r.IsValid;
|
||||
break;
|
||||
}
|
||||
case EEndpointType.Mapping:
|
||||
{
|
||||
var r = endpoint.GetMappings(default, Url, Path);
|
||||
response = JToken.FromObject(r);
|
||||
IsValid = r.Any(x => x.IsValid);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
IsValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Linq;
|
||||
using FModel.ViewModels.ApiEndpoints;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace FModel.Framework;
|
||||
|
||||
public class FEndpoint : ViewModel
|
||||
{
|
||||
private string _url;
|
||||
public string Url
|
||||
{
|
||||
get => _url;
|
||||
set => SetProperty(ref _url, value);
|
||||
}
|
||||
|
||||
private string _path;
|
||||
public string Path
|
||||
{
|
||||
get => _path;
|
||||
set => SetProperty(ref _path, value);
|
||||
}
|
||||
|
||||
private bool _overwrite;
|
||||
public bool Overwrite
|
||||
{
|
||||
get => _overwrite;
|
||||
set => SetProperty(ref _overwrite, value);
|
||||
}
|
||||
|
||||
private string _filePath;
|
||||
public string FilePath
|
||||
{
|
||||
get => _filePath;
|
||||
set => SetProperty(ref _filePath, value);
|
||||
}
|
||||
|
||||
private bool _isValid;
|
||||
public bool IsValid
|
||||
{
|
||||
get => _isValid;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _isValid, value);
|
||||
RaisePropertyChanged(nameof(Label));
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string Label => IsValid ?
|
||||
"Your endpoint configuration is valid! Please, avoid any unnecessary modifications!" :
|
||||
"Your endpoint configuration DOES NOT seem to be valid yet! Please, test it out!";
|
||||
|
||||
public FEndpoint() {}
|
||||
public FEndpoint(string url, string path)
|
||||
{
|
||||
Url = url;
|
||||
Path = path;
|
||||
IsValid = !string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(path); // be careful with this
|
||||
}
|
||||
|
||||
public void TryValidate(DynamicApiEndpoint endpoint, EEndpointType type, out JToken response)
|
||||
{
|
||||
response = null;
|
||||
if (string.IsNullOrEmpty(Url) || string.IsNullOrEmpty(Path))
|
||||
{
|
||||
IsValid = false;
|
||||
}
|
||||
else switch (type)
|
||||
{
|
||||
case EEndpointType.Aes:
|
||||
{
|
||||
var r = endpoint.GetAesKeys(default, Url, Path);
|
||||
response = JToken.FromObject(r);
|
||||
IsValid = r.IsValid;
|
||||
break;
|
||||
}
|
||||
case EEndpointType.Mapping:
|
||||
{
|
||||
var r = endpoint.GetMappings(default, Url, Path);
|
||||
response = JToken.FromObject(r);
|
||||
IsValid = r.Any(x => x.IsValid);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
IsValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,19 @@
|
|||
using System;
|
||||
using System;
|
||||
using RestSharp;
|
||||
|
||||
namespace FModel.Framework;
|
||||
|
||||
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)
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(TimeoutSeconds);
|
||||
Timeout = _timeout;
|
||||
}
|
||||
|
||||
public FRestRequest(Uri uri, Method method = Method.Get) : base(uri, method)
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(TimeoutSeconds);
|
||||
Timeout = _timeout;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ public class FStatus : ViewModel
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using FModel.Settings;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
|
|
@ -37,6 +34,7 @@ public class ImGuiController : IDisposable
|
|||
|
||||
private int _windowWidth;
|
||||
private int _windowHeight;
|
||||
// private string _iniPath;
|
||||
|
||||
public ImFontPtr FontNormal;
|
||||
public ImFontPtr FontBold;
|
||||
|
|
@ -51,6 +49,7 @@ public class ImGuiController : IDisposable
|
|||
{
|
||||
_windowWidth = width;
|
||||
_windowHeight = height;
|
||||
// _iniPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini");
|
||||
|
||||
int major = GL.GetInteger(GetPName.MajorVersion);
|
||||
int minor = GL.GetInteger(GetPName.MinorVersion);
|
||||
|
|
@ -59,13 +58,9 @@ public class ImGuiController : IDisposable
|
|||
|
||||
IntPtr context = ImGui.CreateContext();
|
||||
ImGui.SetCurrentContext(context);
|
||||
// ImGui.LoadIniSettingsFromDisk(_iniPath);
|
||||
|
||||
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);
|
||||
FontBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.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;
|
||||
|
||||
CreateDeviceResources();
|
||||
SetKeyMappings();
|
||||
|
||||
SetPerFrameImGuiData(1f / 60f);
|
||||
|
||||
|
|
@ -275,8 +271,8 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
|||
|
||||
foreach (Keys key in Enum.GetValues(typeof(Keys)))
|
||||
{
|
||||
if (key == Keys.Unknown) continue;
|
||||
io.AddKeyEvent(TranslateKey(key), kState.IsKeyDown(key));
|
||||
if (key == Keys.Unknown || io.KeyMap[(int) key] == -1) continue;
|
||||
io.AddKeyEvent((ImGuiKey) io.KeyMap[(int) key], kState.IsKeyDown(key));
|
||||
}
|
||||
|
||||
foreach (var c in PressedChars)
|
||||
|
|
@ -296,6 +292,115 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
|||
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)
|
||||
{
|
||||
if (draw_data.CmdListsCount == 0)
|
||||
|
|
@ -335,7 +440,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
|||
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBuffer);
|
||||
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>();
|
||||
if (vertexSize > _vertexBufferSize)
|
||||
|
|
@ -385,7 +490,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
|||
// Render command lists
|
||||
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);
|
||||
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));
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ public class JsonNetSerializer : IRestSerializer, ISerializer, IDeserializer
|
|||
public ISerializer Serializer => this;
|
||||
public IDeserializer Deserializer => this;
|
||||
|
||||
public ContentType ContentType { get; set; } = ContentType.Json;
|
||||
public string[] AcceptedContentTypes => ContentType.JsonAccept;
|
||||
public SupportsContentType SupportsContentType => contentType => contentType.Value.EndsWith("json", StringComparison.InvariantCultureIgnoreCase);
|
||||
public string ContentType { get; set; } = "application/json";
|
||||
public string[] AcceptedContentTypes => RestSharp.Serializers.ContentType.JsonAccept;
|
||||
public SupportsContentType SupportsContentType => contentType => contentType.EndsWith("json", StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
public DataFormat DataFormat => DataFormat.Json;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,30 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
namespace FModel;
|
||||
|
||||
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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
return string.Empty;
|
||||
|
||||
var keySpan = key.AsSpan().Trim();
|
||||
if (keySpan.Length > sizeof(char) * (2 /* 0x */ + 32 /* FAES = 256 bit */)) // maybe strictly check for length?
|
||||
return string.Empty; // bullshit key
|
||||
if (key.StartsWith("0x"))
|
||||
key = key[2..];
|
||||
|
||||
Span<char> resultSpan = stackalloc char[keySpan.Length + 2 /* pad for 0x */];
|
||||
keySpan.ToUpperInvariant(resultSpan[2..]);
|
||||
|
||||
if (resultSpan[2..].StartsWith("0X"))
|
||||
resultSpan = resultSpan[2..];
|
||||
else
|
||||
resultSpan[0] = '0';
|
||||
|
||||
resultSpan[1] = 'x';
|
||||
|
||||
return new string(resultSpan);
|
||||
return "0x" + key.ToUpper().Trim();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var ulongValue = Unsafe.As<double, ulong>(ref value);
|
||||
var exp = ulongValue & 0xfff0000000000000;
|
||||
var man = ulongValue & 0x000fffffffffffff;
|
||||
var t = new NanUnion { DoubleValue = value };
|
||||
var exp = t.UlongValue & 0xfff0000000000000;
|
||||
var man = t.UlongValue & 0x000fffffffffffff;
|
||||
return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0;
|
||||
}
|
||||
|
||||
|
|
@ -98,17 +96,13 @@ public static class Helper
|
|||
return -d < n && d > n;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float DegreesToRadians(float degrees)
|
||||
{
|
||||
const float ratio = MathF.PI / 180f;
|
||||
return ratio * degrees;
|
||||
return MathF.PI / 180f * degrees;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float RadiansToDegrees(float radians)
|
||||
{
|
||||
const float ratio = 180f / MathF.PI;
|
||||
return radians * ratio;
|
||||
return radians* 180f / MathF.PI;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Favorite Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding Status.IsReady}">
|
||||
<MenuItem Header="Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding Status.IsReady}">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -126,6 +126,25 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Map Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_MapViewer" IsEnabled="{Binding Status.IsReady}">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource MapIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Style>
|
||||
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding CUE4Parse.Game}" Value="{x:Static local:FGame.FortniteGame}">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</MenuItem.Style>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Image Merger" Command="{Binding MenuCommand}" CommandParameter="Views_ImageMerger">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
|
|
@ -147,11 +166,11 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Releases" Command="{Binding MenuCommand}" CommandParameter="Help_Releases">
|
||||
<MenuItem Header="Changelog" Command="{Binding MenuCommand}" CommandParameter="Help_Changelog">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<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>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
|
|
@ -321,16 +340,16 @@
|
|||
<TreeView Grid.Row="2" x:Name="AssetsFolderName" Style="{StaticResource AssetsFolderTreeView}" PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick">
|
||||
<TreeView.ContextMenu>
|
||||
<ContextMenu>
|
||||
<!-- <MenuItem Header="Extract Folder's Packages" Click="OnFolderExtractClick"> -->
|
||||
<!-- <MenuItem.Icon> -->
|
||||
<!-- <Viewbox Width="16" Height="16"> -->
|
||||
<!-- <Canvas Width="24" Height="24"> -->
|
||||
<!-- <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExtractIcon}" /> -->
|
||||
<!-- </Canvas> -->
|
||||
<!-- </Viewbox> -->
|
||||
<!-- </MenuItem.Icon> -->
|
||||
<!-- </MenuItem> -->
|
||||
<!-- <Separator /> -->
|
||||
<MenuItem Header="Extract Folder's Packages" Click="OnFolderExtractClick">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExtractIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)" Click="OnFolderExportClick">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
|
|
@ -349,7 +368,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Folder's Packages Textures" Click="OnFolderTextureClick">
|
||||
<MenuItem Header="Save Folder's Packages Textures (.png)" Click="OnFolderTextureClick">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -358,7 +377,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Folder's Packages Models" Click="OnFolderModelClick">
|
||||
<MenuItem Header="Save Folder's Packages Models (.psk)" Click="OnFolderModelClick">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -367,7 +386,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Folder's Packages Animations" Click="OnFolderAnimationClick">
|
||||
<MenuItem Header="Save Folder's Packages Animations (.psa)" Click="OnFolderAnimationClick">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -377,7 +396,7 @@
|
|||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Favorite Directory" Click="OnFavoriteDirectoryClick">
|
||||
<MenuItem Header="Save Directory" Click="OnSaveDirectoryClick">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -517,7 +536,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Texture" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem Header="Save Texture (.png)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Textures" />
|
||||
|
|
@ -532,7 +551,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Model" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem Header="Save Model (.psk)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Models" />
|
||||
|
|
@ -547,7 +566,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Animation" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem Header="Save Animation (.psa)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Animations" />
|
||||
|
|
@ -797,17 +816,13 @@
|
|||
<Style TargetType="StatusBarItem">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" Value="False">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Setter Property="Visibility" Value="Hidden" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</StatusBarItem.Style>
|
||||
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="SND" />
|
||||
</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>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public partial class MainWindow
|
|||
|
||||
private void OnClosing(object sender, CancelEventArgs e)
|
||||
{
|
||||
_applicationView.CustomDirectories.Save();
|
||||
_discordHandler.Dispose();
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +48,7 @@ public partial class MainWindow
|
|||
{
|
||||
var newOrUpdated = UserSettings.Default.ShowChangelog;
|
||||
#if !DEBUG
|
||||
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(true);
|
||||
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
|
||||
#endif
|
||||
|
||||
switch (UserSettings.Default.AesReload)
|
||||
|
|
@ -55,14 +56,12 @@ public partial class MainWindow
|
|||
case EAesReload.Always:
|
||||
await _applicationView.CUE4Parse.RefreshAes();
|
||||
break;
|
||||
case EAesReload.OncePerDay when UserSettings.Default.CurrentDir.LastAesReload != DateTime.Today:
|
||||
UserSettings.Default.CurrentDir.LastAesReload = DateTime.Today;
|
||||
case EAesReload.OncePerDay when UserSettings.Default.LastAesReload != DateTime.Today:
|
||||
UserSettings.Default.LastAesReload = DateTime.Today;
|
||||
await _applicationView.CUE4Parse.RefreshAes();
|
||||
break;
|
||||
}
|
||||
|
||||
await ApplicationViewModel.InitOodle();
|
||||
await ApplicationViewModel.InitZlib();
|
||||
await _applicationView.CUE4Parse.Initialize();
|
||||
await _applicationView.AesManager.InitAes();
|
||||
await _applicationView.UpdateProvider(true);
|
||||
|
|
@ -71,10 +70,12 @@ public partial class MainWindow
|
|||
#endif
|
||||
await Task.WhenAll(
|
||||
_applicationView.CUE4Parse.VerifyConsoleVariables(),
|
||||
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
|
||||
_applicationView.CUE4Parse.VerifyVirtualCache(),
|
||||
_applicationView.CUE4Parse.VerifyContentBuildManifest(),
|
||||
_applicationView.CUE4Parse.InitMappings(),
|
||||
ApplicationViewModel.InitVgmStream(),
|
||||
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
|
||||
_applicationView.InitImGuiSettings(newOrUpdated),
|
||||
_applicationView.InitVgmStream(),
|
||||
_applicationView.InitOodle(),
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
|
||||
|
|
@ -85,10 +86,7 @@ public partial class MainWindow
|
|||
#if DEBUG
|
||||
// await _threadWorkerView.Begin(cancellationToken =>
|
||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
// "FortniteGame/Content/Athena/Apollo/Maps/UI/Apollo_Terrain_Minimap.uasset"));
|
||||
// await _threadWorkerView.Begin(cancellationToken =>
|
||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
// "FortniteGame/Content/Environments/Helios/Props/GlacierHotel/GlacierHotel_Globe_A/Meshes/SM_GlacierHotel_Globe_A.uasset"));
|
||||
// "fortnitegame/Content/Characters/Player/Female/Medium/Bodies/F_MED_Ballerina/Meshes/F_MED_Ballerina.uasset"));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +140,7 @@ public partial class MainWindow
|
|||
|
||||
private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
await _applicationView.CUE4Parse.InitMappings(true);
|
||||
await _applicationView.CUE4Parse.InitMappings();
|
||||
}
|
||||
|
||||
private void OnOpenAvalonFinder()
|
||||
|
|
@ -230,13 +228,13 @@ public partial class MainWindow
|
|||
}
|
||||
}
|
||||
|
||||
private void OnFavoriteDirectoryClick(object sender, RoutedEventArgs e)
|
||||
private void OnSaveDirectoryClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
|
||||
|
||||
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new favorite directory", Constants.WHITE, true));
|
||||
FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new custom directory", Constants.WHITE, true));
|
||||
}
|
||||
|
||||
private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e)
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
#version 460 core
|
||||
|
||||
in vec3 fPos;
|
||||
in vec3 fColor;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = vec4(fColor, 1.0);
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
#version 460 core
|
||||
|
||||
layout (location = 0) in vec3 vPos;
|
||||
layout (location = 1) in vec3 vColor;
|
||||
|
||||
uniform mat4 uView;
|
||||
uniform mat4 uProjection;
|
||||
uniform mat4 uInstanceMatrix;
|
||||
|
||||
out vec3 fPos;
|
||||
out vec3 fColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_PointSize = 7.5f;
|
||||
gl_Position = uProjection * uView * uInstanceMatrix * vec4(vPos, 1.0);
|
||||
fPos = vec3(uInstanceMatrix * vec4(vPos, 1.0));
|
||||
fColor = vColor;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 125 KiB |
|
|
@ -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);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 831 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
|
@ -8,7 +8,7 @@ in vec3 fPos;
|
|||
in vec3 fNormal;
|
||||
in vec3 fTangent;
|
||||
in vec2 fTexCoords;
|
||||
flat in int fTexLayer;
|
||||
in float fTexLayer;
|
||||
in vec4 fColor;
|
||||
|
||||
struct Texture
|
||||
|
|
@ -89,7 +89,6 @@ uniform Parameters uParameters;
|
|||
uniform Light uLights[MAX_LIGHT_COUNT];
|
||||
uniform int uNumLights;
|
||||
uniform int uUvCount;
|
||||
uniform float uOpacity;
|
||||
uniform bool uHasVertexColors;
|
||||
uniform vec3 uSectionColor;
|
||||
uniform bool bVertexColors[6];
|
||||
|
|
@ -99,7 +98,7 @@ out vec4 FragColor;
|
|||
|
||||
int LayerToIndex()
|
||||
{
|
||||
return clamp(fTexLayer, 0, uUvCount - 1);
|
||||
return clamp(int(fTexLayer), 0, uUvCount - 1);
|
||||
}
|
||||
|
||||
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 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);
|
||||
|
||||
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 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;
|
||||
float specBrdfDenom = 4.0 * nDotV * nDotL + 0.0001;
|
||||
|
|
@ -218,32 +212,30 @@ vec3 CalcSpotLight(int layer, vec3 normals, Light light)
|
|||
|
||||
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])
|
||||
{
|
||||
FragColor = vec4(diffuseFactor * uSectionColor, uOpacity);
|
||||
FragColor = vec4(uSectionColor, 1.0);
|
||||
}
|
||||
else if (bVertexColors[2] && uHasVertexColors)
|
||||
{
|
||||
FragColor = vec4(diffuseFactor * fColor.rgb, fColor.a);
|
||||
FragColor = fColor;
|
||||
}
|
||||
else if (bVertexColors[3])
|
||||
{
|
||||
FragColor = vec4(normals, uOpacity);
|
||||
int layer = LayerToIndex();
|
||||
vec3 normals = ComputeNormals(layer);
|
||||
FragColor = vec4(normals, 1.0);
|
||||
}
|
||||
else if (bVertexColors[4])
|
||||
{
|
||||
vec4 diffuse = SamplerToVector(uParameters.Diffuse[0].Sampler);
|
||||
FragColor = vec4(diffuseFactor * diffuse.rgb, diffuse.a);
|
||||
FragColor = vec4(fTexCoords, 0.0, 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
int layer = LayerToIndex();
|
||||
vec3 normals = ComputeNormals(layer);
|
||||
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)
|
||||
{
|
||||
|
|
@ -253,7 +245,7 @@ void main()
|
|||
vec3 color = uParameters.Ao.ColorBoost.Color * uParameters.Ao.ColorBoost.Exponent;
|
||||
result = mix(result, result * color, m.b);
|
||||
}
|
||||
result *= m.r;
|
||||
result = vec3(uParameters.Ao.AmbientOcclusion) * result * m.r;
|
||||
}
|
||||
|
||||
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);
|
||||
for (int i = 0; i < uNumLights; i++)
|
||||
|
|
@ -289,6 +281,6 @@ void main()
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ layout (location = 1) in vec3 vPos;
|
|||
layout (location = 2) in vec3 vNormal;
|
||||
layout (location = 3) in vec3 vTangent;
|
||||
layout (location = 4) in vec2 vTexCoords;
|
||||
layout (location = 5) in int vTexLayer;
|
||||
layout (location = 6) in float vColor;
|
||||
layout (location = 7) in vec4 vBoneInfluence;
|
||||
layout (location = 8) in vec4 vBoneInfluenceExtra;
|
||||
layout (location = 5) in float vTexLayer;
|
||||
layout (location = 6) in vec4 vColor;
|
||||
layout (location = 7) in vec4 vBoneIds;
|
||||
layout (location = 8) in vec4 vBoneWeights;
|
||||
layout (location = 9) in mat4 vInstanceMatrix;
|
||||
layout (location = 13) in vec3 vMorphTargetPos;
|
||||
layout (location = 14) in vec3 vMorphTargetTangent;
|
||||
|
|
@ -16,37 +16,18 @@ layout(std430, binding = 1) buffer BoneMatrices
|
|||
{
|
||||
mat4 uFinalBonesMatrix[];
|
||||
};
|
||||
layout(std430, binding = 2) buffer RestBoneMatrices
|
||||
{
|
||||
mat4 uRestBonesMatrix[];
|
||||
};
|
||||
|
||||
uniform mat4 uView;
|
||||
uniform mat4 uProjection;
|
||||
uniform float uMorphTime;
|
||||
uniform bool uIsAnimated;
|
||||
|
||||
out vec3 fPos;
|
||||
out vec3 fNormal;
|
||||
out vec3 fTangent;
|
||||
out vec2 fTexCoords;
|
||||
flat out int fTexLayer;
|
||||
out float fTexLayer;
|
||||
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()
|
||||
{
|
||||
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
|
||||
|
|
@ -56,30 +37,21 @@ void main()
|
|||
vec4 finalPos = vec4(0.0);
|
||||
vec4 finalNormal = vec4(0.0);
|
||||
vec4 finalTangent = vec4(0.0);
|
||||
if (uIsAnimated)
|
||||
if (vBoneIds != vBoneWeights)
|
||||
{
|
||||
vec4 boneInfluences[2];
|
||||
boneInfluences[0] = vBoneInfluence;
|
||||
boneInfluences[1] = vBoneInfluenceExtra;
|
||||
for (int i = 0; i < 2; i++)
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
{
|
||||
for(int j = 0 ; j < 4; j++)
|
||||
{
|
||||
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
|
||||
int boneIndex = int(boneInfluence.x);
|
||||
float weight = boneInfluence.y;
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
|
||||
mat4 inverseBoneMatrix = transpose(inverse(boneMatrix));
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex];
|
||||
mat4 inverseBoneMatrix = transpose(inverse(boneMatrix));
|
||||
float weight = vBoneWeights[i];
|
||||
|
||||
finalPos += boneMatrix * bindPos * weight;
|
||||
finalNormal += inverseBoneMatrix * bindNormal * weight;
|
||||
finalTangent += inverseBoneMatrix * bindTangent * weight;
|
||||
}
|
||||
finalPos += boneMatrix * bindPos * weight;
|
||||
finalNormal += inverseBoneMatrix * bindNormal * weight;
|
||||
finalTangent += inverseBoneMatrix * bindTangent * weight;
|
||||
}
|
||||
finalPos = normalize(finalPos);
|
||||
finalNormal = normalize(finalNormal);
|
||||
finalTangent = normalize(finalTangent);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -95,5 +67,5 @@ void main()
|
|||
fTangent = vec3(transpose(inverse(vInstanceMatrix)) * finalTangent);
|
||||
fTexCoords = vTexCoords;
|
||||
fTexLayer = vTexLayer;
|
||||
fColor = unpackARGB(int(vColor)) / 255.0;
|
||||
fColor = vColor;
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
layout (location = 1) in vec3 vPos;
|
||||
layout (location = 2) in vec3 vNormal;
|
||||
layout (location = 7) in vec4 vBoneInfluence;
|
||||
layout (location = 8) in vec4 vBoneInfluenceExtra;
|
||||
layout (location = 7) in vec4 vBoneIds;
|
||||
layout (location = 8) in vec4 vBoneWeights;
|
||||
layout (location = 9) in mat4 vInstanceMatrix;
|
||||
layout (location = 13) in vec3 vMorphTargetPos;
|
||||
|
||||
|
|
@ -11,55 +11,31 @@ layout(std430, binding = 1) buffer BoneMatrices
|
|||
{
|
||||
mat4 uFinalBonesMatrix[];
|
||||
};
|
||||
layout(std430, binding = 2) buffer RestBoneMatrices
|
||||
{
|
||||
mat4 uRestBonesMatrix[];
|
||||
};
|
||||
|
||||
uniform mat4 uView;
|
||||
uniform vec3 uViewPos;
|
||||
uniform mat4 uProjection;
|
||||
uniform float uMorphTime;
|
||||
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()
|
||||
{
|
||||
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
|
||||
vec4 bindNormal = vec4(vNormal, 1.0);
|
||||
bindPos.xyz += calculateScale(bindPos, bindNormal).xyz;
|
||||
|
||||
vec4 finalPos = vec4(0.0);
|
||||
vec4 finalNormal = vec4(0.0);
|
||||
if (uIsAnimated)
|
||||
if (vBoneIds != vBoneWeights)
|
||||
{
|
||||
vec4 boneInfluences[2];
|
||||
boneInfluences[0] = vBoneInfluence;
|
||||
boneInfluences[1] = vBoneInfluenceExtra;
|
||||
for(int i = 0 ; i < 2; i++)
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
{
|
||||
for(int j = 0; j < 4; j++)
|
||||
{
|
||||
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
|
||||
int boneIndex = int(boneInfluence.x);
|
||||
float weight = boneInfluence.y;
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex];
|
||||
float weight = vBoneWeights[i];
|
||||
|
||||
finalPos += boneMatrix * bindPos * weight;
|
||||
finalNormal += transpose(inverse(boneMatrix)) * bindNormal * weight;
|
||||
}
|
||||
finalPos += boneMatrix * bindPos * weight;
|
||||
finalNormal += transpose(inverse(boneMatrix)) * bindNormal * weight;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -68,5 +44,10 @@ void main()
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#version 460 core
|
||||
|
||||
layout (location = 1) in vec3 vPos;
|
||||
layout (location = 7) in vec4 vBoneInfluence;
|
||||
layout (location = 8) in vec4 vBoneInfluenceExtra;
|
||||
layout (location = 7) in vec4 vBoneIds;
|
||||
layout (location = 8) in vec4 vBoneWeights;
|
||||
layout (location = 9) in mat4 vInstanceMatrix;
|
||||
layout (location = 13) in vec3 vMorphTargetPos;
|
||||
|
||||
|
|
@ -10,41 +10,24 @@ layout(std430, binding = 1) buffer BoneMatrices
|
|||
{
|
||||
mat4 uFinalBonesMatrix[];
|
||||
};
|
||||
layout(std430, binding = 2) buffer RestBoneMatrices
|
||||
{
|
||||
mat4 uRestBonesMatrix[];
|
||||
};
|
||||
|
||||
uniform mat4 uView;
|
||||
uniform mat4 uProjection;
|
||||
uniform float uMorphTime;
|
||||
uniform bool uIsAnimated;
|
||||
|
||||
vec2 unpackBoneIDsAndWeights(int packedData)
|
||||
{
|
||||
return vec2(float((packedData >> 16) & 0xFFFF), float(packedData & 0xFFFF));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
|
||||
|
||||
vec4 finalPos = vec4(0.0);
|
||||
if (uIsAnimated)
|
||||
if (vBoneIds != vBoneWeights)
|
||||
{
|
||||
vec4 boneInfluences[2];
|
||||
boneInfluences[0] = vBoneInfluence;
|
||||
boneInfluences[1] = vBoneInfluenceExtra;
|
||||
for(int i = 0 ; i < 2; i++)
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
{
|
||||
for(int j = 0; j < 4; j++)
|
||||
{
|
||||
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
|
||||
int boneIndex = int(boneInfluence.x);
|
||||
float weight = boneInfluence.y;
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
|
||||
finalPos += uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]) * bindPos * weight;
|
||||
}
|
||||
finalPos += uFinalBonesMatrix[boneIndex] * bindPos * vBoneWeights[i];
|
||||
}
|
||||
}
|
||||
else finalPos = bindPos;
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
|
@ -41,14 +41,14 @@ namespace FModel.Services
|
|||
Details = $"{gameName} - Idling"
|
||||
};
|
||||
|
||||
_client.OnReady += (_, args) => Log.Information("@{Username} ({UserId}) is now ready", args.User.Username, args.User.ID);
|
||||
_client.OnReady += (_, args) => Log.Information("{Username}#{Discriminator} ({UserId}) is now ready", args.User.Username, args.User.Discriminator, args.User.ID);
|
||||
_client.SetPresence(_currentPresence);
|
||||
_client.Initialize();
|
||||
}
|
||||
|
||||
public void UpdatePresence(CUE4ParseViewModel viewModel) =>
|
||||
UpdatePresence(
|
||||
$"{viewModel.Provider.GameDisplayName ?? viewModel.Provider.InternalGameName} - {viewModel.Provider.MountedVfs.Count}/{viewModel.Provider.MountedVfs.Count + viewModel.Provider.UnloadedVfs.Count} Packages",
|
||||
$"{viewModel.Provider.GameDisplayName ?? viewModel.Provider.GameName} - {viewModel.Provider.MountedVfs.Count}/{viewModel.Provider.MountedVfs.Count + viewModel.Provider.UnloadedVfs.Count} Packages",
|
||||
$"Mode: {UserSettings.Default.LoadingMode.GetDescription()} - {viewModel.SearchVm.ResultsCount:### ### ###} Loaded Assets".Trim());
|
||||
|
||||
public void UpdatePresence(string details, string state)
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using FModel.Framework;
|
||||
|
||||
namespace FModel.Settings;
|
||||
|
||||
public class CustomDirectory : ViewModel
|
||||
{
|
||||
public static IList<CustomDirectory> Default(string gameName)
|
||||
{
|
||||
switch (gameName)
|
||||
{
|
||||
case "Fortnite":
|
||||
case "Fortnite [LIVE]":
|
||||
return new List<CustomDirectory>
|
||||
{
|
||||
new("Cosmetics", "FortniteGame/Plugins/GameFeatures/BRCosmetics/Content/Athena/Items/Cosmetics/"),
|
||||
new("Emotes [AUDIO]", "FortniteGame/Plugins/GameFeatures/BRCosmetics/Content/Athena/Sounds/Emotes/"),
|
||||
new("Music Packs [AUDIO]", "FortniteGame/Plugins/GameFeatures/BRCosmetics/Content/Athena/Sounds/MusicPacks/"),
|
||||
new("Weapons", "FortniteGame/Content/Athena/Items/Weapons/"),
|
||||
new("Strings", "FortniteGame/Content/Localization/")
|
||||
};
|
||||
case "VALORANT":
|
||||
case "VALORANT [LIVE]":
|
||||
return new List<CustomDirectory>
|
||||
{
|
||||
new("Audio", "ShooterGame/Content/WwiseAudio/Media/"),
|
||||
new("Characters", "ShooterGame/Content/Characters/"),
|
||||
new("Gun Buddies", "ShooterGame/Content/Equippables/Buddies/"),
|
||||
new("Cards and Sprays", "ShooterGame/Content/Personalization/"),
|
||||
new("Shop Backgrounds", "ShooterGame/Content/UI/OutOfGame/MainMenu/Store/Shared/Textures/"),
|
||||
new("Weapon Renders", "ShooterGame/Content/UI/Screens/OutOfGame/MainMenu/Collection/Assets/Large/")
|
||||
};
|
||||
default:
|
||||
return new List<CustomDirectory>();
|
||||
}
|
||||
}
|
||||
|
||||
private string _header;
|
||||
public string Header
|
||||
{
|
||||
get => _header;
|
||||
set => SetProperty(ref _header, value);
|
||||
}
|
||||
|
||||
private string _directoryPath;
|
||||
public string DirectoryPath
|
||||
{
|
||||
get => _directoryPath;
|
||||
set => SetProperty(ref _directoryPath, value);
|
||||
}
|
||||
|
||||
public CustomDirectory()
|
||||
{
|
||||
Header = string.Empty;
|
||||
DirectoryPath = string.Empty;
|
||||
}
|
||||
|
||||
public CustomDirectory(string header, string path)
|
||||
{
|
||||
Header = header;
|
||||
DirectoryPath = path;
|
||||
}
|
||||
|
||||
public override string ToString() => Header;
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using FModel.Framework;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
|
||||
namespace FModel.Settings;
|
||||
|
||||
public class DirectorySettings : ViewModel, ICloneable
|
||||
{
|
||||
public static DirectorySettings Default(
|
||||
string gameName, string gameDir, bool manual = false, EGame ue = EGame.GAME_UE4_LATEST, string aes = "")
|
||||
{
|
||||
UserSettings.Default.PerDirectory.TryGetValue(gameDir, out var old);
|
||||
return new DirectorySettings
|
||||
{
|
||||
GameName = gameName,
|
||||
GameDirectory = gameDir,
|
||||
IsManual = manual,
|
||||
UeVersion = old?.UeVersion ?? ue,
|
||||
TexturePlatform = old?.TexturePlatform ?? ETexturePlatform.DesktopMobile,
|
||||
Versioning = old?.Versioning ?? new VersioningSettings(),
|
||||
Endpoints = old?.Endpoints ?? EndpointSettings.Default(gameName),
|
||||
Directories = old?.Directories ?? CustomDirectory.Default(gameName),
|
||||
AesKeys = old?.AesKeys ?? new AesResponse { MainKey = aes, DynamicKeys = null },
|
||||
LastAesReload = old?.LastAesReload ?? DateTime.Today.AddDays(-1)
|
||||
};
|
||||
}
|
||||
|
||||
private string _gameName;
|
||||
public string GameName
|
||||
{
|
||||
get => _gameName;
|
||||
set => SetProperty(ref _gameName, value);
|
||||
}
|
||||
|
||||
private string _gameDirectory;
|
||||
public string GameDirectory
|
||||
{
|
||||
get => _gameDirectory;
|
||||
set => SetProperty(ref _gameDirectory, value);
|
||||
}
|
||||
|
||||
private bool _isManual;
|
||||
public bool IsManual
|
||||
{
|
||||
get => _isManual;
|
||||
set => SetProperty(ref _isManual, value);
|
||||
}
|
||||
|
||||
private EGame _ueVersion;
|
||||
public EGame UeVersion
|
||||
{
|
||||
get => _ueVersion;
|
||||
set => SetProperty(ref _ueVersion, value);
|
||||
}
|
||||
|
||||
private ETexturePlatform _texturePlatform;
|
||||
public ETexturePlatform TexturePlatform
|
||||
{
|
||||
get => _texturePlatform;
|
||||
set => SetProperty(ref _texturePlatform, value);
|
||||
}
|
||||
|
||||
private VersioningSettings _versioning;
|
||||
public VersioningSettings Versioning
|
||||
{
|
||||
get => _versioning;
|
||||
set => SetProperty(ref _versioning, value);
|
||||
}
|
||||
|
||||
private EndpointSettings[] _endpoints;
|
||||
public EndpointSettings[] Endpoints
|
||||
{
|
||||
get => _endpoints;
|
||||
set => SetProperty(ref _endpoints, value);
|
||||
}
|
||||
|
||||
private IList<CustomDirectory> _directories;
|
||||
public IList<CustomDirectory> Directories
|
||||
{
|
||||
get => _directories;
|
||||
set => SetProperty(ref _directories, value);
|
||||
}
|
||||
|
||||
private AesResponse _aesKeys;
|
||||
public AesResponse AesKeys
|
||||
{
|
||||
get => _aesKeys;
|
||||
set => SetProperty(ref _aesKeys, value);
|
||||
}
|
||||
|
||||
private DateTime _lastAesReload;
|
||||
public DateTime LastAesReload
|
||||
{
|
||||
get => _lastAesReload;
|
||||
set => SetProperty(ref _lastAesReload, value);
|
||||
}
|
||||
|
||||
private bool Equals(DirectorySettings other)
|
||||
{
|
||||
return GameDirectory == other.GameDirectory && UeVersion == other.UeVersion;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is DirectorySettings other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(GameDirectory, (int) UeVersion);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return GameName;
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return this.MemberwiseClone();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,11 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Animations;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse_Conversion.UEFormat.Enums;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using FModel.Framework;
|
||||
using FModel.ViewModels;
|
||||
|
|
@ -32,48 +31,26 @@ namespace FModel.Settings
|
|||
Default = new UserSettings();
|
||||
}
|
||||
|
||||
private static bool _bSave = true;
|
||||
public static void Save()
|
||||
{
|
||||
if (!_bSave || Default == null) return;
|
||||
Default.PerDirectory[Default.CurrentDir.GameDirectory] = Default.CurrentDir;
|
||||
File.WriteAllText(FilePath, JsonConvert.SerializeObject(Default, Formatting.Indented));
|
||||
}
|
||||
|
||||
public static void Delete()
|
||||
{
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
_bSave = false;
|
||||
File.Delete(FilePath);
|
||||
}
|
||||
if (File.Exists(FilePath)) File.Delete(FilePath);
|
||||
}
|
||||
|
||||
public static bool IsEndpointValid(EEndpointType type, out EndpointSettings endpoint)
|
||||
public static bool IsEndpointValid(FGame game, EEndpointType type, out FEndpoint endpoint)
|
||||
{
|
||||
endpoint = Default.CurrentDir.Endpoints[(int) type];
|
||||
endpoint = null;
|
||||
if (!Default.CustomEndpoints.TryGetValue(game, out var endpoints))
|
||||
return false;
|
||||
|
||||
endpoint = endpoints[(int) type];
|
||||
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;
|
||||
public bool ShowChangelog
|
||||
{
|
||||
|
|
@ -123,7 +100,7 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _modelDirectory, value);
|
||||
}
|
||||
|
||||
private string _gameDirectory = string.Empty;
|
||||
private string _gameDirectory;
|
||||
public string GameDirectory
|
||||
{
|
||||
get => _gameDirectory;
|
||||
|
|
@ -158,6 +135,13 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _avalonImageSize, value);
|
||||
}
|
||||
|
||||
private IDictionary<FGame, AesResponse> _aesKeys = new Dictionary<FGame, AesResponse>();
|
||||
public IDictionary<FGame, AesResponse> AesKeys
|
||||
{
|
||||
get => _aesKeys;
|
||||
set => SetProperty(ref _aesKeys, value);
|
||||
}
|
||||
|
||||
private string _audioDeviceId;
|
||||
public string AudioDeviceId
|
||||
{
|
||||
|
|
@ -172,25 +156,18 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _audioPlayerVolume, value);
|
||||
}
|
||||
|
||||
private ELoadingMode _loadingMode = ELoadingMode.All;
|
||||
private ELoadingMode _loadingMode = ELoadingMode.Multiple;
|
||||
public ELoadingMode LoadingMode
|
||||
{
|
||||
get => _loadingMode;
|
||||
set => SetProperty(ref _loadingMode, value);
|
||||
}
|
||||
|
||||
private DateTime _lastUpdateCheck = DateTime.MinValue;
|
||||
public DateTime LastUpdateCheck
|
||||
private EUpdateMode _updateMode = EUpdateMode.Beta;
|
||||
public EUpdateMode UpdateMode
|
||||
{
|
||||
get => _lastUpdateCheck;
|
||||
set => SetProperty(ref _lastUpdateCheck, value);
|
||||
}
|
||||
|
||||
private DateTime _nextUpdateCheck = DateTime.Now;
|
||||
public DateTime NextUpdateCheck
|
||||
{
|
||||
get => _nextUpdateCheck;
|
||||
set => SetProperty(ref _nextUpdateCheck, value);
|
||||
get => _updateMode;
|
||||
set => SetProperty(ref _updateMode, value);
|
||||
}
|
||||
|
||||
private bool _keepDirectoryStructure = true;
|
||||
|
|
@ -256,19 +233,9 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _readScriptData, value);
|
||||
}
|
||||
|
||||
private IDictionary<string, DirectorySettings> _perDirectory = new Dictionary<string, DirectorySettings>();
|
||||
public IDictionary<string, DirectorySettings> PerDirectory
|
||||
{
|
||||
get => _perDirectory;
|
||||
set => SetProperty(ref _perDirectory, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public DirectorySettings CurrentDir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TO DELETEEEEEEEEEEEEE
|
||||
/// </summary>
|
||||
// <gameDirectory as string, settings>
|
||||
// can't refactor to use this data layout for everything
|
||||
// because it will wipe old user settings that relies on FGame
|
||||
private IDictionary<string, GameSelectorViewModel.DetectedGame> _manualGames = new Dictionary<string, GameSelectorViewModel.DetectedGame>();
|
||||
public IDictionary<string, GameSelectorViewModel.DetectedGame> ManualGames
|
||||
{
|
||||
|
|
@ -276,6 +243,291 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _manualGames, value);
|
||||
}
|
||||
|
||||
private ETexturePlatform _overridedPlatform = ETexturePlatform.DesktopMobile;
|
||||
public ETexturePlatform OverridedPlatform
|
||||
{
|
||||
get => _overridedPlatform;
|
||||
set => SetProperty(ref _overridedPlatform, value);
|
||||
}
|
||||
|
||||
private IDictionary<FGame, string> _presets = new Dictionary<FGame, string>
|
||||
{
|
||||
{FGame.Unknown, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.FortniteGame, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.ShooterGame, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.DeadByDaylight, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.OakGame, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Dungeons, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.WorldExplorers, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.g3, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.StateOfDecay2, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Prospect, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Indiana, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.RogueCompany, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.SwGame, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Platform, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.BendGame, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.TslGame, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.PortalWars, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Gameface, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Athena, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.MultiVersus, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Hotta, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.eFootball, Constants._NO_PRESET_TRIGGER}
|
||||
};
|
||||
public IDictionary<FGame, string> Presets
|
||||
{
|
||||
get => _presets;
|
||||
set => SetProperty(ref _presets, value);
|
||||
}
|
||||
|
||||
private IDictionary<FGame, EGame> _overridedGame = new Dictionary<FGame, EGame>
|
||||
{
|
||||
{FGame.Unknown, EGame.GAME_UE4_LATEST},
|
||||
{FGame.FortniteGame, EGame.GAME_UE5_2},
|
||||
{FGame.ShooterGame, EGame.GAME_Valorant},
|
||||
{FGame.DeadByDaylight, EGame.GAME_UE4_27},
|
||||
{FGame.OakGame, EGame.GAME_Borderlands3},
|
||||
{FGame.Dungeons, EGame.GAME_UE4_22},
|
||||
{FGame.WorldExplorers, EGame.GAME_UE4_24},
|
||||
{FGame.g3, EGame.GAME_UE4_22},
|
||||
{FGame.StateOfDecay2, EGame.GAME_StateOfDecay2},
|
||||
{FGame.Prospect, EGame.GAME_Splitgate},
|
||||
{FGame.Indiana, EGame.GAME_UE4_21},
|
||||
{FGame.RogueCompany, EGame.GAME_RogueCompany},
|
||||
{FGame.SwGame, EGame.GAME_UE4_LATEST},
|
||||
{FGame.Platform, EGame.GAME_UE4_26},
|
||||
{FGame.BendGame, EGame.GAME_UE4_11},
|
||||
{FGame.TslGame, EGame.GAME_PlayerUnknownsBattlegrounds},
|
||||
{FGame.PortalWars, EGame.GAME_UE4_27},
|
||||
{FGame.Gameface, EGame.GAME_GTATheTrilogyDefinitiveEdition},
|
||||
{FGame.Athena, EGame.GAME_SeaOfThieves},
|
||||
{FGame.MultiVersus, EGame.GAME_UE4_26},
|
||||
{FGame.Hotta, EGame.GAME_TowerOfFantasy},
|
||||
{FGame.eFootball, EGame.GAME_UE4_26}
|
||||
};
|
||||
public IDictionary<FGame, EGame> OverridedGame
|
||||
{
|
||||
get => _overridedGame;
|
||||
set => SetProperty(ref _overridedGame, value);
|
||||
}
|
||||
|
||||
private IDictionary<FGame, List<FCustomVersion>> _overridedCustomVersions = new Dictionary<FGame, List<FCustomVersion>>
|
||||
{
|
||||
{FGame.Unknown, null},
|
||||
{FGame.FortniteGame, null},
|
||||
{FGame.ShooterGame, null},
|
||||
{FGame.DeadByDaylight, null},
|
||||
{FGame.OakGame, null},
|
||||
{FGame.Dungeons, null},
|
||||
{FGame.WorldExplorers, null},
|
||||
{FGame.g3, null},
|
||||
{FGame.StateOfDecay2, null},
|
||||
{FGame.Prospect, null},
|
||||
{FGame.Indiana, null},
|
||||
{FGame.RogueCompany, null},
|
||||
{FGame.SwGame, null},
|
||||
{FGame.Platform, null},
|
||||
{FGame.BendGame, null},
|
||||
{FGame.TslGame, null},
|
||||
{FGame.PortalWars, null},
|
||||
{FGame.Gameface, null},
|
||||
{FGame.Athena, null},
|
||||
{FGame.MultiVersus, null},
|
||||
{FGame.Hotta, null},
|
||||
{FGame.eFootball, null}
|
||||
};
|
||||
public IDictionary<FGame, List<FCustomVersion>> OverridedCustomVersions
|
||||
{
|
||||
get => _overridedCustomVersions;
|
||||
set => SetProperty(ref _overridedCustomVersions, value);
|
||||
}
|
||||
|
||||
private IDictionary<FGame, Dictionary<string, bool>> _overridedOptions = new Dictionary<FGame, Dictionary<string, bool>>
|
||||
{
|
||||
{FGame.Unknown, null},
|
||||
{FGame.FortniteGame, null},
|
||||
{FGame.ShooterGame, null},
|
||||
{FGame.DeadByDaylight, null},
|
||||
{FGame.OakGame, null},
|
||||
{FGame.Dungeons, null},
|
||||
{FGame.WorldExplorers, null},
|
||||
{FGame.g3, null},
|
||||
{FGame.StateOfDecay2, null},
|
||||
{FGame.Prospect, null},
|
||||
{FGame.Indiana, null},
|
||||
{FGame.RogueCompany, null},
|
||||
{FGame.SwGame, null},
|
||||
{FGame.Platform, null},
|
||||
{FGame.BendGame, null},
|
||||
{FGame.TslGame, null},
|
||||
{FGame.PortalWars, null},
|
||||
{FGame.Gameface, null},
|
||||
{FGame.Athena, null},
|
||||
{FGame.MultiVersus, null},
|
||||
{FGame.Hotta, null},
|
||||
{FGame.eFootball, null}
|
||||
};
|
||||
|
||||
private IDictionary<FGame, Dictionary<string, KeyValuePair<string, string>>> _overridedMapStructTypes = new Dictionary<FGame, Dictionary<string, KeyValuePair<string, string>>>
|
||||
{
|
||||
{FGame.Unknown, null},
|
||||
{FGame.FortniteGame, null},
|
||||
{FGame.ShooterGame, null},
|
||||
{FGame.DeadByDaylight, null},
|
||||
{FGame.OakGame, null},
|
||||
{FGame.Dungeons, null},
|
||||
{FGame.WorldExplorers, null},
|
||||
{FGame.g3, null},
|
||||
{FGame.StateOfDecay2, null},
|
||||
{FGame.Prospect, null},
|
||||
{FGame.Indiana, null},
|
||||
{FGame.RogueCompany, null},
|
||||
{FGame.SwGame, null},
|
||||
{FGame.Platform, null},
|
||||
{FGame.BendGame, null},
|
||||
{FGame.TslGame, null},
|
||||
{FGame.PortalWars, null},
|
||||
{FGame.Gameface, null},
|
||||
{FGame.Athena, null},
|
||||
{FGame.MultiVersus, null},
|
||||
{FGame.Hotta, null},
|
||||
{FGame.eFootball, null}
|
||||
};
|
||||
public IDictionary<FGame, Dictionary<string, bool>> OverridedOptions
|
||||
{
|
||||
get => _overridedOptions;
|
||||
set => SetProperty(ref _overridedOptions, value);
|
||||
}
|
||||
|
||||
public IDictionary<FGame, Dictionary<string, KeyValuePair<string, string>>> OverridedMapStructTypes
|
||||
{
|
||||
get => _overridedMapStructTypes;
|
||||
set => SetProperty(ref _overridedMapStructTypes, value);
|
||||
}
|
||||
|
||||
private IDictionary<FGame, FEndpoint[]> _customEndpoints = new Dictionary<FGame, FEndpoint[]>
|
||||
{
|
||||
{FGame.Unknown, new FEndpoint[]{new (), new ()}},
|
||||
{
|
||||
FGame.FortniteGame, new []
|
||||
{
|
||||
new FEndpoint("https://fortnitecentral.genxgames.gg/api/v1/aes", "$.['mainKey','dynamicKeys']"),
|
||||
new FEndpoint("https://fortnitecentral.genxgames.gg/api/v1/mappings", "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']") // && @.meta.platform=='Windows'
|
||||
}
|
||||
},
|
||||
{FGame.ShooterGame, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.DeadByDaylight, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.OakGame, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.Dungeons, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.WorldExplorers, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.g3, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.StateOfDecay2, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.Prospect, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.Indiana, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.RogueCompany, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.SwGame, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.Platform, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.BendGame, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.TslGame, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.PortalWars, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.Gameface, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.Athena, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.MultiVersus, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.Hotta, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.eFootball, new FEndpoint[]{new (), new ()}}
|
||||
};
|
||||
public IDictionary<FGame, FEndpoint[]> CustomEndpoints
|
||||
{
|
||||
get => _customEndpoints;
|
||||
set => SetProperty(ref _customEndpoints, value);
|
||||
}
|
||||
|
||||
private IDictionary<FGame, IList<CustomDirectory>> _customDirectories = new Dictionary<FGame, IList<CustomDirectory>>
|
||||
{
|
||||
{FGame.Unknown, new List<CustomDirectory>()},
|
||||
{
|
||||
FGame.FortniteGame, new List<CustomDirectory>
|
||||
{
|
||||
new("Cosmetics", "FortniteGame/Content/Athena/Items/Cosmetics/"),
|
||||
new("Emotes [AUDIO]", "FortniteGame/Content/Athena/Sounds/Emotes/"),
|
||||
new("Music Packs [AUDIO]", "FortniteGame/Content/Athena/Sounds/MusicPacks/"),
|
||||
new("Weapons", "FortniteGame/Content/Athena/Items/Weapons/"),
|
||||
new("Strings", "FortniteGame/Content/Localization/")
|
||||
}
|
||||
},
|
||||
{
|
||||
FGame.ShooterGame, new List<CustomDirectory>
|
||||
{
|
||||
new("Audio", "ShooterGame/Content/WwiseAudio/Media/"),
|
||||
new("Characters", "ShooterGame/Content/Characters/"),
|
||||
new("Gun Buddies", "ShooterGame/Content/Equippables/Buddies/"),
|
||||
new("Cards and Sprays", "ShooterGame/Content/Personalization/"),
|
||||
new("Shop Backgrounds", "ShooterGame/Content/UI/OutOfGame/MainMenu/Store/Shared/Textures/"),
|
||||
new("Weapon Renders", "ShooterGame/Content/UI/Screens/OutOfGame/MainMenu/Collection/Assets/Large/")
|
||||
}
|
||||
},
|
||||
{
|
||||
FGame.DeadByDaylight, new List<CustomDirectory>
|
||||
{
|
||||
new("Audio", "DeadByDaylight/Content/WwiseAudio/Windows/"),
|
||||
new("Characters", "DeadByDaylight/Content/Characters/"),
|
||||
new("Icons", "DeadByDaylight/Content/UI/UMGAssets/Icons/"),
|
||||
new("Strings", "DeadByDaylight/Content/Localization/")
|
||||
}
|
||||
},
|
||||
{FGame.OakGame, new List<CustomDirectory>()},
|
||||
{
|
||||
FGame.Dungeons, new List<CustomDirectory>
|
||||
{
|
||||
new("Levels", "Dungeons/Content/data/Lovika/Levels"),
|
||||
new("Friendlies", "Dungeons/Content/Actor/Characters/Friendlies"),
|
||||
new("Skins", "Dungeons/Content/Actor/Characters/Player/Master/Skins"),
|
||||
new("Strings", "Dungeons/Content/Localization/")
|
||||
}
|
||||
},
|
||||
{
|
||||
FGame.WorldExplorers, new List<CustomDirectory>
|
||||
{
|
||||
new("Loot", "WorldExplorers/Content/Loot/"),
|
||||
new("Strings", "WorldExplorers/Content/Localization/")
|
||||
}
|
||||
},
|
||||
{
|
||||
FGame.g3, new List<CustomDirectory>
|
||||
{
|
||||
new("Cosmetics", "g3/Content/Blueprints/Cosmetics/"),
|
||||
new("Strings", "g3/Content/Localization/")
|
||||
}
|
||||
},
|
||||
{FGame.StateOfDecay2, new List<CustomDirectory>()},
|
||||
{FGame.Prospect, new List<CustomDirectory>()},
|
||||
{FGame.Indiana, new List<CustomDirectory>()},
|
||||
{FGame.RogueCompany, new List<CustomDirectory>()},
|
||||
{FGame.SwGame, new List<CustomDirectory>()},
|
||||
{FGame.Platform, new List<CustomDirectory>()},
|
||||
{FGame.BendGame, new List<CustomDirectory>()},
|
||||
{FGame.TslGame, new List<CustomDirectory>()},
|
||||
{FGame.PortalWars, new List<CustomDirectory>()},
|
||||
{FGame.Gameface, new List<CustomDirectory>()},
|
||||
{FGame.Athena, new List<CustomDirectory>()},
|
||||
{FGame.MultiVersus, new List<CustomDirectory>()},
|
||||
{FGame.Hotta, new List<CustomDirectory>()},
|
||||
{FGame.eFootball, new List<CustomDirectory>()}
|
||||
};
|
||||
public IDictionary<FGame, IList<CustomDirectory>> CustomDirectories
|
||||
{
|
||||
get => _customDirectories;
|
||||
set => SetProperty(ref _customDirectories, value);
|
||||
}
|
||||
|
||||
private DateTime _lastAesReload = DateTime.Today.AddDays(-1);
|
||||
public DateTime LastAesReload
|
||||
{
|
||||
get => _lastAesReload;
|
||||
set => SetProperty(ref _lastAesReload, value);
|
||||
}
|
||||
|
||||
private AuthResponse _lastAuthResponse = new() {AccessToken = "", ExpiresAt = DateTime.Now};
|
||||
public AuthResponse LastAuthResponse
|
||||
{
|
||||
|
|
@ -381,13 +633,6 @@ namespace FModel.Settings
|
|||
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;
|
||||
public ELodFormat LodExportFormat
|
||||
{
|
||||
|
|
@ -465,13 +710,6 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _saveMorphTargets, value);
|
||||
}
|
||||
|
||||
private bool _saveEmbeddedMaterials = true;
|
||||
public bool SaveEmbeddedMaterials
|
||||
{
|
||||
get => _saveEmbeddedMaterials;
|
||||
set => SetProperty(ref _saveEmbeddedMaterials, value);
|
||||
}
|
||||
|
||||
private bool _saveSkeletonAsMesh;
|
||||
public bool SaveSkeletonAsMesh
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using FModel.Framework;
|
||||
|
||||
namespace FModel.Settings;
|
||||
|
||||
public class VersioningSettings : ViewModel
|
||||
{
|
||||
private IList<FCustomVersion> _customVersions;
|
||||
public IList<FCustomVersion> CustomVersions
|
||||
{
|
||||
get => _customVersions;
|
||||
set => SetProperty(ref _customVersions, value);
|
||||
}
|
||||
|
||||
private IDictionary<string, bool> _options;
|
||||
public IDictionary<string, bool> Options
|
||||
{
|
||||
get => _options;
|
||||
set => SetProperty(ref _options, value);
|
||||
}
|
||||
|
||||
private IDictionary<string, KeyValuePair<string, string>> _mapStructTypes;
|
||||
public IDictionary<string, KeyValuePair<string, string>> MapStructTypes
|
||||
{
|
||||
get => _mapStructTypes;
|
||||
set => SetProperty(ref _mapStructTypes, value);
|
||||
}
|
||||
|
||||
public VersioningSettings() {}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class AboutViewModel : ViewModel
|
||||
{
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
|
||||
private string _descriptionLabel;
|
||||
public string DescriptionLabel
|
||||
{
|
||||
get => _descriptionLabel;
|
||||
set => SetProperty(ref _descriptionLabel, value);
|
||||
}
|
||||
|
||||
private string _contributorsLabel;
|
||||
public string ContributorsLabel
|
||||
{
|
||||
get => _contributorsLabel;
|
||||
set => SetProperty(ref _contributorsLabel, value);
|
||||
}
|
||||
|
||||
private string _donatorsLabel;
|
||||
public string DonatorsLabel
|
||||
{
|
||||
get => _donatorsLabel;
|
||||
set => SetProperty(ref _donatorsLabel, value);
|
||||
}
|
||||
|
||||
private string _referencesLabel;
|
||||
public string ReferencesLabel
|
||||
{
|
||||
get => _referencesLabel;
|
||||
set => SetProperty(ref _referencesLabel, value);
|
||||
}
|
||||
|
||||
public AboutViewModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async Task Initialize()
|
||||
{
|
||||
await Task.WhenAll(
|
||||
Task.Run(() =>
|
||||
{
|
||||
DescriptionLabel = "FModel is an archive explorer for Unreal Engine games that uses CUE4Parse as its core parsing library, providing robust support for the latest UE4 and UE5 archive formats. It aims to deliver a modern and intuitive user interface, powerful features, and a comprehensive set of tools for previewing and converting game packages, empowering YOU to understand games' inner workings with ease.";
|
||||
ContributorsLabel = $"FModel owes its continued existence to the passionate individuals who have generously contributed their time and expertise. Contributions from individuals such as {string.Join(", ", "GMatrixGames", "amr", "LongerWarrior", "MinshuG", "InTheShade", "Officer")}, and countless others, both in the past and those yet to come, ensure the continuous development and success of this project. If you are benefiting from FModel and would like to support its continued improvements, please consider making a donation.";
|
||||
ReferencesLabel = string.Join(", ",
|
||||
"Adonis UI", "AutoUpdater.NET", "AvalonEdit", "CSCore", "CUE4Parse", "DiscordRichPresence",
|
||||
"EpicManifestParser", "ImGui.NET", "K4os.Compression.LZ4", "Newtonsoft.Json", "NVorbis", "Oodle.NET",
|
||||
"Ookii.Dialogs.Wpf", "OpenTK", "RestSharp", "Serilog", "SixLabors.ImageSharp", "SkiaSharp");
|
||||
}),
|
||||
Task.Run(() =>
|
||||
{
|
||||
var donators = _apiEndpointView.FModelApi.GetDonators();
|
||||
if (donators == null) return;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendJoin<Donator>(", ", donators);
|
||||
sb.Append('.');
|
||||
DonatorsLabel = sb.ToString();
|
||||
})
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ using FModel.Framework;
|
|||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
|
|
@ -34,7 +35,22 @@ public class AesManagerViewModel : ViewModel
|
|||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_keysFromSettings = UserSettings.Default.CurrentDir.AesKeys;
|
||||
if (_cue4Parse.Game == FGame.Unknown &&
|
||||
UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings))
|
||||
{
|
||||
_keysFromSettings = settings.AesKeys;
|
||||
}
|
||||
else
|
||||
{
|
||||
UserSettings.Default.AesKeys.TryGetValue(_cue4Parse.Game, out _keysFromSettings);
|
||||
}
|
||||
|
||||
_keysFromSettings ??= new AesResponse
|
||||
{
|
||||
MainKey = string.Empty,
|
||||
DynamicKeys = null
|
||||
};
|
||||
|
||||
_mainKey.Key = Helper.FixKey(_keysFromSettings.MainKey);
|
||||
AesKeys = new FullyObservableCollection<FileItem>(EnumerateAesKeys());
|
||||
AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged;
|
||||
|
|
@ -89,7 +105,9 @@ public class AesManagerViewModel : ViewModel
|
|||
|
||||
public void SetAesKeys()
|
||||
{
|
||||
UserSettings.Default.CurrentDir.AesKeys = _keysFromSettings;
|
||||
if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings;
|
||||
else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings;
|
||||
// Log.Information("{@Json}", UserSettings.Default);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using FModel.Framework;
|
||||
|
|
@ -9,18 +9,20 @@ namespace FModel.ViewModels;
|
|||
|
||||
public class ApiEndpointViewModel
|
||||
{
|
||||
private readonly RestClient _client = new (new RestClientOptions
|
||||
private readonly RestClient _client = new RestClient
|
||||
{
|
||||
UserAgent = $"FModel/{Constants.APP_VERSION}",
|
||||
Timeout = TimeSpan.FromSeconds(5)
|
||||
}, configureSerialization: s => s.UseSerializer<JsonNetSerializer>());
|
||||
Options =
|
||||
{
|
||||
UserAgent = $"FModel/{Constants.APP_VERSION}",
|
||||
MaxTimeout = 3 * 1000
|
||||
}
|
||||
}.UseSerializer<JsonNetSerializer>();
|
||||
|
||||
public FortniteApiEndpoint FortniteApi { get; }
|
||||
public ValorantApiEndpoint ValorantApi { get; }
|
||||
public FortniteCentralApiEndpoint CentralApi { get; }
|
||||
public EpicApiEndpoint EpicApi { get; }
|
||||
public FModelApiEndpoint FModelApi { get; }
|
||||
public GitHubApiEndpoint GitHubApi { get; }
|
||||
public DynamicApiEndpoint DynamicApi { get; }
|
||||
|
||||
public ApiEndpointViewModel()
|
||||
|
|
@ -30,7 +32,6 @@ public class ApiEndpointViewModel
|
|||
CentralApi = new FortniteCentralApiEndpoint(_client);
|
||||
EpicApi = new EpicApiEndpoint(_client);
|
||||
FModelApi = new FModelApiEndpoint(_client);
|
||||
GitHubApi = new GitHubApiEndpoint(_client);
|
||||
DynamicApi = new DynamicApiEndpoint(_client);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using EpicManifestParser.Api;
|
||||
|
||||
using EpicManifestParser.Objects;
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
|
||||
using RestSharp;
|
||||
|
||||
using Serilog;
|
||||
|
||||
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 _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 _CBM_URL = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/Windows/5cb97847cee34581afdbc445400e2f77/FortniteContentBuilds";
|
||||
|
||||
public EpicApiEndpoint(RestClient client) : base(client) { }
|
||||
|
||||
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))
|
||||
{
|
||||
|
|
@ -47,6 +28,41 @@ public class EpicApiEndpoint : AbstractApiProvider
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ using FModel.Framework;
|
|||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
using FModel.Views;
|
||||
using Newtonsoft.Json;
|
||||
using RestSharp;
|
||||
using Serilog;
|
||||
using MessageBox = AdonisUI.Controls.MessageBox;
|
||||
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
|
||||
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
|
||||
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
||||
|
|
@ -24,7 +24,6 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
private News _news;
|
||||
private Info _infos;
|
||||
private Donator[] _donators;
|
||||
private Backup[] _backups;
|
||||
private Game _game;
|
||||
private readonly IDictionary<string, CommunityDesign> _communityDesigns = new Dictionary<string, CommunityDesign>();
|
||||
|
|
@ -46,17 +45,17 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
return _news ??= GetNewsAsync(token, game).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<Donator[]> GetDonatorsAsync()
|
||||
public async Task<Info> GetInfosAsync(CancellationToken token, EUpdateMode updateMode)
|
||||
{
|
||||
var request = new FRestRequest($"https://api.fmodel.app/v1/donations/donators");
|
||||
var response = await _client.ExecuteAsync<Donator[]>(request).ConfigureAwait(false);
|
||||
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 Donator[] GetDonators()
|
||||
public Info GetInfos(CancellationToken token, EUpdateMode updateMode)
|
||||
{
|
||||
return _donators ??= GetDonatorsAsync().GetAwaiter().GetResult();
|
||||
return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<Backup[]> GetBackupsAsync(CancellationToken token, string gameName)
|
||||
|
|
@ -103,16 +102,11 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
return communityDesign;
|
||||
}
|
||||
|
||||
public void CheckForUpdates(bool launch = false)
|
||||
public void CheckForUpdates(EUpdateMode updateMode)
|
||||
{
|
||||
if (DateTime.Now < UserSettings.Default.NextUpdateCheck) return;
|
||||
|
||||
if (launch)
|
||||
{
|
||||
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
|
||||
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
|
||||
}
|
||||
AutoUpdater.Start("https://api.fmodel.app/v1/infos/Qa");
|
||||
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
|
||||
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
|
||||
AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}");
|
||||
}
|
||||
|
||||
private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
|
||||
|
|
@ -122,13 +116,9 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
args.UpdateInfo = new UpdateInfoEventArgs
|
||||
{
|
||||
CurrentVersion = _infos.Version.SubstringBefore('-'),
|
||||
CurrentVersion = _infos.Version,
|
||||
ChangelogURL = _infos.ChangelogUrl,
|
||||
DownloadURL = _infos.DownloadUrl,
|
||||
Mandatory = new CustomMandatory
|
||||
{
|
||||
CommitHash = _infos.Version.SubstringAfter('+')
|
||||
}
|
||||
DownloadURL = _infos.DownloadUrl
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -137,21 +127,40 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
if (args is { CurrentVersion: { } })
|
||||
{
|
||||
UserSettings.Default.LastUpdateCheck = DateTime.Now;
|
||||
|
||||
if (((CustomMandatory)args.Mandatory).CommitHash == Constants.APP_COMMIT_ID)
|
||||
var currentVersion = new System.Version(args.CurrentVersion);
|
||||
if (currentVersion == args.InstalledVersion)
|
||||
{
|
||||
if (UserSettings.Default.ShowChangelog)
|
||||
ShowChangelog(args);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var currentVersion = new System.Version(args.CurrentVersion);
|
||||
UserSettings.Default.ShowChangelog = currentVersion != args.InstalledVersion;
|
||||
var downgrade = currentVersion < args.InstalledVersion;
|
||||
var messageBox = new MessageBoxModel
|
||||
{
|
||||
Text = $"The latest version of FModel {UserSettings.Default.UpdateMode} is {args.CurrentVersion}. You are using version {args.InstalledVersion}. Do you want to {(downgrade ? "downgrade" : "update")} the application now?",
|
||||
Caption = $"{(downgrade ? "Downgrade" : "Update")} Available",
|
||||
Icon = MessageBoxImage.Question,
|
||||
Buttons = MessageBoxButtons.YesNo(),
|
||||
IsSoundEnabled = false
|
||||
};
|
||||
|
||||
const string message = "A new update is available!";
|
||||
Helper.OpenWindow<AdonisWindow>(message, () => new UpdateView { Title = message, ResizeMode = ResizeMode.NoResize }.ShowDialog());
|
||||
MessageBox.Show(messageBox);
|
||||
if (messageBox.Result != MessageBoxResult.Yes) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (AutoUpdater.DownloadUpdate(args))
|
||||
{
|
||||
UserSettings.Default.ShowChangelog = true;
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
UserSettings.Default.ShowChangelog = false;
|
||||
MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -165,7 +174,7 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
var request = new FRestRequest(args.ChangelogURL);
|
||||
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.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog");
|
||||
|
|
@ -173,9 +182,3 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
UserSettings.Default.ShowChangelog = false;
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomMandatory : Mandatory
|
||||
{
|
||||
public string CommitHash { get; set; }
|
||||
public string ShortCommitHash => CommitHash[..7];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,14 +25,6 @@ public class Backup
|
|||
[J] public long FileSize { get; private set; }
|
||||
}
|
||||
|
||||
public class Donator
|
||||
{
|
||||
[J] public string Username { get; private set; }
|
||||
[J] public int Count { get; private set; }
|
||||
|
||||
public override string ToString() => $"{Username}{(Count > 5 ? " ❤️" : "")}";
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{" + nameof(DisplayName) + "}")]
|
||||
public class Game
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
using CUE4Parse.UE4.Exceptions;
|
||||
using CUE4Parse.UE4.Readers;
|
||||
using FModel.Settings;
|
||||
using Ionic.Zlib;
|
||||
using RestSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
|
@ -8,15 +13,7 @@ using System.Runtime.InteropServices;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using CUE4Parse.Compression;
|
||||
using CUE4Parse.UE4.Exceptions;
|
||||
using CUE4Parse.UE4.Readers;
|
||||
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
using OffiUtils;
|
||||
using RestSharp;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
||||
|
|
@ -43,22 +40,26 @@ public class VManifest
|
|||
public readonly VChunk[] Chunks;
|
||||
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)
|
||||
{
|
||||
using (Ar)
|
||||
{
|
||||
Header = new VHeader(Ar);
|
||||
var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize);
|
||||
var uncompressedBuffer = new byte[(int)Header.UncompressedSize];
|
||||
ZlibHelper.Decompress(compressedBuffer, 0, compressedBuffer.Length, uncompressedBuffer, 0, uncompressedBuffer.Length);
|
||||
var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
|
||||
if (uncompressedBuffer.Length != Header.UncompressedSize)
|
||||
throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}");
|
||||
|
||||
var manifestAr = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
|
||||
Chunks = manifestAr.ReadArray<VChunk>((int) Header.ChunkCount);
|
||||
Paks = manifestAr.ReadArray((int) Header.PakCount, () => new VPak(manifestAr));
|
||||
using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
|
||||
Chunks = manifest.ReadArray<VChunk>((int) Header.ChunkCount);
|
||||
Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest));
|
||||
|
||||
if (manifestAr.Position != manifestAr.Length)
|
||||
throw new ParserException(manifestAr, $"Parsing failed, {manifestAr.Position} != {manifestAr.Length}");
|
||||
if (manifest.Position != manifest.Length)
|
||||
throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}");
|
||||
}
|
||||
|
||||
_client = new HttpClient(new HttpClientHandler
|
||||
|
|
@ -117,7 +118,7 @@ public class VManifest
|
|||
return chunkBytes;
|
||||
}
|
||||
|
||||
public VPakStream GetPakStream(int index) => new VPakStream(this, index);
|
||||
public Stream GetPakStream(int index) => new VPakStream(this, index);
|
||||
}
|
||||
|
||||
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 class VPakStream : Stream, IRandomAccessStream, ICloneable
|
||||
public class VPakStream : Stream, ICloneable
|
||||
{
|
||||
private readonly VManifest _manifest;
|
||||
private readonly int _pakIndex;
|
||||
|
|
@ -203,22 +204,11 @@ public class VPakStream : Stream, IRandomAccessStream, ICloneable
|
|||
|
||||
public object Clone() => new VPakStream(_manifest, _pakIndex, _position);
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) =>
|
||||
ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
public int ReadAt(long position, byte[] buffer, int offset, int count) =>
|
||||
ReadAtAsync(position, buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
||||
public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var bytesRead = await ReadAtAsync(_position, buffer, offset, count, cancellationToken);
|
||||
_position += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public async Task<int> ReadAtAsync(long position, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var (i, startPos) = GetChunkIndex(position);
|
||||
var (i, startPos) = GetChunkIndex(_position);
|
||||
if (i == -1) return 0;
|
||||
|
||||
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -245,14 +235,10 @@ public class VPakStream : Stream, IRandomAccessStream, ICloneable
|
|||
if (++i == _chunks.Length) break;
|
||||
}
|
||||
|
||||
_position += 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)
|
||||
{
|
||||
var tasks = new List<Task>();
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
using System;
|
||||
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.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.Commands;
|
||||
using FModel.Views;
|
||||
using FModel.Views.Resources.Controls;
|
||||
using Ionic.Zip;
|
||||
using Oodle.NET;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using MessageBox = AdonisUI.Controls.MessageBox;
|
||||
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
|
||||
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
|
||||
using OodleCUE4 = CUE4Parse.Compression.Oodle;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
|
|
@ -49,9 +46,11 @@ public class ApplicationViewModel : ViewModel
|
|||
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
|
||||
private CopyCommand _copyCommand;
|
||||
|
||||
public string InitialWindowTitle => $"FModel ({Constants.APP_SHORT_COMMIT_ID})";
|
||||
public string InitialWindowTitle => $"FModel {UserSettings.Default.UpdateMode}";
|
||||
public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown";
|
||||
public string TitleExtra => $"({UserSettings.Default.CurrentDir.UeVersion}){(Build != EBuildKind.Release ? $" ({Build})" : "")}";
|
||||
public string TitleExtra =>
|
||||
$"({(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" +
|
||||
$"{(Build != EBuildKind.Release ? $" ({Build})" : "")}";
|
||||
|
||||
public LoadingModesViewModel LoadingModes { get; }
|
||||
public CustomDirectoriesViewModel CustomDirectories { get; }
|
||||
|
|
@ -59,6 +58,8 @@ public class ApplicationViewModel : ViewModel
|
|||
public SettingsViewModel SettingsView { get; }
|
||||
public AesManagerViewModel AesManager { get; }
|
||||
public AudioPlayerViewModel AudioPlayer { get; }
|
||||
public MapViewerViewModel MapViewer { get; }
|
||||
private OodleCompressor _oodle;
|
||||
|
||||
public ApplicationViewModel()
|
||||
{
|
||||
|
|
@ -72,59 +73,50 @@ public class ApplicationViewModel : ViewModel
|
|||
#endif
|
||||
LoadingModes = new LoadingModesViewModel();
|
||||
|
||||
UserSettings.Default.CurrentDir = AvoidEmptyGameDirectory(false);
|
||||
if (UserSettings.Default.CurrentDir is null)
|
||||
AvoidEmptyGameDirectoryAndSetEGame(false);
|
||||
if (UserSettings.Default.GameDirectory is null)
|
||||
{
|
||||
//If no game is selected, many things will break before a shutdown request is processed in the normal way.
|
||||
//A hard exit is preferable to an unhandled expection in this case
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
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();
|
||||
SettingsView = new SettingsViewModel();
|
||||
CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory);
|
||||
CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory);
|
||||
SettingsView = new SettingsViewModel(CUE4Parse.Game);
|
||||
AesManager = new AesManagerViewModel(CUE4Parse);
|
||||
MapViewer = new MapViewerViewModel(CUE4Parse);
|
||||
AudioPlayer = new AudioPlayerViewModel();
|
||||
|
||||
Status.SetStatus(EStatusKind.Ready);
|
||||
}
|
||||
|
||||
public DirectorySettings AvoidEmptyGameDirectory(bool bAlreadyLaunched)
|
||||
public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched)
|
||||
{
|
||||
var gameDirectory = UserSettings.Default.GameDirectory;
|
||||
if (!bAlreadyLaunched && UserSettings.Default.PerDirectory.TryGetValue(gameDirectory, out var currentDir))
|
||||
return currentDir;
|
||||
if (!string.IsNullOrEmpty(gameDirectory) && !bAlreadyLaunched) return;
|
||||
|
||||
var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory);
|
||||
var result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return null;
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
|
||||
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDirectory.GameDirectory;
|
||||
if (!bAlreadyLaunched || UserSettings.Default.CurrentDir.Equals(gameLauncherViewModel.SelectedDirectory))
|
||||
return gameLauncherViewModel.SelectedDirectory;
|
||||
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory;
|
||||
if (!bAlreadyLaunched || gameDirectory == gameLauncherViewModel.SelectedDetectedGame.GameDirectory) return;
|
||||
|
||||
// UserSettings.Save(); // ??? change key then change game, key saved correctly what?
|
||||
UserSettings.Default.CurrentDir = gameLauncherViewModel.SelectedDirectory;
|
||||
RestartWithWarning();
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task UpdateProvider(bool isLaunch)
|
||||
{
|
||||
if (!isLaunch && !AesManager.HasChange) return;
|
||||
|
||||
CUE4Parse.ClearProvider();
|
||||
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
CUE4Parse.LoadVfs(cancellationToken, AesManager.AesKeys);
|
||||
CUE4Parse.Provider.LoadIniConfigs();
|
||||
AesManager.SetAesKeys();
|
||||
});
|
||||
RaisePropertyChanged(nameof(GameDisplayName));
|
||||
}
|
||||
|
||||
public void RestartWithWarning()
|
||||
|
|
@ -143,7 +135,7 @@ public class ApplicationViewModel : ViewModel
|
|||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
Arguments = $"\"{path}\"",
|
||||
Arguments = $"\"{Path.GetFullPath(Environment.GetCommandLineArgs()[0])}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = false,
|
||||
RedirectStandardError = false,
|
||||
|
|
@ -169,30 +161,7 @@ public class ApplicationViewModel : ViewModel
|
|||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
public async Task UpdateProvider(bool isLaunch)
|
||||
{
|
||||
if (!isLaunch && !AesManager.HasChange) return;
|
||||
|
||||
CUE4Parse.ClearProvider();
|
||||
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
// TODO: refactor after release, select updated keys only
|
||||
var aes = AesManager.AesKeys.Select(x =>
|
||||
{
|
||||
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();
|
||||
});
|
||||
RaisePropertyChanged(nameof(GameDisplayName));
|
||||
}
|
||||
|
||||
public static async Task InitVgmStream()
|
||||
public async Task InitVgmStream()
|
||||
{
|
||||
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
|
||||
if (File.Exists(vgmZipFilePath)) return;
|
||||
|
|
@ -200,17 +169,9 @@ public class ApplicationViewModel : ViewModel
|
|||
await ApplicationService.ApiEndpointView.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
|
||||
if (new FileInfo(vgmZipFilePath).Length > 0)
|
||||
{
|
||||
var zipDir = Path.GetDirectoryName(vgmZipFilePath)!;
|
||||
await using var zipFs = File.OpenRead(vgmZipFilePath);
|
||||
using var zip = new ZipArchive(zipFs, ZipArchiveMode.Read);
|
||||
|
||||
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);
|
||||
}
|
||||
var zip = ZipFile.Read(vgmZipFilePath);
|
||||
var zipDir = vgmZipFilePath.SubstringBeforeLast("\\");
|
||||
foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -218,44 +179,43 @@ public class ApplicationViewModel : ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public static async Task InitImGuiSettings(bool forceDownload)
|
||||
public async Task InitOodle()
|
||||
{
|
||||
var imgui = "imgui.ini";
|
||||
var imguiPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", imgui);
|
||||
var dataDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
||||
var oodlePath = Path.Combine(dataDir.FullName, OodleCUE4.OODLE_DLL_NAME);
|
||||
|
||||
if (File.Exists(imgui)) File.Move(imgui, imguiPath, true);
|
||||
if (File.Exists(imguiPath) && !forceDownload) return;
|
||||
if (File.Exists(OodleCUE4.OODLE_DLL_NAME))
|
||||
{
|
||||
File.Move(OodleCUE4.OODLE_DLL_NAME, oodlePath, true);
|
||||
}
|
||||
else if (!File.Exists(oodlePath))
|
||||
{
|
||||
var result = await OodleCUE4.DownloadOodleDll(oodlePath);
|
||||
if (!result) return;
|
||||
}
|
||||
|
||||
await ApplicationService.ApiEndpointView.DownloadFileAsync($"https://cdn.fmodel.app/d/configurations/{imgui}", imguiPath);
|
||||
if (new FileInfo(imguiPath).Length == 0)
|
||||
if (File.Exists("oo2core_8_win64.dll"))
|
||||
File.Delete("oo2core_8_win64.dll");
|
||||
|
||||
_oodle = new OodleCompressor(oodlePath);
|
||||
|
||||
unsafe
|
||||
{
|
||||
OodleCUE4.DecompressFunc = (bufferPtr, bufferSize, outputPtr, outputSize, a, b, c, d, e, f, g, h, i, threadModule) =>
|
||||
_oodle.Decompress(new IntPtr(bufferPtr), bufferSize, new IntPtr(outputPtr), outputSize,
|
||||
(OodleLZ_FuzzSafe) a, (OodleLZ_CheckCRC) b, (OodleLZ_Verbosity) c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase) threadModule);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -551,27 +551,14 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
case "adpcm":
|
||||
case "opus":
|
||||
case "wem":
|
||||
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));
|
||||
Replace(newAudio);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -579,8 +566,7 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath);
|
||||
private bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath)
|
||||
private bool TryConvert(out string wavFilePath)
|
||||
{
|
||||
wavFilePath = string.Empty;
|
||||
var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe");
|
||||
|
|
@ -590,46 +576,22 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
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("/"));
|
||||
File.WriteAllBytes(SelectedAudioFile.FilePath, SelectedAudioFile.Data);
|
||||
|
||||
rawFilePath = Path.ChangeExtension(SelectedAudioFile.FilePath, ".wav");
|
||||
var binkadecProcess = Process.Start(new ProcessStartInfo
|
||||
wavFilePath = Path.ChangeExtension(SelectedAudioFile.FilePath, ".wav");
|
||||
var vgmProcess = Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = binkadecPath,
|
||||
Arguments = $"-i \"{SelectedAudioFile.FilePath}\" -o \"{rawFilePath}\"",
|
||||
FileName = vgmFilePath,
|
||||
Arguments = $"-o \"{wavFilePath}\" \"{SelectedAudioFile.FilePath}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
binkadecProcess?.WaitForExit(5000);
|
||||
vgmProcess?.WaitForExit();
|
||||
|
||||
File.Delete(SelectedAudioFile.FilePath);
|
||||
return binkadecProcess?.ExitCode == 0 && File.Exists(rawFilePath);
|
||||
return vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.UE4.VirtualFileSystem;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
|
|
@ -21,8 +20,6 @@ namespace FModel.ViewModels;
|
|||
|
||||
public class BackupManagerViewModel : ViewModel
|
||||
{
|
||||
public const uint FBKP_MAGIC = 0x504B4246;
|
||||
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
|
@ -67,21 +64,23 @@ public class BackupManagerViewModel : ViewModel
|
|||
var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups");
|
||||
var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp";
|
||||
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 compressedStream = LZ4Stream.Encode(fileStream, LZ4Level.L00_FAST);
|
||||
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)
|
||||
{
|
||||
if (!func(asset)) continue;
|
||||
writer.Write(asset.Size);
|
||||
writer.Write(asset.IsEncrypted);
|
||||
writer.Write($"/{asset.Path.ToLower()}");
|
||||
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") ||
|
||||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
|
||||
continue;
|
||||
|
||||
writer.Write((long) 0);
|
||||
writer.Write((long) 0);
|
||||
writer.Write(entry.Size);
|
||||
writer.Write(entry.IsEncrypted);
|
||||
writer.Write(0);
|
||||
writer.Write($"/{entry.Path.ToLower()}");
|
||||
writer.Write(0);
|
||||
}
|
||||
|
||||
SaveCheck(fullPath, fileName, "created", "create");
|
||||
|
|
@ -117,12 +116,3 @@ public class BackupManagerViewModel : ViewModel
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum EBackupVersion : byte
|
||||
{
|
||||
BeforeVersionWasAdded = 0,
|
||||
Initial,
|
||||
|
||||
LatestPlusOne,
|
||||
Latest = LatestPlusOne - 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using AdonisUI.Controls;
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
using FModel.Views;
|
||||
|
||||
namespace FModel.ViewModels.Commands;
|
||||
|
|
@ -32,4 +31,4 @@ public class AddEditDirectoryCommand : ViewModelCommand<CustomDirectoriesViewMod
|
|||
contextViewModel.Add(customDir);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
|
||||
namespace FModel.ViewModels.Commands;
|
||||
|
||||
|
|
@ -18,4 +17,4 @@ public class DeleteDirectoryCommand : ViewModelCommand<CustomDirectoriesViewMode
|
|||
|
||||
contextViewModel.Delete(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
|
||||
{
|
||||
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
|
||||
if (_applicationView.CUE4Parse.Provider.Keys.Count == 0 && _applicationView.CUE4Parse.Provider.RequiredKeys.Count > 0)
|
||||
if (_applicationView.CUE4Parse.Provider.Files.Count <= 0)
|
||||
{
|
||||
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));
|
||||
|
|
@ -56,11 +56,13 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
await Task.WhenAll(
|
||||
_applicationView.CUE4Parse.LoadLocalizedResources(), // load locres if not already loaded,
|
||||
_applicationView.CUE4Parse.LoadVirtualPaths(), // load virtual paths if not already loaded
|
||||
Task.Run(() => Utils.Typefaces = new Typefaces(_applicationView.CUE4Parse)),
|
||||
_threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
// filter what to show
|
||||
switch (UserSettings.Default.LoadingMode)
|
||||
{
|
||||
case ELoadingMode.Single:
|
||||
case ELoadingMode.Multiple:
|
||||
{
|
||||
var l = (IList) parameter;
|
||||
|
|
@ -154,16 +156,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true));
|
||||
|
||||
var mode = UserSettings.Default.LoadingMode;
|
||||
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 fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
||||
if (fileStream.ReadUInt32() == _IS_LZ4)
|
||||
|
|
@ -178,41 +171,25 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
|
||||
var entries = new List<VfsEntry>();
|
||||
|
||||
var mode = UserSettings.Default.LoadingMode;
|
||||
switch (mode)
|
||||
{
|
||||
case ELoadingMode.AllButNew:
|
||||
{
|
||||
var paths = new HashSet<string>();
|
||||
var magic = archive.Read<uint>();
|
||||
if (magic != BackupManagerViewModel.FBKP_MAGIC)
|
||||
var paths = new Dictionary<string, int>();
|
||||
while (archive.Position < archive.Length)
|
||||
{
|
||||
archive.Position -= sizeof(uint);
|
||||
while (archive.Position < archive.Length)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
archive.Position += 29;
|
||||
paths.Add(archive.ReadString().ToLower()[1..]);
|
||||
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..]);
|
||||
}
|
||||
archive.Position += 29;
|
||||
paths[archive.ReadString().ToLower()[1..]] = 0;
|
||||
archive.Position += 4;
|
||||
}
|
||||
|
||||
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (value is not VfsEntry entry || paths.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;
|
||||
|
||||
entries.Add(entry);
|
||||
|
|
@ -223,54 +200,31 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
}
|
||||
case ELoadingMode.AllButModified:
|
||||
{
|
||||
var magic = archive.Read<uint>();
|
||||
if (magic != BackupManagerViewModel.FBKP_MAGIC)
|
||||
while (archive.Position < archive.Length)
|
||||
{
|
||||
archive.Position -= sizeof(uint);
|
||||
while (archive.Position < archive.Length)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
archive.Position += 16;
|
||||
var uncompressedSize = archive.Read<long>();
|
||||
var isEncrypted = archive.ReadFlag();
|
||||
archive.Position += 4;
|
||||
var fullPath = archive.ReadString().ToLower()[1..];
|
||||
archive.Position += 4;
|
||||
archive.Position += 16;
|
||||
var uncompressedSize = archive.Read<long>();
|
||||
var isEncrypted = archive.ReadFlag();
|
||||
archive.Position += 4;
|
||||
var fullPath = archive.ReadString().ToLower()[1..];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return 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);
|
||||
_applicationView.Status.UpdateStatusLabel($"{mode.ToString()[6..]} Folders & Packages");
|
||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
switch (parameter)
|
||||
{
|
||||
case "Directory_Selector":
|
||||
contextViewModel.AvoidEmptyGameDirectory(true);
|
||||
contextViewModel.AvoidEmptyGameDirectoryAndSetEGame(true);
|
||||
break;
|
||||
case "Directory_AES":
|
||||
Helper.OpenWindow<AdonisWindow>("AES Manager", () => new AesManager().Show());
|
||||
break;
|
||||
case "Directory_Backup":
|
||||
Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.InternalGameName).Show());
|
||||
Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.GameName).Show());
|
||||
break;
|
||||
case "Directory_ArchivesInfo":
|
||||
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
|
||||
|
|
@ -42,20 +42,28 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Views_AudioPlayer":
|
||||
Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show());
|
||||
break;
|
||||
case "Views_MapViewer":
|
||||
Helper.OpenWindow<AdonisWindow>("Map Viewer", () => new MapViewer().Show());
|
||||
break;
|
||||
case "Views_ImageMerger":
|
||||
Helper.OpenWindow<AdonisWindow>("Image Merger", () => new ImageMerger().Show());
|
||||
break;
|
||||
case "Settings":
|
||||
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
|
||||
break;
|
||||
case "ModelSettings":
|
||||
UserSettings.Default.LastOpenedSettingTab = contextViewModel.CUE4Parse.Game == FGame.FortniteGame ? 2 : 1;
|
||||
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
|
||||
break;
|
||||
case "Help_About":
|
||||
Helper.OpenWindow<AdonisWindow>("About", () => new About().Show());
|
||||
break;
|
||||
case "Help_Donate":
|
||||
Process.Start(new ProcessStartInfo { FileName = Constants.DONATE_LINK, UseShellExecute = true });
|
||||
break;
|
||||
case "Help_Releases":
|
||||
Helper.OpenWindow<AdonisWindow>("Releases", () => new UpdateView().Show());
|
||||
case "Help_Changelog":
|
||||
UserSettings.Default.ShowChangelog = true;
|
||||
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
|
||||
break;
|
||||
case "Help_BugsReport":
|
||||
Process.Start(new ProcessStartInfo { FileName = Constants.ISSUE_LINK, UseShellExecute = true });
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
|
||||
if (!assetItems.Any()) return;
|
||||
|
||||
var updateUi = assetItems.Length > 1 ? EBulkType.Auto : EBulkType.None;
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
switch (trigger)
|
||||
|
|
@ -48,7 +47,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties | updateUi);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Textures":
|
||||
|
|
@ -56,7 +55,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures | updateUi);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Models":
|
||||
|
|
@ -64,7 +63,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | updateUi);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | EBulkType.Auto);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Animations":
|
||||
|
|
@ -72,7 +71,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | updateUi);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | EBulkType.Auto);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,37 @@ using FModel.ViewModels.Commands;
|
|||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class CustomDirectory : ViewModel
|
||||
{
|
||||
private string _header;
|
||||
public string Header
|
||||
{
|
||||
get => _header;
|
||||
set => SetProperty(ref _header, value);
|
||||
}
|
||||
|
||||
private string _directoryPath;
|
||||
public string DirectoryPath
|
||||
{
|
||||
get => _directoryPath;
|
||||
set => SetProperty(ref _directoryPath, value);
|
||||
}
|
||||
|
||||
public CustomDirectory()
|
||||
{
|
||||
Header = string.Empty;
|
||||
DirectoryPath = string.Empty;
|
||||
}
|
||||
|
||||
public CustomDirectory(string header, string path)
|
||||
{
|
||||
Header = header;
|
||||
DirectoryPath = path;
|
||||
}
|
||||
|
||||
public override string ToString() => Header;
|
||||
}
|
||||
|
||||
public class CustomDirectoriesViewModel : ViewModel
|
||||
{
|
||||
private GoToCommand _goToCommand;
|
||||
|
|
@ -23,8 +54,13 @@ public class CustomDirectoriesViewModel : ViewModel
|
|||
private readonly ObservableCollection<Control> _directories;
|
||||
public ReadOnlyObservableCollection<Control> Directories { get; }
|
||||
|
||||
public CustomDirectoriesViewModel()
|
||||
private readonly FGame _game;
|
||||
private readonly string _gameDirectoryAtLaunch;
|
||||
|
||||
public CustomDirectoriesViewModel(FGame game, string directory)
|
||||
{
|
||||
_game = game;
|
||||
_gameDirectoryAtLaunch = directory;
|
||||
_directories = new ObservableCollection<Control>(EnumerateDirectories());
|
||||
Directories = new ReadOnlyObservableCollection<Control>(_directories);
|
||||
}
|
||||
|
|
@ -38,7 +74,6 @@ public class CustomDirectoriesViewModel : ViewModel
|
|||
public void Add(CustomDirectory dir)
|
||||
{
|
||||
_directories.Add(new MenuItem { Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir) });
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Edit(int index, CustomDirectory newDir)
|
||||
|
|
@ -47,25 +82,25 @@ public class CustomDirectoriesViewModel : ViewModel
|
|||
|
||||
dir.Header = newDir.Header;
|
||||
dir.Tag = newDir.DirectoryPath;
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Delete(int index)
|
||||
{
|
||||
_directories.RemoveAt(index);
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
var directories = new List<CustomDirectory>();
|
||||
var cd = new List<CustomDirectory>();
|
||||
for (var i = 2; i < _directories.Count; i++)
|
||||
{
|
||||
if (_directories[i] is not MenuItem m) continue;
|
||||
directories.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString()));
|
||||
cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString()));
|
||||
}
|
||||
|
||||
UserSettings.Default.CurrentDir.Directories = directories;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch))
|
||||
UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd;
|
||||
else UserSettings.Default.CustomDirectories[_game] = cd;
|
||||
}
|
||||
|
||||
private IEnumerable<Control> EnumerateDirectories()
|
||||
|
|
@ -80,7 +115,12 @@ public class CustomDirectoriesViewModel : ViewModel
|
|||
};
|
||||
yield return new Separator();
|
||||
|
||||
foreach (var setting in UserSettings.Default.CurrentDir.Directories)
|
||||
IList<CustomDirectory> cd;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameDirectoryAtLaunch, out var settings))
|
||||
cd = settings.CustomDirectories;
|
||||
else cd = UserSettings.Default.CustomDirectories[_game];
|
||||
|
||||
foreach (var setting in cd)
|
||||
{
|
||||
if (setting.DirectoryPath.EndsWith('/'))
|
||||
setting.DirectoryPath = setting.DirectoryPath[..^1];
|
||||
|
|
@ -127,4 +167,4 @@ public class CustomDirectoriesViewModel : ViewModel
|
|||
CommandParameter = dir
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
using FModel.Framework;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using CUE4Parse.UE4.IO;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.VirtualFileSystem;
|
||||
|
||||
|
|
@ -75,17 +72,6 @@ public class FileItem : ViewModel
|
|||
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()
|
||||
{
|
||||
return $"{Name} | {Key}";
|
||||
|
|
@ -98,35 +84,31 @@ public class GameDirectoryViewModel : ViewModel
|
|||
public readonly ObservableCollection<FileItem> DirectoryFiles;
|
||||
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()
|
||||
{
|
||||
DirectoryFiles = new ObservableCollection<FileItem>();
|
||||
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)
|
||||
{
|
||||
if (!_hiddenArchives.IsMatch(reader.Name)) return;
|
||||
|
||||
var fileItem = new FileItem(reader);
|
||||
Application.Current.Dispatcher.Invoke(() => DirectoryFiles.Add(fileItem));
|
||||
}
|
||||
|
||||
public void Verify(IAesVfsReader reader)
|
||||
{
|
||||
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;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
DirectoryFiles.Add(new FileItem(reader.Name, reader.Length)
|
||||
{
|
||||
Guid = reader.EncryptionKeyGuid,
|
||||
IsEncrypted = reader.IsEncrypted,
|
||||
IsEnabled = false,
|
||||
Key = string.Empty
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,148 +22,171 @@ public class GameSelectorViewModel : ViewModel
|
|||
{
|
||||
public string GameName { get; set; }
|
||||
public string GameDirectory { get; set; }
|
||||
public EGame OverridedGame { get; set; }
|
||||
public bool IsManual { get; set; }
|
||||
|
||||
// the followings are only used when game is manually added
|
||||
public AesResponse AesKeys { get; set; }
|
||||
public EGame OverridedGame { get; set; }
|
||||
public List<FCustomVersion> OverridedCustomVersions { get; set; }
|
||||
public Dictionary<string, bool> OverridedOptions { get; set; }
|
||||
public Dictionary<string, KeyValuePair<string, string>> OverridedMapStructTypes { get; set; }
|
||||
public IList<CustomDirectory> CustomDirectories { get; set; }
|
||||
}
|
||||
|
||||
private DirectorySettings _selectedDirectory;
|
||||
public DirectorySettings SelectedDirectory
|
||||
private DetectedGame _selectedDetectedGame;
|
||||
public DetectedGame SelectedDetectedGame
|
||||
{
|
||||
get => _selectedDirectory;
|
||||
set => SetProperty(ref _selectedDirectory, value);
|
||||
get => _selectedDetectedGame;
|
||||
set => SetProperty(ref _selectedDetectedGame, value);
|
||||
}
|
||||
|
||||
private readonly ObservableCollection<DirectorySettings> _detectedDirectories;
|
||||
public ReadOnlyObservableCollection<DirectorySettings> DetectedDirectories { get; }
|
||||
public ReadOnlyObservableCollection<EGame> UeGames { get; }
|
||||
private readonly ObservableCollection<DetectedGame> _autoDetectedGames;
|
||||
public ReadOnlyObservableCollection<DetectedGame> AutoDetectedGames { get; }
|
||||
|
||||
public GameSelectorViewModel(string gameDirectory)
|
||||
{
|
||||
_detectedDirectories = new ObservableCollection<DirectorySettings>(EnumerateDetectedGames().Where(x => x != null));
|
||||
foreach (var dir in UserSettings.Default.PerDirectory.Values.Where(x => x.IsManual))
|
||||
_autoDetectedGames = new ObservableCollection<DetectedGame>(EnumerateDetectedGames().Where(x => x != null));
|
||||
foreach (var game in UserSettings.Default.ManualGames.Values)
|
||||
{
|
||||
_detectedDirectories.Add((DirectorySettings) dir.Clone());
|
||||
_autoDetectedGames.Add(game);
|
||||
}
|
||||
|
||||
DetectedDirectories = new ReadOnlyObservableCollection<DirectorySettings>(_detectedDirectories);
|
||||
AutoDetectedGames = new ReadOnlyObservableCollection<DetectedGame>(_autoDetectedGames);
|
||||
|
||||
if (DetectedDirectories.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame)
|
||||
SelectedDirectory = detectedGame;
|
||||
if (AutoDetectedGames.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame)
|
||||
SelectedDetectedGame = detectedGame;
|
||||
else if (!string.IsNullOrEmpty(gameDirectory))
|
||||
AddUndetectedDir(gameDirectory);
|
||||
AddUnknownGame(gameDirectory);
|
||||
else
|
||||
SelectedDirectory = DetectedDirectories.FirstOrDefault();
|
||||
|
||||
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
|
||||
SelectedDetectedGame = AutoDetectedGames.FirstOrDefault();
|
||||
}
|
||||
|
||||
public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory);
|
||||
public void AddUndetectedDir(string gameName, string gameDirectory)
|
||||
/// <summary>
|
||||
/// dedicated to manual games
|
||||
/// </summary>
|
||||
public void AddUnknownGame(string gameName, string gameDirectory)
|
||||
{
|
||||
var setting = DirectorySettings.Default(gameName, gameDirectory, true);
|
||||
UserSettings.Default.PerDirectory[gameDirectory] = setting;
|
||||
_detectedDirectories.Add(setting);
|
||||
SelectedDirectory = DetectedDirectories.Last();
|
||||
var game = new DetectedGame
|
||||
{
|
||||
GameName = gameName,
|
||||
GameDirectory = gameDirectory,
|
||||
IsManual = true,
|
||||
AesKeys = null,
|
||||
OverridedGame = EGame.GAME_UE4_LATEST,
|
||||
OverridedCustomVersions = null,
|
||||
OverridedOptions = null,
|
||||
OverridedMapStructTypes = null,
|
||||
CustomDirectories = new List<CustomDirectory>()
|
||||
};
|
||||
|
||||
UserSettings.Default.ManualGames[gameDirectory] = game;
|
||||
_autoDetectedGames.Add(game);
|
||||
SelectedDetectedGame = AutoDetectedGames.Last();
|
||||
}
|
||||
|
||||
public void AddUnknownGame(string gameDirectory)
|
||||
{
|
||||
_autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory });
|
||||
SelectedDetectedGame = AutoDetectedGames.Last();
|
||||
}
|
||||
|
||||
public void DeleteSelectedGame()
|
||||
{
|
||||
UserSettings.Default.PerDirectory.Remove(SelectedDirectory.GameDirectory); // should not be a problem
|
||||
_detectedDirectories.Remove(SelectedDirectory);
|
||||
SelectedDirectory = DetectedDirectories.Last();
|
||||
UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem
|
||||
_autoDetectedGames.Remove(SelectedDetectedGame);
|
||||
SelectedDetectedGame = AutoDetectedGames.Last();
|
||||
}
|
||||
|
||||
private IEnumerable<EGame> EnumerateUeGames()
|
||||
=> Enum.GetValues<EGame>()
|
||||
.GroupBy(value => (int)value)
|
||||
.Select(group => group.First())
|
||||
.OrderBy(value => (int)value == ((int)value & ~0xF));
|
||||
private IEnumerable<DirectorySettings> EnumerateDetectedGames()
|
||||
private IEnumerable<DetectedGame> EnumerateDetectedGames()
|
||||
{
|
||||
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_5);
|
||||
yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_5);
|
||||
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks", EGame.GAME_RogueCompany);
|
||||
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks", EGame.GAME_UE4_21);
|
||||
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks", EGame.GAME_Borderlands3);
|
||||
yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks", EGame.GAME_UE4_27);
|
||||
yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks", EGame.GAME_StarWarsJediFallenOrder);
|
||||
yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks", EGame.GAME_StateOfDecay2);
|
||||
yield return GetUnrealEngineGame("711c5e95dc094ca58e5f16bd48e751d6", "\\MultiVersus\\Content\\Paks", EGame.GAME_UE4_26);
|
||||
yield return GetUnrealEngineGame("9361c8c6d2f34b42b5f2f61093eedf48", "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds);
|
||||
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks", EGame.GAME_Valorant);
|
||||
yield return DirectorySettings.Default("VALORANT [LIVE]", Constants._VAL_LIVE_TRIGGER, ue: EGame.GAME_Valorant);
|
||||
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks", EGame.GAME_UE4_27); // Dead By Daylight
|
||||
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds); // PUBG
|
||||
yield return GetSteamGame(1172380, "\\SwGame\\Content\\Paks", EGame.GAME_StarWarsJediFallenOrder); // STAR WARS Jedi: Fallen Order™
|
||||
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks", EGame.GAME_Splitgate); // Splitgate
|
||||
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks", EGame.GAME_SeaOfThieves); // Sea of Thieves
|
||||
yield return GetSteamGame(1665460, "\\pak", EGame.GAME_UE4_26); // eFootball 2023
|
||||
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition);
|
||||
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition);
|
||||
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition);
|
||||
yield return GetLevelInfiniteGame("tof_launcher", "\\Hotta\\Content\\Paks", EGame.GAME_TowerOfFantasy);
|
||||
yield return GetUnrealEngineGame("Fortnite");
|
||||
yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER };
|
||||
yield return GetUnrealEngineGame("Pewee");
|
||||
yield return GetUnrealEngineGame("Rosemallow");
|
||||
yield return GetUnrealEngineGame("Catnip");
|
||||
yield return GetUnrealEngineGame("AzaleaAlpha");
|
||||
yield return GetUnrealEngineGame("WorldExplorersLive");
|
||||
yield return GetUnrealEngineGame("Newt");
|
||||
yield return GetUnrealEngineGame("shoebill");
|
||||
yield return GetUnrealEngineGame("Snoek");
|
||||
yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508");
|
||||
yield return GetUnrealEngineGame("Nebula");
|
||||
yield return GetUnrealEngineGame("711c5e95dc094ca58e5f16bd48e751d6");
|
||||
yield return GetUnrealEngineGame("9361c8c6d2f34b42b5f2f61093eedf48");
|
||||
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks");
|
||||
yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER };
|
||||
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks");
|
||||
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight
|
||||
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG
|
||||
yield return GetSteamGame(1172380, "\\SwGame\\Content\\Paks"); // STAR WARS Jedi: Fallen Order™
|
||||
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate
|
||||
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves
|
||||
yield return GetSteamGame(1665460, "\\pak"); // eFootball 2023
|
||||
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks");
|
||||
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks");
|
||||
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks");
|
||||
yield return GetLevelInfiniteGame("tof_launcher", "\\Hotta\\Content\\Paks");
|
||||
}
|
||||
|
||||
private LauncherInstalled _launcherInstalled;
|
||||
private DirectorySettings GetUnrealEngineGame(string gameName, string pakDirectory, EGame ueVersion)
|
||||
private DetectedGame GetUnrealEngineGame(string gameName)
|
||||
{
|
||||
_launcherInstalled ??= GetDriveLauncherInstalls<LauncherInstalled>("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat");
|
||||
if (_launcherInstalled?.InstallationList != null)
|
||||
{
|
||||
foreach (var installationList in _launcherInstalled.InstallationList)
|
||||
{
|
||||
var gameDir = $"{installationList.InstallLocation}{pakDirectory}";
|
||||
if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase) && Directory.Exists(gameDir))
|
||||
if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Log.Debug("Found {GameName} in LauncherInstalled.dat", gameName);
|
||||
return DirectorySettings.Default(installationList.AppName, gameDir, ue: ueVersion);
|
||||
var pak = Directory.GetDirectories(installationList.InstallLocation, "Paks*", SearchOption.AllDirectories);
|
||||
if (pak.Length > 0) return new DetectedGame { GameName = installationList.AppName, GameDirectory = pak[0] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.Warning("Could not find {GameName} in LauncherInstalled.dat", gameName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private RiotClientInstalls _riotClientInstalls;
|
||||
private DirectorySettings GetRiotGame(string gameName, string pakDirectory, EGame ueVersion)
|
||||
private DetectedGame GetRiotGame(string gameName, string pakDirectory)
|
||||
{
|
||||
_riotClientInstalls ??= GetDriveLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json");
|
||||
if (_riotClientInstalls is { AssociatedClient: { } })
|
||||
{
|
||||
foreach (var (key, _) in _riotClientInstalls.AssociatedClient)
|
||||
{
|
||||
var gameDir = $"{key.Replace('/', '\\')}{pakDirectory}";
|
||||
if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase) && Directory.Exists(gameDir))
|
||||
{
|
||||
Log.Debug("Found {GameName} in RiotClientInstalls.json", gameName);
|
||||
return DirectorySettings.Default(gameName, gameDir, ue: ueVersion);
|
||||
}
|
||||
if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase))
|
||||
return new DetectedGame { GameName = gameName, GameDirectory = $"{key.Replace('/', '\\')}{pakDirectory}" };
|
||||
}
|
||||
}
|
||||
|
||||
Log.Warning("Could not find {GameName} in RiotClientInstalls.json", gameName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private DirectorySettings GetSteamGame(int id, string pakDirectory, EGame ueVersion)
|
||||
private LauncherSettings _launcherSettings;
|
||||
private DetectedGame GetMojangGame(string gameName, string pakDirectory)
|
||||
{
|
||||
_launcherSettings ??= GetDataLauncherInstalls<LauncherSettings>("\\.minecraft\\launcher_settings.json");
|
||||
if (_launcherSettings is { ProductLibraryDir: { } })
|
||||
return new DetectedGame { GameName = gameName, GameDirectory = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}" };
|
||||
|
||||
Log.Warning("Could not find {GameName} in launcher_settings.json", gameName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private DetectedGame GetSteamGame(int id, string pakDirectory)
|
||||
{
|
||||
var steamInfo = SteamDetection.GetSteamGameById(id);
|
||||
if (steamInfo is not null)
|
||||
{
|
||||
Log.Debug("Found {GameName} in steam manifests", steamInfo.Name);
|
||||
return DirectorySettings.Default(steamInfo.Name, $"{steamInfo.GameRoot}{pakDirectory}", ue: ueVersion);
|
||||
}
|
||||
return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" };
|
||||
|
||||
Log.Warning("Could not find {GameId} in steam manifests", id);
|
||||
return null;
|
||||
}
|
||||
|
||||
private DirectorySettings GetRockstarGamesGame(string key, string pakDirectory, EGame ueVersion)
|
||||
private DetectedGame GetRockstarGamesGame(string key, string pakDirectory)
|
||||
{
|
||||
var installLocation = string.Empty;
|
||||
try
|
||||
|
|
@ -175,17 +198,14 @@ public class GameSelectorViewModel : ViewModel
|
|||
// ignored
|
||||
}
|
||||
|
||||
var gameDir = $"{installLocation}{pakDirectory}";
|
||||
if (Directory.Exists(gameDir))
|
||||
{
|
||||
Log.Debug("Found {GameName} in the registry", key);
|
||||
return DirectorySettings.Default(key, gameDir, ue: ueVersion);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(installLocation))
|
||||
return new DetectedGame { GameName = key, GameDirectory = $"{installLocation}{pakDirectory}" };
|
||||
|
||||
Log.Warning("Could not find {GameName} in the registry", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
private DirectorySettings GetLevelInfiniteGame(string key, string pakDirectory, EGame ueVersion)
|
||||
private DetectedGame GetLevelInfiniteGame(string key, string pakDirectory)
|
||||
{
|
||||
var installLocation = string.Empty;
|
||||
var displayName = string.Empty;
|
||||
|
|
@ -200,13 +220,10 @@ public class GameSelectorViewModel : ViewModel
|
|||
// ignored
|
||||
}
|
||||
|
||||
var gameDir = $"{installLocation}{pakDirectory}";
|
||||
if (Directory.Exists(gameDir))
|
||||
{
|
||||
Log.Debug("Found {GameName} in the registry", key);
|
||||
return DirectorySettings.Default(displayName, gameDir, ue: ueVersion);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(installLocation))
|
||||
return new DetectedGame { GameName = displayName, GameDirectory = $"{installLocation}{pakDirectory}" };
|
||||
|
||||
Log.Warning("Could not find {GameName} in the registry", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -217,10 +234,25 @@ public class GameSelectorViewModel : ViewModel
|
|||
var launcher = $"{drive.Name}{jsonFile}";
|
||||
if (!File.Exists(launcher)) continue;
|
||||
|
||||
Log.Debug("\"{Launcher}\" found in drive \"{DriveName}\"", launcher, drive.Name);
|
||||
Log.Information("\"{Launcher}\" found in drive \"{DriveName}\"", launcher, drive.Name);
|
||||
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
|
||||
}
|
||||
|
||||
Log.Warning("\"{JsonFile}\" not found in any drives", jsonFile);
|
||||
return default;
|
||||
}
|
||||
|
||||
private T GetDataLauncherInstalls<T>(string jsonFile)
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var launcher = $"{appData}{jsonFile}";
|
||||
if (File.Exists(launcher))
|
||||
{
|
||||
Log.Information("\"{Launcher}\" found in \"{AppData}\"", launcher, appData);
|
||||
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
|
||||
}
|
||||
|
||||
Log.Warning("\"{Json}\" not found anywhere", jsonFile);
|
||||
return default;
|
||||
}
|
||||
|
||||
|
|
@ -289,10 +321,7 @@ public class GameSelectorViewModel : ViewModel
|
|||
private static List<AppInfo> GetSteamApps(IEnumerable<string> steamLibs)
|
||||
{
|
||||
var apps = new List<AppInfo>();
|
||||
foreach (var files in steamLibs
|
||||
.Select(lib => Path.Combine(lib, "SteamApps"))
|
||||
.Select(appMetaDataPath => Directory.Exists(appMetaDataPath) ? Directory.GetFiles(appMetaDataPath, "*.acf") : null)
|
||||
.Where(files => files != null))
|
||||
foreach (var files in steamLibs.Select(lib => Path.Combine(lib, "SteamApps")).Select(appMetaDataPath => Directory.GetFiles(appMetaDataPath, "*.acf")))
|
||||
{
|
||||
apps.AddRange(files.Select(GetAppInfo).Where(appInfo => appInfo != null));
|
||||
}
|
||||
|
|
@ -317,16 +346,16 @@ public class GameSelectorViewModel : ViewModel
|
|||
!dic.TryGetValue("name", out var name) ||
|
||||
!dic.TryGetValue("installDir", out var installDir)) return null;
|
||||
|
||||
var path = Path.GetDirectoryName(appMetaFile) ?? "";
|
||||
var path = Path.GetDirectoryName(appMetaFile);
|
||||
var libGameRoot = Path.Combine(path, "common", installDir);
|
||||
|
||||
return Directory.Exists(libGameRoot) ? new AppInfo { Id = appId, Name = name, GameRoot = libGameRoot } : null;
|
||||
return !Directory.Exists(libGameRoot) ? null : new AppInfo { Id = appId, Name = name, GameRoot = libGameRoot };
|
||||
}
|
||||
|
||||
private static List<string> GetSteamLibs()
|
||||
{
|
||||
var steamPath = GetSteamPath();
|
||||
if (steamPath == null || !Directory.Exists(steamPath)) return new List<string>();
|
||||
if (steamPath == null) return new List<string>();
|
||||
var libraries = new List<string> { steamPath };
|
||||
|
||||
var listFile = Path.Combine(steamPath, @"steamapps\libraryfolders.vdf");
|
||||
|
|
@ -337,7 +366,7 @@ public class GameSelectorViewModel : ViewModel
|
|||
var match = Regex.Match(line, @"""(?<path>\w:\\\\.*)""");
|
||||
if (!match.Success) continue;
|
||||
var path = match.Groups["path"].Value.Replace(@"\\", @"\");
|
||||
if (Directory.Exists(path) && !libraries.Contains(path))
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
libraries.Add(path);
|
||||
}
|
||||
|
|
|
|||
878
FModel/ViewModels/MapViewerViewModel.cs
Normal file
|
|
@ -0,0 +1,878 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Imaging;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.GameplayTags;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Creator;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class MapLayer
|
||||
{
|
||||
public SKBitmap Layer;
|
||||
public bool IsEnabled;
|
||||
}
|
||||
|
||||
public enum EWaypointType
|
||||
{
|
||||
Parkour,
|
||||
TimeTrials
|
||||
}
|
||||
|
||||
public class MapViewerViewModel : ViewModel
|
||||
{
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
|
||||
|
||||
#region BINDINGS
|
||||
|
||||
private bool _brPois;
|
||||
public bool BrPois
|
||||
{
|
||||
get => _brPois;
|
||||
set => SetProperty(ref _brPois, value, "ApolloGameplay_MapPois");
|
||||
}
|
||||
|
||||
private bool _brLandmarks;
|
||||
public bool BrLandmarks
|
||||
{
|
||||
get => _brLandmarks;
|
||||
set => SetProperty(ref _brLandmarks, value, "ApolloGameplay_MapLandmarks");
|
||||
}
|
||||
|
||||
private bool _brTagsLocation;
|
||||
public bool BrTagsLocation
|
||||
{
|
||||
get => _brTagsLocation;
|
||||
set => SetProperty(ref _brTagsLocation, value, "ApolloGameplay_TagsLocation");
|
||||
}
|
||||
|
||||
private bool _brPatrolsPath;
|
||||
public bool BrPatrolsPath
|
||||
{
|
||||
get => _brPatrolsPath;
|
||||
set => SetProperty(ref _brPatrolsPath, value, "ApolloGameplay_PatrolsPath");
|
||||
}
|
||||
|
||||
private bool _brUpgradeBenches;
|
||||
public bool BrUpgradeBenches
|
||||
{
|
||||
get => _brUpgradeBenches;
|
||||
set => SetProperty(ref _brUpgradeBenches, value, "ApolloGameplay_UpgradeBenches");
|
||||
}
|
||||
|
||||
private bool _brPhonebooths;
|
||||
public bool BrPhonebooths
|
||||
{
|
||||
get => _brPhonebooths;
|
||||
set => SetProperty(ref _brPhonebooths, value, "ApolloGameplay_Phonebooths");
|
||||
}
|
||||
|
||||
private bool _brVendingMachines;
|
||||
public bool BrVendingMachines
|
||||
{
|
||||
get => _brVendingMachines;
|
||||
set => SetProperty(ref _brVendingMachines, value, "ApolloGameplay_VendingMachines");
|
||||
}
|
||||
|
||||
private bool _brBountyBoards;
|
||||
public bool BrBountyBoards
|
||||
{
|
||||
get => _brBountyBoards;
|
||||
set => SetProperty(ref _brBountyBoards, value, "ApolloGameplay_BountyBoards");
|
||||
}
|
||||
|
||||
private bool _prLandmarks;
|
||||
public bool PrLandmarks
|
||||
{
|
||||
get => _prLandmarks;
|
||||
set => SetProperty(ref _prLandmarks, value, "PapayaGameplay_MapLandmarks");
|
||||
}
|
||||
|
||||
private bool _prCannonball;
|
||||
public bool PrCannonball
|
||||
{
|
||||
get => _prCannonball;
|
||||
set => SetProperty(ref _prCannonball, value, "PapayaGameplay_CannonballGame");
|
||||
}
|
||||
|
||||
private bool _prSkydive;
|
||||
public bool PrSkydive
|
||||
{
|
||||
get => _prSkydive;
|
||||
set => SetProperty(ref _prSkydive, value, "PapayaGameplay_SkydiveGame");
|
||||
}
|
||||
|
||||
private bool _prShootingTargets;
|
||||
public bool PrShootingTargets
|
||||
{
|
||||
get => _prShootingTargets;
|
||||
set => SetProperty(ref _prShootingTargets, value, "PapayaGameplay_ShootingTargets");
|
||||
}
|
||||
|
||||
private bool _prParkour;
|
||||
public bool PrParkour
|
||||
{
|
||||
get => _prParkour;
|
||||
set => SetProperty(ref _prParkour, value, "PapayaGameplay_ParkourGame");
|
||||
}
|
||||
|
||||
private bool _prTimeTrials;
|
||||
public bool PrTimeTrials
|
||||
{
|
||||
get => _prTimeTrials;
|
||||
set => SetProperty(ref _prTimeTrials, value, "PapayaGameplay_TimeTrials");
|
||||
}
|
||||
|
||||
private bool _prVendingMachines;
|
||||
public bool PrVendingMachines
|
||||
{
|
||||
get => _prVendingMachines;
|
||||
set => SetProperty(ref _prVendingMachines, value, "PapayaGameplay_VendingMachines");
|
||||
}
|
||||
|
||||
private bool _prMusicBlocks;
|
||||
public bool PrMusicBlocks
|
||||
{
|
||||
get => _prMusicBlocks;
|
||||
set => SetProperty(ref _prMusicBlocks, value, "PapayaGameplay_MusicBlocks");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BITMAP IMAGES
|
||||
|
||||
private BitmapImage _brMiniMapImage;
|
||||
private BitmapImage _prMiniMapImage;
|
||||
private BitmapImage _mapImage;
|
||||
public BitmapImage MapImage
|
||||
{
|
||||
get => _mapImage;
|
||||
set => SetProperty(ref _mapImage, value);
|
||||
}
|
||||
|
||||
private BitmapImage _brLayerImage;
|
||||
private BitmapImage _prLayerImage;
|
||||
private BitmapImage _layerImage;
|
||||
public BitmapImage LayerImage
|
||||
{
|
||||
get => _layerImage;
|
||||
set => SetProperty(ref _layerImage, value);
|
||||
}
|
||||
|
||||
private const int _widthHeight = 2048;
|
||||
private const int _brRadius = 141000;
|
||||
private const int _prRadius = 51000;
|
||||
private int _mapIndex;
|
||||
public int MapIndex // 0 is BR, 1 is PR
|
||||
{
|
||||
get => _mapIndex;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _mapIndex, value);
|
||||
TriggerChange();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private const string _FIRST_BITMAP = "MapCheck";
|
||||
private readonly Dictionary<string, MapLayer>[] _bitmaps; // first bitmap is the displayed map, others are overlays of the map
|
||||
private readonly CUE4ParseViewModel _cue4Parse;
|
||||
|
||||
public MapViewerViewModel(CUE4ParseViewModel cue4Parse)
|
||||
{
|
||||
_bitmaps = new[]
|
||||
{
|
||||
new Dictionary<string, MapLayer>(),
|
||||
new Dictionary<string, MapLayer>()
|
||||
};
|
||||
_cue4Parse = cue4Parse;
|
||||
}
|
||||
|
||||
public async void Initialize()
|
||||
{
|
||||
Utils.Typefaces ??= new Typefaces(_cue4Parse);
|
||||
_textPaint.Typeface = _fillPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
|
||||
await LoadBrMiniMap();
|
||||
await LoadPrMiniMap();
|
||||
TriggerChange();
|
||||
}
|
||||
|
||||
public BitmapImage GetImageToSave() => GetImageSource(GetLayerBitmap(true));
|
||||
|
||||
private SKBitmap GetLayerBitmap(bool withMap)
|
||||
{
|
||||
var ret = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
foreach (var (key, value) in _bitmaps[MapIndex])
|
||||
{
|
||||
if (!value.IsEnabled || !withMap && key == _FIRST_BITMAP)
|
||||
continue;
|
||||
|
||||
c.DrawBitmap(value.Layer, new SKRect(0, 0, _widthHeight, _widthHeight));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected override bool SetProperty<T>(ref T storage, T value, string propertyName = null) // don't delete, else nothing will update for some reason
|
||||
{
|
||||
var ret = base.SetProperty(ref storage, value, propertyName);
|
||||
if (bool.TryParse(value.ToString(), out var b)) GenericToggle(propertyName, b);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private async void GenericToggle(string key, bool enabled)
|
||||
{
|
||||
if (_bitmaps[MapIndex].TryGetValue(key, out var layer) && layer.Layer != null)
|
||||
{
|
||||
layer.IsEnabled = enabled;
|
||||
}
|
||||
else if (enabled) // load layer
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case "ApolloGameplay_MapPois":
|
||||
case "ApolloGameplay_MapLandmarks":
|
||||
case "PapayaGameplay_MapLandmarks":
|
||||
await LoadQuestIndicatorData();
|
||||
break;
|
||||
case "ApolloGameplay_TagsLocation":
|
||||
await LoadTagsLocation();
|
||||
break;
|
||||
case "ApolloGameplay_PatrolsPath":
|
||||
await LoadPatrolsPath();
|
||||
break;
|
||||
case "ApolloGameplay_UpgradeBenches":
|
||||
await LoadUpgradeBenches();
|
||||
break;
|
||||
case "ApolloGameplay_Phonebooths":
|
||||
await LoadPhonebooths();
|
||||
break;
|
||||
case "ApolloGameplay_VendingMachines":
|
||||
await LoadBrVendingMachines();
|
||||
break;
|
||||
case "ApolloGameplay_BountyBoards":
|
||||
await LoadBountyBoards();
|
||||
break;
|
||||
case "PapayaGameplay_CannonballGame":
|
||||
await LoadCannonballGame();
|
||||
break;
|
||||
case "PapayaGameplay_SkydiveGame":
|
||||
await LoadSkydiveGame();
|
||||
break;
|
||||
case "PapayaGameplay_ShootingTargets":
|
||||
await LoadShootingTargets();
|
||||
break;
|
||||
case "PapayaGameplay_ParkourGame":
|
||||
await LoadWaypoint(EWaypointType.Parkour);
|
||||
break;
|
||||
case "PapayaGameplay_TimeTrials":
|
||||
await LoadWaypoint(EWaypointType.TimeTrials);
|
||||
break;
|
||||
case "PapayaGameplay_VendingMachines":
|
||||
await LoadPrVendingMachines();
|
||||
break;
|
||||
case "PapayaGameplay_MusicBlocks":
|
||||
await LoadMusicBlocks();
|
||||
break;
|
||||
}
|
||||
|
||||
_bitmaps[MapIndex][key].IsEnabled = true;
|
||||
}
|
||||
|
||||
switch (MapIndex)
|
||||
{
|
||||
case 0:
|
||||
_brLayerImage = GetImageSource(GetLayerBitmap(false));
|
||||
break;
|
||||
case 1:
|
||||
_prLayerImage = GetImageSource(GetLayerBitmap(false));
|
||||
break;
|
||||
}
|
||||
|
||||
TriggerChange();
|
||||
}
|
||||
|
||||
private BitmapImage GetImageSource(SKBitmap bitmap)
|
||||
{
|
||||
if (bitmap == null) return null;
|
||||
using var data = bitmap.Encode(SKEncodedImageFormat.Png, 100);
|
||||
using var stream = data.AsStream();
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.StreamSource = stream;
|
||||
image.EndInit();
|
||||
image.Freeze();
|
||||
return image;
|
||||
}
|
||||
|
||||
private void TriggerChange()
|
||||
{
|
||||
var layerCount = _bitmaps[_mapIndex].Count(x => x.Value.IsEnabled);
|
||||
var layerString = $"{layerCount} Layer{(layerCount > 1 ? "s" : "")}";
|
||||
switch (_mapIndex)
|
||||
{
|
||||
case 0:
|
||||
_discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Battle Royale ({layerString})");
|
||||
_mapImage = _brMiniMapImage;
|
||||
_layerImage = _brLayerImage;
|
||||
break;
|
||||
case 1:
|
||||
_discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Party Royale ({layerString})");
|
||||
_mapImage = _prMiniMapImage;
|
||||
_layerImage = _prLayerImage;
|
||||
break;
|
||||
}
|
||||
|
||||
RaisePropertyChanged(nameof(MapImage));
|
||||
RaisePropertyChanged(nameof(LayerImage));
|
||||
}
|
||||
|
||||
private readonly SKPaint _textPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 26
|
||||
};
|
||||
|
||||
private readonly SKPaint _fillPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
IsStroke = true, Color = SKColors.Black, TextSize = 26,
|
||||
TextAlign = SKTextAlign.Center
|
||||
};
|
||||
|
||||
private readonly SKPaint _pathPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High, IsStroke = true,
|
||||
Style = SKPaintStyle.Stroke, StrokeWidth = 5, Color = SKColors.Red,
|
||||
ImageFilter = SKImageFilter.CreateDropShadow(4, 4, 8, 8, SKColors.Black)
|
||||
};
|
||||
|
||||
private FVector2D GetMapPosition(FVector vector, int mapRadius)
|
||||
{
|
||||
const int wh = 2048 + 128 + 32;
|
||||
var nx = (vector.Y + mapRadius) / (mapRadius * 2) * wh;
|
||||
var ny = (1 - (vector.X + mapRadius) / (mapRadius * 2)) * wh;
|
||||
return new FVector2D(nx, ny);
|
||||
}
|
||||
|
||||
private async Task LoadBrMiniMap()
|
||||
{
|
||||
if (_bitmaps[0].TryGetValue(_FIRST_BITMAP, out var brMap) && brMap.Layer != null)
|
||||
return; // if map already loaded
|
||||
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerBR.Default__UIMapManagerBR_C", out UObject mapManager) ||
|
||||
!mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial")) return;
|
||||
var midTex = mapMaterial.GetFirstTexture();
|
||||
if ((midTex?.Name ?? string.Empty).Contains("Mask"))
|
||||
midTex = mapMaterial.GetTextureAtIndex(1);
|
||||
|
||||
if (midTex is not UTexture2D tex) return;
|
||||
_bitmaps[0][_FIRST_BITMAP] = new MapLayer { Layer = Utils.GetBitmap(tex), IsEnabled = true };
|
||||
_brMiniMapImage = GetImageSource(_bitmaps[0][_FIRST_BITMAP].Layer);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadPrMiniMap()
|
||||
{
|
||||
if (_bitmaps[1].TryGetValue(_FIRST_BITMAP, out var prMap) && prMap.Layer != null)
|
||||
return; // if map already loaded
|
||||
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerPapaya.Default__UIMapManagerPapaya_C", out UObject mapManager) ||
|
||||
!mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial") || mapMaterial.GetFirstTexture() is not UTexture2D tex) return;
|
||||
|
||||
_bitmaps[1][_FIRST_BITMAP] = new MapLayer { Layer = Utils.GetBitmap(tex), IsEnabled = true };
|
||||
_prMiniMapImage = GetImageSource(_bitmaps[1][_FIRST_BITMAP].Layer);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadQuestIndicatorData()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
var poisBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
var brLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
var prLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var pois = new SKCanvas(poisBitmap);
|
||||
using var brLandmarks = new SKCanvas(brLandmarksBitmap);
|
||||
using var prLandmarks = new SKCanvas(prLandmarksBitmap);
|
||||
|
||||
if (Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData", out UObject indicatorData) &&
|
||||
indicatorData.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
|
||||
{
|
||||
foreach (var poiData in challengeMapPoiData)
|
||||
{
|
||||
if (!poiData.TryGetValue(out FSoftObjectPath discoveryQuest, "DiscoveryQuest") ||
|
||||
!poiData.TryGetValue(out FText text, "Text") || string.IsNullOrEmpty(text.Text) ||
|
||||
!poiData.TryGetValue(out FVector worldLocation, "WorldLocation") ||
|
||||
!poiData.TryGetValue(out FName discoverBackend, "DiscoverObjectiveBackendName")) continue;
|
||||
|
||||
var shaper = new CustomSKShaper(_textPaint.Typeface);
|
||||
var shapedText = shaper.Shape(text.Text, _textPaint);
|
||||
|
||||
if (discoverBackend.Text.Contains("papaya", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var vector = GetMapPosition(worldLocation, _prRadius);
|
||||
prLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
prLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _fillPaint);
|
||||
prLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
else if (discoveryQuest.AssetPathName.Text.Contains("landmarks", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var vector = GetMapPosition(worldLocation, _brRadius);
|
||||
brLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
brLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _fillPaint);
|
||||
brLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
_fillPaint.StrokeWidth = 10;
|
||||
var vector = GetMapPosition(worldLocation, _brRadius);
|
||||
pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X - shapedText.Points[^1].X / 2, vector.Y, _fillPaint);
|
||||
pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X - shapedText.Points[^1].X / 2, vector.Y, _textPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_bitmaps[0]["ApolloGameplay_MapPois"] = new MapLayer { Layer = poisBitmap, IsEnabled = false };
|
||||
_bitmaps[0]["ApolloGameplay_MapLandmarks"] = new MapLayer { Layer = brLandmarksBitmap, IsEnabled = false };
|
||||
_bitmaps[1]["PapayaGameplay_MapLandmarks"] = new MapLayer { Layer = prLandmarksBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadPatrolsPath()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var patrolsPathBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(patrolsPathBitmap);
|
||||
|
||||
var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S22_NPCLibrary");
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.ExportType.Equals("FortAthenaPatrolPath", StringComparison.OrdinalIgnoreCase) ||
|
||||
!export.TryGetValue(out FPackageIndex[] patrolPoints, "PatrolPoints")) continue;
|
||||
|
||||
var displayName = export.Name["FortAthenaPatrolPath_".Length..];
|
||||
if (export.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags") && gameplayTags.GameplayTags.Length > 0)
|
||||
displayName = gameplayTags.GameplayTags[0].Text["Athena.AI.SpawnLocation.Tandem.".Length..];
|
||||
|
||||
if (!Utils.TryGetPackageIndexExport(patrolPoints[0], out UObject uObject) ||
|
||||
!uObject.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out uObject) ||
|
||||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
var path = new SKPath();
|
||||
var vector = GetMapPosition(relativeLocation, _brRadius);
|
||||
path.MoveTo(vector.X, vector.Y);
|
||||
|
||||
for (var i = 1; i < patrolPoints.Length; i++)
|
||||
{
|
||||
if (!Utils.TryGetPackageIndexExport(patrolPoints[i], out uObject) ||
|
||||
!uObject.TryGetValue(out rootComponent, "RootComponent") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out uObject) ||
|
||||
!uObject.TryGetValue(out relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
vector = GetMapPosition(relativeLocation, _brRadius);
|
||||
path.LineTo(vector.X, vector.Y);
|
||||
}
|
||||
|
||||
c.DrawPath(path, _pathPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
|
||||
_bitmaps[0]["ApolloGameplay_PatrolsPath"] = new MapLayer { Layer = patrolsPathBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadCannonballGame()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var cannonballBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(cannonballBitmap);
|
||||
|
||||
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_CannonballGame");
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.ExportType.Equals("BP_CannonballGame_Target_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("CannonballGame_VehicleSpawner_C", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
|
||||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
var displayName = Utils.GetLocalizedResource("", "D998BEF44F051E0885C6C58565934BEA", "Cannonball");
|
||||
var vector = GetMapPosition(relativeLocation, _prRadius);
|
||||
|
||||
c.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
|
||||
_bitmaps[1]["PapayaGameplay_CannonballGame"] = new MapLayer { Layer = cannonballBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadSkydiveGame()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var skydiveBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(skydiveBitmap);
|
||||
|
||||
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_SkydiveGame");
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.ExportType.Equals("BP_Waypoint_Papaya_Skydive_Start_C", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
|
||||
!export.TryGetValue(out FText minigameActivityName, "MinigameActivityName") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
|
||||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
var vector = GetMapPosition(relativeLocation, _prRadius);
|
||||
|
||||
c.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
|
||||
_bitmaps[1]["PapayaGameplay_SkydiveGame"] = new MapLayer { Layer = skydiveBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadShootingTargets()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(shootingTargetsBitmap);
|
||||
|
||||
var bDone = false;
|
||||
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_ShootingTargets");
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.ExportType.Equals("PapayaShootingTarget_C", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
|
||||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
var vector = GetMapPosition(relativeLocation, _prRadius);
|
||||
c.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
if (bDone) continue;
|
||||
|
||||
bDone = true;
|
||||
c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
|
||||
_bitmaps[1]["PapayaGameplay_ShootingTargets"] = new MapLayer { Layer = shootingTargetsBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadWaypoint(EWaypointType type)
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var waypointBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(waypointBitmap);
|
||||
|
||||
string file;
|
||||
string name;
|
||||
switch (type)
|
||||
{
|
||||
case EWaypointType.Parkour:
|
||||
file = "PapayaGameplay_ParkourGame";
|
||||
name = "Parkour";
|
||||
break;
|
||||
case EWaypointType.TimeTrials:
|
||||
file = "PapayaGameplay_TimeTrials";
|
||||
name = "Time Trials";
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
|
||||
var path = new SKPath();
|
||||
var exports = Utils.LoadExports($"/PapayaGameplay/LevelOverlays/{file}");
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.ExportType.Equals("BP_Waypoint_Parent_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject root) ||
|
||||
!root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
var vector = GetMapPosition(relativeLocation, _prRadius);
|
||||
if (path.IsEmpty || export.TryGetValue(out bool startsTrial, "StartsTrial") && startsTrial)
|
||||
{
|
||||
path.MoveTo(vector.X, vector.Y);
|
||||
c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
else if (export.TryGetValue(out bool endsTrial, "EndsTrial") && endsTrial)
|
||||
{
|
||||
path.LineTo(vector.X, vector.Y);
|
||||
c.DrawPath(path, _pathPaint);
|
||||
path = new SKPath();
|
||||
}
|
||||
else path.LineTo(vector.X, vector.Y);
|
||||
}
|
||||
|
||||
_bitmaps[1][file] = new MapLayer { Layer = waypointBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadPrVendingMachines()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var set = new HashSet<string>();
|
||||
var timeTrialsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(timeTrialsBitmap);
|
||||
|
||||
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_VendingMachines");
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.ExportType.Equals("B_Papaya_VendingMachine_Boat_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_BoogieBomb_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_Burger_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_CrashPad_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_FishingPole_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_Grappler_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_Jetpack_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Blue_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Red_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Blue_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Red_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_PlungerBow_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_Quad_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Papaya_VendingMachine_Tomato_C", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject root) ||
|
||||
!root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
var name = export.ExportType.SubstringAfter("B_Papaya_VendingMachine_").SubstringBeforeLast("_C");
|
||||
var vector = GetMapPosition(relativeLocation, _prRadius);
|
||||
c.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
if (!set.Add(name)) continue;
|
||||
|
||||
c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
|
||||
_bitmaps[1]["PapayaGameplay_VendingMachines"] = new MapLayer { Layer = timeTrialsBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadMusicBlocks()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(shootingTargetsBitmap);
|
||||
|
||||
var bDone = false;
|
||||
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_MusicBlocks");
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.ExportType.Equals("MusicBlock_Piano3_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
|
||||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
var vector = GetMapPosition(relativeLocation, _prRadius);
|
||||
c.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
if (bDone) continue;
|
||||
|
||||
bDone = true;
|
||||
c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
|
||||
_bitmaps[1]["PapayaGameplay_MusicBlocks"] = new MapLayer { Layer = shootingTargetsBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadUpgradeBenches()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var upgradeBenchesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(upgradeBenchesBitmap);
|
||||
|
||||
var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_UpgradeBenches");
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.ExportType.Equals("B_Athena_Spawner_UpgradeStation_C", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
var displayName = export.Name["B_Athena_Spawner_".Length..];
|
||||
|
||||
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
|
||||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
var vector = GetMapPosition(relativeLocation, _brRadius);
|
||||
c.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
|
||||
_bitmaps[0]["ApolloGameplay_UpgradeBenches"] = new MapLayer { Layer = upgradeBenchesBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadPhonebooths()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var phoneboothsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(phoneboothsBitmap);
|
||||
|
||||
var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Apollo_Terrain_NPCLibrary_Stations_Phonebooths");
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.ExportType.Equals("B_Athena_Spawner_Payphone_C", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
var displayName = export.Name["B_Athena_Spawner_".Length..];
|
||||
|
||||
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
|
||||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
var vector = GetMapPosition(relativeLocation, _brRadius);
|
||||
c.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
|
||||
_bitmaps[0]["ApolloGameplay_Phonebooths"] = new MapLayer { Layer = phoneboothsBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadBrVendingMachines()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var vendingMachinesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(vendingMachinesBitmap);
|
||||
|
||||
var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_ServiceStations");
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.ExportType.Equals("B_Athena_Spawner_VendingMachine_MendingOnly_C", StringComparison.OrdinalIgnoreCase) &&
|
||||
!export.ExportType.Equals("B_Athena_Spawner_VendingMachine_Random_C", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
var displayName = $"{(export.ExportType.Contains("Mending") ? "MM" : "WOM")}_{export.Name["B_Athena_Spawner_VendingMachine_Random".Length..]}";
|
||||
|
||||
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
|
||||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
var vector = GetMapPosition(relativeLocation, _brRadius);
|
||||
c.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
|
||||
_bitmaps[0]["ApolloGameplay_VendingMachines"] = new MapLayer { Layer = vendingMachinesBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadBountyBoards()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
var bountyBoardsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(bountyBoardsBitmap);
|
||||
|
||||
var exports = Utils.LoadExports("/Bounties/Maps/BB_Overlay_ServiceStations");
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.ExportType.Equals("B_Bounties_Spawner_BountyBoard_C", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
var displayName = $"BountyBoard_{export.Name["BP_BountyBoard_C_".Length..]}";
|
||||
|
||||
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
|
||||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
|
||||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
|
||||
|
||||
var vector = GetMapPosition(relativeLocation, _brRadius);
|
||||
c.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
|
||||
_bitmaps[0]["ApolloGameplay_BountyBoards"] = new MapLayer { Layer = bountyBoardsBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadTagsLocation()
|
||||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
_fillPaint.StrokeWidth = 5;
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/Quests/QuestTagToLocationDataRows.QuestTagToLocationDataRows", out UDataTable locationData))
|
||||
return;
|
||||
|
||||
var tagsLocationBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(tagsLocationBitmap);
|
||||
|
||||
foreach (var (key, uObject) in locationData.RowMap)
|
||||
{
|
||||
if (key.Text.StartsWith("Athena.Location.POI", StringComparison.OrdinalIgnoreCase) ||
|
||||
key.Text.StartsWith("Athena.Location.Unnamed", StringComparison.OrdinalIgnoreCase) ||
|
||||
key.Text.Contains(".Tandem.", StringComparison.OrdinalIgnoreCase) ||
|
||||
!uObject.TryGetValue(out FVector worldLocation, "WorldLocation")) continue;
|
||||
|
||||
var parts = key.Text.Split('.');
|
||||
var displayName = parts[^2];
|
||||
if (!int.TryParse(parts[^1], out var _))
|
||||
displayName += " " + parts[^1];
|
||||
|
||||
var vector = GetMapPosition(worldLocation, _brRadius);
|
||||
c.DrawPoint(vector.X, vector.Y, _pathPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
|
||||
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
|
||||
}
|
||||
|
||||
_bitmaps[0]["ApolloGameplay_TagsLocation"] = new MapLayer { Layer = tagsLocationBitmap, IsEnabled = false };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse_Conversion.UEFormat.Enums;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class SettingsViewModel : ViewModel
|
||||
{
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler;
|
||||
|
||||
private bool _useCustomOutputFolders;
|
||||
|
|
@ -26,6 +30,24 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _useCustomOutputFolders, value);
|
||||
}
|
||||
|
||||
private EUpdateMode _selectedUpdateMode;
|
||||
public EUpdateMode SelectedUpdateMode
|
||||
{
|
||||
get => _selectedUpdateMode;
|
||||
set => SetProperty(ref _selectedUpdateMode, value);
|
||||
}
|
||||
|
||||
private string _selectedPreset;
|
||||
public string SelectedPreset
|
||||
{
|
||||
get => _selectedPreset;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedPreset, value);
|
||||
RaisePropertyChanged("EnableElements");
|
||||
}
|
||||
}
|
||||
|
||||
private ETexturePlatform _selectedUePlatform;
|
||||
public ETexturePlatform SelectedUePlatform
|
||||
{
|
||||
|
|
@ -40,36 +62,36 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _selectedUeGame, value);
|
||||
}
|
||||
|
||||
private IList<FCustomVersion> _selectedCustomVersions;
|
||||
public IList<FCustomVersion> SelectedCustomVersions
|
||||
private List<FCustomVersion> _selectedCustomVersions;
|
||||
public List<FCustomVersion> SelectedCustomVersions
|
||||
{
|
||||
get => _selectedCustomVersions;
|
||||
set => SetProperty(ref _selectedCustomVersions, value);
|
||||
}
|
||||
|
||||
private IDictionary<string, bool> _selectedOptions;
|
||||
public IDictionary<string, bool> SelectedOptions
|
||||
private Dictionary<string, bool> _selectedOptions;
|
||||
public Dictionary<string, bool> SelectedOptions
|
||||
{
|
||||
get => _selectedOptions;
|
||||
set => SetProperty(ref _selectedOptions, value);
|
||||
}
|
||||
|
||||
private IDictionary<string, KeyValuePair<string, string>> _selectedMapStructTypes;
|
||||
public IDictionary<string, KeyValuePair<string, string>> SelectedMapStructTypes
|
||||
private Dictionary<string, KeyValuePair<string, string>> _selectedMapStructTypes;
|
||||
public Dictionary<string, KeyValuePair<string, string>> SelectedMapStructTypes
|
||||
{
|
||||
get => _selectedMapStructTypes;
|
||||
set => SetProperty(ref _selectedMapStructTypes, value);
|
||||
}
|
||||
|
||||
private EndpointSettings _aesEndpoint;
|
||||
public EndpointSettings AesEndpoint
|
||||
private FEndpoint _aesEndpoint;
|
||||
public FEndpoint AesEndpoint
|
||||
{
|
||||
get => _aesEndpoint;
|
||||
set => SetProperty(ref _aesEndpoint, value);
|
||||
}
|
||||
|
||||
private EndpointSettings _mappingEndpoint;
|
||||
public EndpointSettings MappingEndpoint
|
||||
private FEndpoint _mappingEndpoint;
|
||||
public FEndpoint MappingEndpoint
|
||||
{
|
||||
get => _mappingEndpoint;
|
||||
set => SetProperty(ref _mappingEndpoint, value);
|
||||
|
|
@ -114,12 +136,7 @@ public class SettingsViewModel : ViewModel
|
|||
public EMeshFormat SelectedMeshExportFormat
|
||||
{
|
||||
get => _selectedMeshExportFormat;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedMeshExportFormat, value);
|
||||
RaisePropertyChanged(nameof(SocketSettingsEnabled));
|
||||
RaisePropertyChanged(nameof(CompressionSettingsEnabled));
|
||||
}
|
||||
set => SetProperty(ref _selectedMeshExportFormat, value);
|
||||
}
|
||||
|
||||
private ESocketFormat _selectedSocketExportFormat;
|
||||
|
|
@ -129,13 +146,6 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _selectedSocketExportFormat, value);
|
||||
}
|
||||
|
||||
private EFileCompressionFormat _selectedCompressionFormat;
|
||||
public EFileCompressionFormat SelectedCompressionFormat
|
||||
{
|
||||
get => _selectedCompressionFormat;
|
||||
set => SetProperty(ref _selectedCompressionFormat, value);
|
||||
}
|
||||
|
||||
private ELodFormat _selectedLodExportFormat;
|
||||
public ELodFormat SelectedLodExportFormat
|
||||
{
|
||||
|
|
@ -157,9 +167,8 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _selectedTextureExportFormat, value);
|
||||
}
|
||||
|
||||
public bool SocketSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.ActorX;
|
||||
public bool CompressionSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.UEFormat;
|
||||
|
||||
public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; }
|
||||
public ObservableCollection<string> Presets { get; private set; }
|
||||
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
|
||||
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
|
||||
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
|
||||
|
|
@ -168,12 +177,15 @@ public class SettingsViewModel : ViewModel
|
|||
public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; }
|
||||
public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { 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<EMaterialFormat> MaterialExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
|
||||
|
||||
public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER;
|
||||
|
||||
private readonly FGame _game;
|
||||
private Game _gamePreset;
|
||||
private string _outputSnapshot;
|
||||
private string _rawDataSnapshot;
|
||||
private string _propertiesSnapshot;
|
||||
|
|
@ -181,26 +193,27 @@ public class SettingsViewModel : ViewModel
|
|||
private string _audioSnapshot;
|
||||
private string _modelSnapshot;
|
||||
private string _gameSnapshot;
|
||||
private EUpdateMode _updateModeSnapshot;
|
||||
private string _presetSnapshot;
|
||||
private ETexturePlatform _uePlatformSnapshot;
|
||||
private EGame _ueGameSnapshot;
|
||||
private IList<FCustomVersion> _customVersionsSnapshot;
|
||||
private IDictionary<string, bool> _optionsSnapshot;
|
||||
private IDictionary<string, KeyValuePair<string, string>> _mapStructTypesSnapshot;
|
||||
private List<FCustomVersion> _customVersionsSnapshot;
|
||||
private Dictionary<string, bool> _optionsSnapshot;
|
||||
private Dictionary<string, KeyValuePair<string, string>> _mapStructTypesSnapshot;
|
||||
private ELanguage _assetLanguageSnapshot;
|
||||
private ECompressedAudio _compressedAudioSnapshot;
|
||||
private EIconStyle _cosmeticStyleSnapshot;
|
||||
private EMeshFormat _meshExportFormatSnapshot;
|
||||
private ESocketFormat _socketExportFormatSnapshot;
|
||||
private EFileCompressionFormat _compressionFormatSnapshot;
|
||||
private ELodFormat _lodExportFormatSnapshot;
|
||||
private EMaterialFormat _materialExportFormatSnapshot;
|
||||
private ETextureFormat _textureExportFormatSnapshot;
|
||||
|
||||
private bool _mappingsUpdate = false;
|
||||
|
||||
public SettingsViewModel()
|
||||
public SettingsViewModel(FGame game)
|
||||
{
|
||||
|
||||
_game = game;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
|
|
@ -212,30 +225,46 @@ public class SettingsViewModel : ViewModel
|
|||
_audioSnapshot = UserSettings.Default.AudioDirectory;
|
||||
_modelSnapshot = UserSettings.Default.ModelDirectory;
|
||||
_gameSnapshot = UserSettings.Default.GameDirectory;
|
||||
_uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform;
|
||||
_ueGameSnapshot = UserSettings.Default.CurrentDir.UeVersion;
|
||||
_customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions;
|
||||
_optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options;
|
||||
_mapStructTypesSnapshot = UserSettings.Default.CurrentDir.Versioning.MapStructTypes;
|
||||
|
||||
AesEndpoint = UserSettings.Default.CurrentDir.Endpoints[0];
|
||||
MappingEndpoint = UserSettings.Default.CurrentDir.Endpoints[1];
|
||||
MappingEndpoint.PropertyChanged += (_, args) =>
|
||||
_updateModeSnapshot = UserSettings.Default.UpdateMode;
|
||||
_presetSnapshot = UserSettings.Default.Presets[_game];
|
||||
_uePlatformSnapshot = UserSettings.Default.OverridedPlatform;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings))
|
||||
{
|
||||
if (!_mappingsUpdate)
|
||||
_mappingsUpdate = args.PropertyName is "Overwrite" or "FilePath";
|
||||
};
|
||||
_ueGameSnapshot = settings.OverridedGame;
|
||||
_customVersionsSnapshot = settings.OverridedCustomVersions;
|
||||
_optionsSnapshot = settings.OverridedOptions;
|
||||
_mapStructTypesSnapshot = settings.OverridedMapStructTypes;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ueGameSnapshot = UserSettings.Default.OverridedGame[_game];
|
||||
_customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game];
|
||||
_optionsSnapshot = UserSettings.Default.OverridedOptions[_game];
|
||||
_mapStructTypesSnapshot = UserSettings.Default.OverridedMapStructTypes[_game];
|
||||
}
|
||||
|
||||
if (UserSettings.Default.CustomEndpoints.TryGetValue(_game, out var endpoints))
|
||||
{
|
||||
AesEndpoint = endpoints[0];
|
||||
MappingEndpoint = endpoints[1];
|
||||
MappingEndpoint.PropertyChanged += (_, args) =>
|
||||
{
|
||||
if (!_mappingsUpdate)
|
||||
_mappingsUpdate = args.PropertyName is "Overwrite" or "FilePath";
|
||||
};
|
||||
}
|
||||
|
||||
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
|
||||
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
|
||||
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
|
||||
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
|
||||
_socketExportFormatSnapshot = UserSettings.Default.SocketExportFormat;
|
||||
_compressionFormatSnapshot = UserSettings.Default.CompressionFormat;
|
||||
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
|
||||
_materialExportFormatSnapshot = UserSettings.Default.MaterialExportFormat;
|
||||
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
|
||||
|
||||
SelectedUpdateMode = _updateModeSnapshot;
|
||||
SelectedPreset = _presetSnapshot;
|
||||
SelectedUePlatform = _uePlatformSnapshot;
|
||||
SelectedUeGame = _ueGameSnapshot;
|
||||
SelectedCustomVersions = _customVersionsSnapshot;
|
||||
|
|
@ -246,13 +275,14 @@ public class SettingsViewModel : ViewModel
|
|||
SelectedCosmeticStyle = _cosmeticStyleSnapshot;
|
||||
SelectedMeshExportFormat = _meshExportFormatSnapshot;
|
||||
SelectedSocketExportFormat = _socketExportFormatSnapshot;
|
||||
SelectedCompressionFormat = _selectedCompressionFormat;
|
||||
SelectedLodExportFormat = _lodExportFormatSnapshot;
|
||||
SelectedMaterialExportFormat = _materialExportFormatSnapshot;
|
||||
SelectedTextureExportFormat = _textureExportFormatSnapshot;
|
||||
SelectedAesReload = UserSettings.Default.AesReload;
|
||||
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
|
||||
|
||||
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
|
||||
Presets = new ObservableCollection<string>(EnumeratePresets());
|
||||
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
|
||||
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
|
||||
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
|
||||
|
|
@ -261,13 +291,59 @@ public class SettingsViewModel : ViewModel
|
|||
CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles()));
|
||||
MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat()));
|
||||
SocketExportFormats = new ReadOnlyObservableCollection<ESocketFormat>(new ObservableCollection<ESocketFormat>(EnumerateSocketExportFormat()));
|
||||
CompressionFormats = new ReadOnlyObservableCollection<EFileCompressionFormat>(new ObservableCollection<EFileCompressionFormat>(EnumerateCompressionFormat()));
|
||||
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
|
||||
MaterialExportFormats = new ReadOnlyObservableCollection<EMaterialFormat>(new ObservableCollection<EMaterialFormat>(EnumerateMaterialExportFormat()));
|
||||
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
|
||||
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
|
||||
}
|
||||
|
||||
public async Task InitPresets(string gameName)
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(gameName)) return;
|
||||
_gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName);
|
||||
});
|
||||
|
||||
if (_gamePreset?.Versions == null) return;
|
||||
foreach (var version in _gamePreset.Versions.Keys)
|
||||
{
|
||||
Presets.Add(version);
|
||||
}
|
||||
}
|
||||
|
||||
public void SwitchPreset(string key)
|
||||
{
|
||||
if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return;
|
||||
SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST);
|
||||
|
||||
SelectedCustomVersions = new List<FCustomVersion>();
|
||||
foreach (var (guid, v) in version.CustomVersions)
|
||||
{
|
||||
SelectedCustomVersions.Add(new FCustomVersion { Key = new FGuid(guid), Version = v });
|
||||
}
|
||||
|
||||
SelectedOptions = new Dictionary<string, bool>();
|
||||
foreach (var (k, v) in version.Options)
|
||||
{
|
||||
SelectedOptions[k] = v;
|
||||
}
|
||||
|
||||
SelectedMapStructTypes = new Dictionary<string, KeyValuePair<string, string>>();
|
||||
foreach (var (k, v) in version.MapStructTypes)
|
||||
{
|
||||
SelectedMapStructTypes[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetPreset()
|
||||
{
|
||||
SelectedUeGame = _ueGameSnapshot;
|
||||
SelectedCustomVersions = _customVersionsSnapshot;
|
||||
SelectedOptions = _optionsSnapshot;
|
||||
SelectedMapStructTypes = _mapStructTypesSnapshot;
|
||||
}
|
||||
|
||||
public bool Save(out List<SettingsOut> whatShouldIDo)
|
||||
{
|
||||
var restart = false;
|
||||
|
|
@ -277,6 +353,8 @@ public class SettingsViewModel : ViewModel
|
|||
whatShouldIDo.Add(SettingsOut.ReloadLocres);
|
||||
if (_mappingsUpdate)
|
||||
whatShouldIDo.Add(SettingsOut.ReloadMappings);
|
||||
if (_updateModeSnapshot != SelectedUpdateMode)
|
||||
whatShouldIDo.Add(SettingsOut.CheckForUpdates);
|
||||
|
||||
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
|
||||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
|
||||
|
|
@ -290,18 +368,29 @@ public class SettingsViewModel : ViewModel
|
|||
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
|
||||
restart = true;
|
||||
|
||||
UserSettings.Default.CurrentDir.UeVersion = SelectedUeGame;
|
||||
UserSettings.Default.CurrentDir.TexturePlatform = SelectedUePlatform;
|
||||
UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions;
|
||||
UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions;
|
||||
UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes;
|
||||
UserSettings.Default.UpdateMode = SelectedUpdateMode;
|
||||
UserSettings.Default.Presets[_game] = SelectedPreset;
|
||||
UserSettings.Default.OverridedPlatform = SelectedUePlatform;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
|
||||
{
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedMapStructTypes = SelectedMapStructTypes;
|
||||
}
|
||||
else
|
||||
{
|
||||
UserSettings.Default.OverridedGame[_game] = SelectedUeGame;
|
||||
UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions;
|
||||
UserSettings.Default.OverridedOptions[_game] = SelectedOptions;
|
||||
UserSettings.Default.OverridedMapStructTypes[_game] = SelectedMapStructTypes;
|
||||
}
|
||||
|
||||
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
|
||||
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
|
||||
UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle;
|
||||
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
|
||||
UserSettings.Default.SocketExportFormat = SelectedSocketExportFormat;
|
||||
UserSettings.Default.CompressionFormat = SelectedCompressionFormat;
|
||||
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
|
||||
UserSettings.Default.MaterialExportFormat = SelectedMaterialExportFormat;
|
||||
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
|
||||
|
|
@ -314,11 +403,12 @@ public class SettingsViewModel : ViewModel
|
|||
return restart;
|
||||
}
|
||||
|
||||
private IEnumerable<EGame> EnumerateUeGames()
|
||||
=> Enum.GetValues<EGame>()
|
||||
.GroupBy(value => (int)value)
|
||||
.Select(group => group.First())
|
||||
.OrderBy(value => (int)value == ((int)value & ~0xF));
|
||||
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
|
||||
private IEnumerable<string> EnumeratePresets()
|
||||
{
|
||||
yield return Constants._NO_PRESET_TRIGGER;
|
||||
}
|
||||
private IEnumerable<EGame> EnumerateUeGames() => Enum.GetValues<EGame>();
|
||||
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
|
||||
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
|
||||
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
|
||||
|
|
@ -326,7 +416,6 @@ public class SettingsViewModel : ViewModel
|
|||
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues<EIconStyle>();
|
||||
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues<EMeshFormat>();
|
||||
private IEnumerable<ESocketFormat> EnumerateSocketExportFormat() => Enum.GetValues<ESocketFormat>();
|
||||
private IEnumerable<EFileCompressionFormat> EnumerateCompressionFormat() => Enum.GetValues<EFileCompressionFormat>();
|
||||
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
|
||||
private IEnumerable<EMaterialFormat> EnumerateMaterialExportFormat() => Enum.GetValues<EMaterialFormat>();
|
||||
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
|
||||
|
|
|
|||
|
|
@ -68,13 +68,9 @@ public class TabImage : ViewModel
|
|||
Image = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_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);
|
||||
if (UserSettings.Default.TextureExportFormat == ETextureFormat.Tga)
|
||||
return;
|
||||
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
|
|
@ -90,8 +86,6 @@ public class TabImage : ViewModel
|
|||
|
||||
public class TabItem : ViewModel
|
||||
{
|
||||
public string ParentExportType { get; private set; }
|
||||
|
||||
private string _header;
|
||||
public string Header
|
||||
{
|
||||
|
|
@ -217,58 +211,29 @@ public class TabItem : ViewModel
|
|||
private GoToCommand _goToCommand;
|
||||
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
|
||||
|
||||
public TabItem(string header, string directory, string parentExportType)
|
||||
public TabItem(string header, string directory)
|
||||
{
|
||||
Header = header;
|
||||
Directory = directory;
|
||||
ParentExportType = parentExportType;
|
||||
_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(() =>
|
||||
{
|
||||
_images.Clear();
|
||||
SelectedImage = null;
|
||||
RaisePropertyChanged("HasMultipleImages");
|
||||
|
||||
Document ??= new TextDocument();
|
||||
Document.Text = string.Empty;
|
||||
});
|
||||
}
|
||||
|
||||
public void AddImage(UTexture texture, bool save, bool 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();
|
||||
}
|
||||
}
|
||||
public void AddImage(UTexture2D texture, bool save, bool updateUi)
|
||||
=> AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform), save, updateUi);
|
||||
|
||||
AddImage(texture.Name, texture.RenderNearestNeighbor, img, save, updateUi, appendLayerNumber);
|
||||
}
|
||||
|
||||
public void AddImage(string name, bool rnn, SKBitmap[] img, bool save, bool updateUi, bool appendLayerNumber = false)
|
||||
public void AddImage(string name, bool rnn, SKBitmap[] img, bool save, bool updateUi)
|
||||
{
|
||||
for (var i = 0; i < img.Length; i++)
|
||||
{
|
||||
AddImage($"{name}{(appendLayerNumber ? $"_{i}" : "")}", rnn, img[i], save, updateUi);
|
||||
}
|
||||
foreach (var i in img) AddImage(name, rnn, i, save, 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);
|
||||
private void SaveImage(TabImage image, bool updateUi)
|
||||
{
|
||||
if (image == null) return;
|
||||
|
||||
var ext = UserSettings.Default.TextureExportFormat switch
|
||||
{
|
||||
ETextureFormat.Png => ".png",
|
||||
ETextureFormat.Jpeg => ".jpg",
|
||||
ETextureFormat.Tga => ".tga",
|
||||
_ => ".png"
|
||||
};
|
||||
|
||||
var fileName = image.ExportName + ext;
|
||||
var fileName = $"{image.ExportName}.png";
|
||||
var path = Path.Combine(UserSettings.Default.TextureDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
|
||||
|
||||
|
|
@ -395,13 +360,12 @@ public class TabControlViewModel : ViewModel
|
|||
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;
|
||||
|
||||
var h = header ?? "New Tab";
|
||||
var d = directory ?? string.Empty;
|
||||
var p = parentExportType ?? string.Empty;
|
||||
if (SelectedTab is { Header : "New Tab" })
|
||||
{
|
||||
SelectedTab.Header = h;
|
||||
|
|
@ -411,7 +375,7 @@ public class TabControlViewModel : ViewModel
|
|||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
_tabItems.Add(new TabItem(h, d, p));
|
||||
_tabItems.Add(new TabItem(h, d));
|
||||
SelectedTab = _tabItems.Last();
|
||||
});
|
||||
}
|
||||
|
|
@ -473,6 +437,6 @@ public class TabControlViewModel : ViewModel
|
|||
|
||||
private static IEnumerable<TabItem> EnumerateTabs()
|
||||
{
|
||||
yield return new TabItem("New Tab", string.Empty, string.Empty);
|
||||
yield return new TabItem("New Tab", string.Empty);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,7 @@
|
|||
xmlns:local="clr-namespace:FModel"
|
||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
||||
WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
|
||||
IconVisibility="Collapsed" Width="500" SizeToContent="Height"
|
||||
Loaded="OnLoaded">
|
||||
IconVisibility="Collapsed" Width="500" SizeToContent="Height">
|
||||
<adonisControls:AdonisWindow.Style>
|
||||
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
|
||||
<Setter Property="Title" Value="About" />
|
||||
|
|
@ -25,27 +24,31 @@
|
|||
</StackPanel>
|
||||
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="Description" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
|
||||
<TextBlock Text="History" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
|
||||
<TextBlock Text="				" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding DescriptionLabel}" />
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30"
|
||||
Text="Since the release in March 2019, we've continuously added new features and improved old ones. It started as a simple UE4 file explorer to parse packages and, as things progressed, became really popular to data-mine games and create items icons. FModel 4 is now the most complete and well-made version so far. It features dozens of settings for you to use to make FModel your own." />
|
||||
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="Contributors" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
|
||||
<TextBlock Text="				" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding ContributorsLabel}" />
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30"
|
||||
Text="With the help of Waddlesworth, Fabian, Maiky, GMatrix, Amr, Officer, Tiger, Mang0e, and a lot more, this project continues to exist. If you're making money off of FModel consider donating to sustain FModel's continued improvements." />
|
||||
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="Donators" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
|
||||
<TextBlock Text="				" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding DonatorsLabel}" />
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30"
|
||||
Text="TheGameVlog ♥, Quentin ♥, Maiky ♥, HYPEX ♥, AnimatedAspect, Evan, VenomLeaks, Fortnite.GG, JayKey, Fevers, Netu, Laggy, s0ll, RazTracker, Mikey, kyle, Yanteh, Shiina, SexyNutella, Alexander, Jinx, koba, Tector, imatrix, LamZykoss, Frenzy Leaks, LlamaLeaks, xplore, XTigerHyperX, FunGames, WeLoveFortnite." />
|
||||
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="Powered by" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
|
||||
<TextBlock Text="				" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding ReferencesLabel}" />
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30"
|
||||
Text="CUE4Parse, BenBot, Fortnite-Api, AdonisUI, AvalonEdit, CSCore, NVorbis, VgmStream, RestSharp, Serilog, Discord, K4os.Compression.LZ4, Ookii.Dialogs, Newtonsoft.Json, ..." />
|
||||
</StackPanel>
|
||||
</adonisControls:AdonisWindow>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,9 @@
|
|||
using System.Windows;
|
||||
using FModel.ViewModels;
|
||||
|
||||
namespace FModel.Views;
|
||||
namespace FModel.Views;
|
||||
|
||||
public partial class About
|
||||
{
|
||||
private readonly AboutViewModel _viewModel;
|
||||
|
||||
public About()
|
||||
{
|
||||
DataContext = _viewModel = new AboutViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await _viewModel.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,8 +80,14 @@
|
|||
<Button Grid.Column="1" MinWidth="78" Margin="0 0 12 0" IsDefault="True" IsCancel="False"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK" Click="OnClick" />
|
||||
<Button Grid.Column="2" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="False"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Refresh" Click="OnRefreshAes"
|
||||
Visibility="{Binding Converter={x:Static converters:EndpointToTypeConverter.Instance}, ConverterParameter={x:Static local:EEndpointType.Aes}}">
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Refresh" Click="OnRefreshAes">
|
||||
<Button.Visibility>
|
||||
<!-- if aes custom endpoint is enabled, make this visible -->
|
||||
<MultiBinding Converter="{x:Static converters:EndpointToTypeConverter.Instance}">
|
||||
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:Views.AesManager}}" Path="DataContext" />
|
||||
<Binding Source="{x:Static local:EEndpointType.Aes}" />
|
||||
</MultiBinding>
|
||||
</Button.Visibility>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using System.Windows;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels;
|
||||
|
||||
namespace FModel.Views;
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ public partial class CustomDir
|
|||
{
|
||||
DataContext = customDir;
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
Activate();
|
||||
WpfSuckMyDick.Focus();
|
||||
WpfSuckMyDick.SelectAll();
|
||||
|
|
@ -20,4 +20,4 @@ public partial class CustomDir
|
|||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
<adonisControls:AdonisWindow x:Class="FModel.Views.DirectorySelector"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
||||
|
|
@ -37,51 +36,37 @@
|
|||
<GroupBox Grid.Row="1" adonisExtensions:LayerExtension.Layer="2" Margin="10 10 10 18"
|
||||
Padding="{adonisUi:Space 0}" Background="Transparent">
|
||||
<StackPanel>
|
||||
<Grid x:Name="Hello" Margin="0 5">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<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"
|
||||
ItemsSource="{Binding DetectedDirectories}" Margin="0 0 0 5"
|
||||
Style="{StaticResource UComboBox}"
|
||||
adonisExtensions:WatermarkExtension.Watermark="Search for a game..."
|
||||
VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory, Mode=TwoWay}">
|
||||
<TextBlock Grid.Column="0" Text="Detected Game" VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Column="2" ItemsSource="{Binding AutoDetectedGames}"
|
||||
VerticalAlignment="Center" SelectedItem="{Binding SelectedDetectedGame, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding GameName, Converter={x:Static converters:StringToGameConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</controls:FilterableComboBox>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
<Grid x:Name="Hello" Margin="0 0 0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<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"
|
||||
ItemsSource="{Binding UeGames}"
|
||||
Style="{StaticResource UComboBox}"
|
||||
VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory.UeVersion, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</controls:FilterableComboBox>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Directory" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Row="2" Grid.Column="2" Text="{Binding SelectedDirectory.GameDirectory, Mode=TwoWay}" />
|
||||
<Button Grid.Row="2" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" />
|
||||
<Button Grid.Row="2" Grid.Column="4" Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}" Padding="0"
|
||||
Click="OnDeleteDirectory" Width="{Binding ActualWidth, ElementName=OkGuysButWhoFuckingAsked}" ToolTip="Delete Game"
|
||||
Visibility="{Binding SelectedDirectory.IsManual, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<TextBox Grid.Column="0" Text="{Binding SelectedDetectedGame.GameDirectory, Mode=TwoWay}" />
|
||||
<Button Grid.Column="2" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" />
|
||||
<Button Grid.Column="3" Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}" Padding="0"
|
||||
Click="OnDeleteDirectory" Width="{Binding ActualWidth, ElementName=OkGuysButWhoFuckingAsked}"
|
||||
Visibility="{Binding SelectedDetectedGame.IsManual, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
ToolTip="Delete Game" Margin="5 0 0 0">
|
||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource RemoveIcon}" />
|
||||
|
|
@ -89,9 +74,7 @@
|
|||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Separator Style="{StaticResource CustomSeparator}" Tag="ADD UNDETECTED GAME" />
|
||||
|
||||
<Expander ExpandDirection="Down" IsExpanded="False">
|
||||
<Grid MaxWidth="{Binding ActualWidth, ElementName=Hello}">
|
||||
<Grid.RowDefinitions>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FModel.ViewModels;
|
||||
using FModel.ViewModels;
|
||||
using Ookii.Dialogs.Wpf;
|
||||
using System.Windows;
|
||||
using CUE4Parse.Utils;
|
||||
|
||||
namespace FModel.Views;
|
||||
|
||||
|
|
@ -33,7 +29,7 @@ public partial class DirectorySelector
|
|||
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
|
||||
if (folderBrowser.ShowDialog() == true)
|
||||
{
|
||||
gameLauncherViewModel.AddUndetectedDir(folderBrowser.SelectedPath);
|
||||
gameLauncherViewModel.AddUnknownGame(folderBrowser.SelectedPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,39 +39,17 @@ public partial class DirectorySelector
|
|||
if (folderBrowser.ShowDialog() == true)
|
||||
{
|
||||
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('\\');
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddDirectory(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not GameSelectorViewModel gameLauncherViewModel ||
|
||||
if (DataContext is not GameSelectorViewModel gameLauncherViewModel||
|
||||
string.IsNullOrEmpty(HelloMyNameIsGame.Text) ||
|
||||
string.IsNullOrEmpty(HelloGameMyNameIsDirectory.Text))
|
||||
return;
|
||||
|
||||
gameLauncherViewModel.AddUndetectedDir(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text);
|
||||
gameLauncherViewModel.AddUnknownGame(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text);
|
||||
HelloMyNameIsGame.Clear();
|
||||
HelloGameMyNameIsDirectory.Clear();
|
||||
}
|
||||
|
|
@ -87,4 +61,4 @@ public partial class DirectorySelector
|
|||
|
||||
gameLauncherViewModel.DeleteSelectedGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -142,7 +142,7 @@ public partial class ImageMerger
|
|||
var fileBrowser = new OpenFileDialog
|
||||
{
|
||||
Title = "Add image(s)",
|
||||
InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"),
|
||||
InitialDirectory = $"{UserSettings.Default.OutputDirectory}\\Exports",
|
||||
Multiselect = true,
|
||||
Filter = "Image Files (*.png,*.bmp,*.jpg,*.jpeg,*.jfif,*.jpe,*.tiff,*.tif)|*.png;*.bmp;*.jpg;*.jpeg;*.jfif;*.jpe;*.tiff;*.tif|All Files (*.*)|*.*"
|
||||
};
|
||||
|
|
|
|||
126
FModel/Views/MapViewer.xaml
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
<adonisControls:AdonisWindow x:Class="FModel.Views.MapViewer"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:FModel"
|
||||
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
|
||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
||||
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" SizeToContent="Width" ResizeMode="CanMinimize" Closing="OnClosing"
|
||||
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.75'}"
|
||||
MinWidth="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.50'}">
|
||||
<adonisControls:AdonisWindow.Style>
|
||||
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
|
||||
<Setter Property="Title" Value="Map Viewer" />
|
||||
</Style>
|
||||
</adonisControls:AdonisWindow.Style>
|
||||
<adonisControls:AdonisWindow.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Resources/Resources.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<controls:OnTagDataTemplateSelector x:Key="TagTemplateSelector" />
|
||||
<DataTemplate x:Key="BrTemplate">
|
||||
<StackPanel VerticalAlignment="Center" Margin="25 0">
|
||||
<CheckBox Content="Points Of Interest" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrPois}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
|
||||
<CheckBox Content="Landmarks" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrLandmarks}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
|
||||
<!-- <CheckBox Content="Tags Location" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrTagsLocation}" -->
|
||||
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
|
||||
<!-- <CheckBox Content="Patrols Path" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrPatrolsPath}" -->
|
||||
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
|
||||
<!-- <CheckBox Content="Upgrade Benches" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrUpgradeBenches}" -->
|
||||
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
|
||||
<!-- <CheckBox Content="Phonebooths" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrPhonebooths}" -->
|
||||
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
|
||||
<!-- <CheckBox Content="Weapon-o-matic/Mending Machines" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrVendingMachines}" -->
|
||||
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
|
||||
<!-- <CheckBox Content="Bounty Boards" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrBountyBoards}" -->
|
||||
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="PrTemplate">
|
||||
<StackPanel VerticalAlignment="Center" Margin="25 0">
|
||||
<CheckBox Content="Landmarks" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrLandmarks}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
|
||||
<CheckBox Content="Cannonball" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrCannonball}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
|
||||
<CheckBox Content="Skydive" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrSkydive}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
|
||||
<CheckBox Content="Shooting Targets" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrShootingTargets}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
|
||||
<CheckBox Content="Parkour" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrParkour}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
|
||||
<CheckBox Content="Time Trials" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrTimeTrials}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
|
||||
<CheckBox Content="Vending Machines" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrVendingMachines}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
|
||||
<!-- <CheckBox Content="Music Blocks" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrMusicBlocks}" -->
|
||||
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</adonisControls:AdonisWindow.Resources>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" MinWidth="300" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TreeView Grid.Row="0" Grid.RowSpan="3" x:Name="MapTree" SelectedItemChanged="OnSelectedItemChanged"
|
||||
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}">
|
||||
<TreeViewItem Tag="BrTemplate" IsSelected="True">
|
||||
<TreeViewItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center" Margin="-20 4 7.5 4">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GliderIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
<TextBlock Text="Battle Royale" HorizontalAlignment="Left" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</TreeViewItem.Header>
|
||||
</TreeViewItem>
|
||||
<TreeViewItem Tag="PrTemplate">
|
||||
<TreeViewItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center" Margin="-20 4 7.5 4">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource AnchorIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
<TextBlock Text="Party Royale" HorizontalAlignment="Left" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</TreeViewItem.Header>
|
||||
</TreeViewItem>
|
||||
</TreeView>
|
||||
|
||||
<Grid Grid.Row="1" HorizontalAlignment="Stretch">
|
||||
<ContentControl ContentTemplateSelector="{StaticResource TagTemplateSelector}" Content="{Binding SelectedItem.Tag, ElementName=MapTree}" />
|
||||
</Grid>
|
||||
|
||||
<Button Grid.Row="2" Content="Save Image" Margin="5" IsEnabled="{Binding Status.IsReady}" VerticalAlignment="Bottom" Click="OnClick" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="1" HorizontalAlignment="Stretch">
|
||||
<controls:MagnifierManager.Magnifier>
|
||||
<controls:Magnifier Radius="200" ZoomFactor=".4" BorderBrush="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}" BorderThickness="1" />
|
||||
</controls:MagnifierManager.Magnifier>
|
||||
|
||||
<TextBlock Text="Minimap is loading, please wait..." HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<Image UseLayoutRounding="True" Source="{Binding MapViewer.MapImage}" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Visibility="{Binding Status.IsReady, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<Image UseLayoutRounding="True" Source="{Binding MapViewer.LayerImage}" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Visibility="{Binding Status.IsReady, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</adonisControls:AdonisWindow>
|
||||
78
FModel/Views/MapViewer.xaml.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Imaging;
|
||||
using FModel.Extensions;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels;
|
||||
using FModel.Views.Resources.Controls;
|
||||
using Microsoft.Win32;
|
||||
using Serilog;
|
||||
|
||||
namespace FModel.Views;
|
||||
|
||||
public partial class MapViewer
|
||||
{
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
||||
public MapViewer()
|
||||
{
|
||||
DataContext = _applicationView;
|
||||
_applicationView.MapViewer.Initialize();
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, CancelEventArgs e) => DiscordService.DiscordHandler.UpdateToSavedPresence();
|
||||
|
||||
private void OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_applicationView.MapViewer.MapImage == null) return;
|
||||
var path = Path.Combine(UserSettings.Default.TextureDirectory, "MiniMap.png");
|
||||
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Save MiniMap",
|
||||
FileName = "MiniMap.png",
|
||||
InitialDirectory = path.SubstringBeforeLast('\\'),
|
||||
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
|
||||
};
|
||||
|
||||
if (!saveFileDialog.ShowDialog().GetValueOrDefault()) return;
|
||||
path = saveFileDialog.FileName;
|
||||
|
||||
using var fileStream = new FileStream(path, FileMode.Create);
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(_applicationView.MapViewer.GetImageToSave()));
|
||||
encoder.Save(fileStream);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Log.Information("MiniMap.png successfully saved");
|
||||
FLogger.Append(ELog.Information, () => FLogger.Text("Successfully saved 'MiniMap.png'", Constants.WHITE, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("MiniMap.png could not be saved");
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text("Could not save 'MiniMap.png'", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var item in MapTree.Items)
|
||||
{
|
||||
if (item is not TreeViewItem { IsSelected: true })
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
_applicationView.MapViewer.MapIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using FModel.Extensions;
|
||||
using ICSharpCode.AvalonEdit.Rendering;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
|
@ -30,10 +29,8 @@ public class GamePathElementGenerator : VisualLineElementGenerator
|
|||
public override VisualLineElement ConstructElement(int offset)
|
||||
{
|
||||
var m = FindMatch(offset);
|
||||
if (!m.Success || m.Index != 0 ||
|
||||
!m.Groups.TryGetValue("target", out var g)) return null;
|
||||
if (!m.Success || m.Index != 0) return null;
|
||||
|
||||
var parentExportType = CurrentContext.Document.GetParentExportType(offset);
|
||||
return new GamePathVisualLineText(g.Value, parentExportType, CurrentContext.VisualLine, g.Length + g.Index + 1);
|
||||
return m.Groups.TryGetValue("target", out var g) ? new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,16 +16,14 @@ public class GamePathVisualLineText : VisualLineText
|
|||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
||||
public delegate void GamePathOnClick(string gamePath, string parentExportType);
|
||||
public delegate void GamePathOnClick(string gamePath);
|
||||
|
||||
public event GamePathOnClick OnGamePathClicked;
|
||||
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;
|
||||
_parentExportType = parentExportType;
|
||||
}
|
||||
|
||||
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
|
||||
|
|
@ -58,14 +56,14 @@ public class GamePathVisualLineText : VisualLineText
|
|||
if (e.Handled || OnGamePathClicked == null)
|
||||
return;
|
||||
|
||||
OnGamePathClicked(_gamePath, _parentExportType);
|
||||
OnGamePathClicked(_gamePath);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override VisualLineText CreateInstance(int length)
|
||||
{
|
||||
var a = new GamePathVisualLineText(_gamePath, _parentExportType, ParentVisualLine, length);
|
||||
a.OnGamePathClicked += async (gamePath, parentExportType) =>
|
||||
var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length);
|
||||
a.OnGamePathClicked += async gamePath =>
|
||||
{
|
||||
var obj = gamePath.SubstringAfterLast('.');
|
||||
var package = gamePath.SubstringBeforeLast('.');
|
||||
|
|
@ -82,17 +80,17 @@ public class GamePathVisualLineText : VisualLineText
|
|||
}
|
||||
else
|
||||
{
|
||||
lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumber(obj);
|
||||
lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj);
|
||||
line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
|
||||
}
|
||||
|
||||
|
||||
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
|
||||
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
_applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType));
|
||||
_applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj));
|
||||
}
|
||||
};
|
||||
return a;
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ public partial class AvalonEditor
|
|||
|
||||
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);
|
||||
avalonEditor.Select(line.Offset, line.Length);
|
||||
avalonEditor.ScrollToLine(lineNumber);
|
||||
|
|
@ -223,9 +223,10 @@ public partial class AvalonEditor
|
|||
|
||||
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;
|
||||
|
||||
var fileName = e.TabToRemove.Document.FileName;
|
||||
if (_savedCarets.ContainsKey(fileName))
|
||||
_savedCarets.Remove(fileName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
using System.Windows.Controls;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
public partial class CommitControl : UserControl
|
||||
{
|
||||
public CommitControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||