mirror of
https://github.com/4sval/FModel.git
synced 2026-03-21 17:24:26 -05:00
commit
bcbda59523
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
|
|
@ -21,16 +21,16 @@ jobs:
|
|||
- name: Fetch Submodules Recursively
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: .NET 6 Setup
|
||||
- name: .NET 8 Setup
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: .NET Restore
|
||||
run: dotnet restore FModel
|
||||
|
||||
- name: .NET Publish
|
||||
run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net6.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }}
|
||||
run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net8.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }}
|
||||
|
||||
- name: ZIP File
|
||||
uses: papeloto/action-zip@v1
|
||||
|
|
|
|||
65
.github/workflows/qa.yml
vendored
Normal file
65
.github/workflows/qa.yml
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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
3
.gitmodules
vendored
|
|
@ -1,6 +1,3 @@
|
|||
[submodule "CUE4Parse"]
|
||||
path = CUE4Parse
|
||||
url = https://github.com/FabianFG/CUE4Parse
|
||||
[submodule "EpicManifestParser"]
|
||||
path = EpicManifestParser
|
||||
url = https://github.com/FModel/EpicManifestParser
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit d816fe61ac8e5798d1584ea2f9871acfca0ca429
|
||||
Subproject commit 4e955153559be8dc156d15fc93ff8c1016d3ebfe
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 21df8a55d474f14148a35bc943e06f3fdc20c997
|
||||
|
|
@ -41,8 +41,6 @@ public partial class App
|
|||
{
|
||||
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
|
||||
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
|
||||
|
||||
/*if (UserSettings.Default.ShowChangelog) */MigrateV1Games();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -52,7 +50,14 @@ public partial class App
|
|||
var createMe = false;
|
||||
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
|
||||
{
|
||||
UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
|
||||
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");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
|
||||
|
|
@ -143,17 +148,6 @@ public partial class App
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void MigrateV1Games()
|
||||
{
|
||||
foreach ((var gameDir, var setting) in UserSettings.Default.ManualGames)
|
||||
{
|
||||
if (!Directory.Exists(gameDir)) continue;
|
||||
UserSettings.Default.PerDirectory[gameDir] =
|
||||
DirectorySettings.Default(setting.GameName, setting.GameDirectory, true, setting.OverridedGame, setting.AesKeys?.MainKey);
|
||||
}
|
||||
UserSettings.Default.ManualGames.Clear();
|
||||
}
|
||||
|
||||
private string GetOperatingSystemProductName()
|
||||
{
|
||||
var productName = string.Empty;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class BaseBundle : UCreator
|
|||
{
|
||||
_quests = new List<BaseQuest>();
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName"))
|
||||
DisplayName = displayName.Text.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
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;
|
||||
|
|
@ -32,10 +34,27 @@ public class BaseCommunity : BaseIcon
|
|||
{
|
||||
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
|
||||
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export))
|
||||
_rarityName = export.Name;
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
_rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription();
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||
CheckGameplayTags(gameplayTags);
|
||||
|
|
|
|||
|
|
@ -1,298 +1,317 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 FInstancedStruct[] dataList, "DataList")) GetSeries(dataList);
|
||||
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer")) GetSeries(componentContainer);
|
||||
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", "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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@ public class BaseItemAccessToken : UCreator
|
|||
_icon.ParseForReward(false);
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName") && displayName.Text != "TBD")
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName") && displayName.Text != "TBD")
|
||||
DisplayName = displayName.Text;
|
||||
else
|
||||
DisplayName = _icon?.DisplayName;
|
||||
|
||||
Description = Object.TryGetValue(out FText description, "Description") ? description.Text : _icon?.Description;
|
||||
Description = Object.TryGetValue(out FText description, "Description", "ItemDescription") ? description.Text : _icon?.Description;
|
||||
if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text;
|
||||
}
|
||||
|
||||
|
|
|
|||
49
FModel/Creator/Bases/FN/BaseJuno.cs
Normal file
49
FModel/Creator/Bases/FN/BaseJuno.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
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();
|
||||
}
|
||||
|
|
@ -17,10 +17,9 @@ public class BaseMtxOffer : UCreator
|
|||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FStructFallback typeImage, "DetailsImage", "TileImage") &&
|
||||
typeImage.TryGetValue(out FPackageIndex resource, "ResourceObject"))
|
||||
if (Object.TryGetValue(out FSoftObjectPath image, "SoftDetailsImage", "SoftTileImage"))
|
||||
{
|
||||
Preview = Utils.GetBitmap(resource);
|
||||
Preview = Utils.GetBitmap(image);
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback gradient, "Gradient") &&
|
||||
|
|
@ -81,4 +80,4 @@ public class BaseMtxOffer : UCreator
|
|||
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
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;
|
||||
|
|
@ -14,13 +16,16 @@ public class BaseOfferDisplayData : UCreator
|
|||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (!Object.TryGetValue(out UMaterialInterface[] presentations, "Presentations"))
|
||||
if (!Object.TryGetValue(out FStructFallback[] contextualPresentations, "ContextualPresentations"))
|
||||
return;
|
||||
|
||||
_offerImages = new BaseMaterialInstance[presentations.Length];
|
||||
_offerImages = new BaseMaterialInstance[contextualPresentations.Length];
|
||||
for (var i = 0; i < _offerImages.Length; i++)
|
||||
{
|
||||
var offerImage = new BaseMaterialInstance(presentations[i], Style);
|
||||
if (!contextualPresentations[i].TryGetValue(out FSoftObjectPath material, "Material") ||
|
||||
!material.TryLoad(out UMaterialInterface presentation)) continue;
|
||||
|
||||
var offerImage = new BaseMaterialInstance(presentation, Style);
|
||||
offerImage.ParseForInfo();
|
||||
_offerImages[i] = offerImage;
|
||||
}
|
||||
|
|
@ -36,4 +41,4 @@ public class BaseOfferDisplayData : UCreator
|
|||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,13 +69,19 @@ public class BaseQuest : BaseIcon
|
|||
}
|
||||
else
|
||||
{
|
||||
Description = ShortDescription;
|
||||
if (Object.TryGetValue(out FText completionText, "CompletionText"))
|
||||
Description += "\n" + completionText.Text;
|
||||
if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
|
||||
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") &&
|
||||
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
|
||||
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") &&
|
||||
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out UObject iconObject))
|
||||
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out iconObject)))
|
||||
{
|
||||
Preview = iconObject switch
|
||||
{
|
||||
|
|
@ -127,9 +133,16 @@ public class BaseQuest : BaseIcon
|
|||
}
|
||||
}
|
||||
|
||||
if (_reward == null && Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
|
||||
if (_reward == null)
|
||||
{
|
||||
if (rewardsTable.TryGetDataTableRow("Default", StringComparison.InvariantCulture, out var row))
|
||||
FName rowName = null;
|
||||
if (Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
|
||||
rowName = new FName("Default");
|
||||
else if (Object.TryGetValue(out FStructFallback[] rewardTableRows, "IndividualRewardTableRows") &&
|
||||
rewardTableRows.Length > 0 && rewardTableRows[0].TryGetValue(out rowName, "RowName") &&
|
||||
rewardTableRows[0].TryGetValue(out rewardsTable, "DataTable")) {}
|
||||
|
||||
if (rewardsTable != null && rowName != null && rewardsTable.TryGetDataTableRow(rowName.Text, StringComparison.InvariantCulture, out var row))
|
||||
{
|
||||
if (row.TryGetValue(out FName templateId, "TemplateId") &&
|
||||
row.TryGetValue(out int quantity, "Quantity"))
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public class BaseSeason : UCreator
|
|||
{
|
||||
_bookXpSchedule = Array.Empty<Page>();
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName"))
|
||||
DisplayName = displayName.Text.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public class Reward
|
|||
_rewardPaint.TextSize = 50;
|
||||
if (HasReward())
|
||||
{
|
||||
c.DrawBitmap(_theReward.Preview.Resize((int) rect.Height), new SKPoint(rect.Left, rect.Top), _rewardPaint);
|
||||
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).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;
|
||||
|
|
@ -88,7 +88,7 @@ public class Reward
|
|||
public void DrawSeasonWin(SKCanvas c, int size)
|
||||
{
|
||||
if (!HasReward()) return;
|
||||
c.DrawBitmap(_theReward.Preview.Resize(size), new SKPoint(0, 0), _rewardPaint);
|
||||
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).Resize(size), new SKPoint(0, 0), _rewardPaint);
|
||||
}
|
||||
|
||||
public void DrawSeason(SKCanvas c, int x, int y, int areaSize)
|
||||
|
|
@ -115,33 +115,33 @@ 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/.*?/{trigger}.*"); // path has no objectname and its needed so we push the trigger again as the objectname
|
||||
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
|
||||
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 = $"{_theReward.DisplayName} ({_rewardQuantity})";
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,218 +1,243 @@
|
|||
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;
|
||||
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 "JunoKnowledgeBundle":
|
||||
case "FortBannerTokenType":
|
||||
case "FortVariantTokenType":
|
||||
case "FortDecoItemDefinition":
|
||||
case "FortStatItemDefinition":
|
||||
case "FortAmmoItemDefinition":
|
||||
case "FortEmoteItemDefinition":
|
||||
case "FortBadgeItemDefinition":
|
||||
case "SparksMicItemDefinition":
|
||||
case "FortAwardItemDefinition":
|
||||
case "SparksAuraItemDefinition":
|
||||
case "SparksDrumItemDefinition":
|
||||
case "SparksBassItemDefinition":
|
||||
case "FortGadgetItemDefinition":
|
||||
case "AthenaCharmItemDefinition":
|
||||
case "FortPlaysetItemDefinition":
|
||||
case "FortGiftBoxItemDefinition":
|
||||
case "FortOutpostItemDefinition":
|
||||
case "FortVehicleItemDefinition":
|
||||
case "SparksGuitarItemDefinition":
|
||||
case "FortCardPackItemDefinition":
|
||||
case "FortDefenderItemDefinition":
|
||||
case "FortCurrencyItemDefinition":
|
||||
case "FortResourceItemDefinition":
|
||||
case "FortBackpackItemDefinition":
|
||||
case "FortEventQuestMapDataAsset":
|
||||
case "FortWeaponModItemDefinition":
|
||||
case "FortCodeTokenItemDefinition":
|
||||
case "FortSchematicItemDefinition":
|
||||
case "SparksKeyboardItemDefinition":
|
||||
case "FortWorldMultiItemDefinition":
|
||||
case "FortAlterationItemDefinition":
|
||||
case "FortExpeditionItemDefinition":
|
||||
case "FortIngredientItemDefinition":
|
||||
case "StWFortAccoladeItemDefinition":
|
||||
case "FortAccountBuffItemDefinition":
|
||||
case "FortWeaponMeleeItemDefinition":
|
||||
case "FortPlayerPerksItemDefinition":
|
||||
case "FortPlaysetPropItemDefinition":
|
||||
case "JunoRecipeBundleItemDefinition":
|
||||
case "FortHomebaseNodeItemDefinition":
|
||||
case "FortNeverPersistItemDefinition":
|
||||
case "FortPlayerAugmentItemDefinition":
|
||||
case "FortSmartBuildingItemDefinition":
|
||||
case "FortWeaponModItemDefinitionOptic":
|
||||
case "RadioContentSourceItemDefinition":
|
||||
case "FortPlaysetGrenadeItemDefinition":
|
||||
case "JunoWeaponCreatureItemDefinition":
|
||||
case "FortPersonalVehicleItemDefinition":
|
||||
case "FortGameplayModifierItemDefinition":
|
||||
case "FortHardcoreModifierItemDefinition":
|
||||
case "FortWeaponModItemDefinitionMagazine":
|
||||
case "FortConsumableAccountItemDefinition":
|
||||
case "FortConversionControlItemDefinition":
|
||||
case "FortAccountBuffCreditItemDefinition":
|
||||
case "JunoBuildInstructionsItemDefinition":
|
||||
case "JunoBuildingSetAccountItemDefinition":
|
||||
case "FortEventCurrencyItemDefinitionRedir":
|
||||
case "FortPersistentResourceItemDefinition":
|
||||
case "FortWeaponMeleeOffhandItemDefinition":
|
||||
case "FortHomebaseBannerIconItemDefinition":
|
||||
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 "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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,8 +54,6 @@ public enum EDiscordRpc
|
|||
|
||||
public enum ELoadingMode
|
||||
{
|
||||
[Description("Single")]
|
||||
Single,
|
||||
[Description("Multiple")]
|
||||
Multiple,
|
||||
[Description("All")]
|
||||
|
|
@ -71,7 +69,9 @@ public enum EUpdateMode
|
|||
[Description("Stable")]
|
||||
Stable,
|
||||
[Description("Beta")]
|
||||
Beta
|
||||
Beta,
|
||||
[Description("QA Testing")]
|
||||
Qa
|
||||
}
|
||||
|
||||
public enum ECompressedAudio
|
||||
|
|
|
|||
|
|
@ -11,8 +11,21 @@ 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);
|
||||
return attributes.Length > 0 ? attributes[0].Description : $"{value} ({value:D})";
|
||||
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})";
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
|
@ -43,4 +56,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,6 +2,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
|
||||
namespace FModel.Extensions;
|
||||
|
||||
|
|
@ -94,7 +95,7 @@ public static class StringExtensions
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetLineNumber(this string s, string lineToFind)
|
||||
public static int GetNameLineNumber(this string s, string lineToFind)
|
||||
{
|
||||
if (int.TryParse(lineToFind, out var index))
|
||||
return s.GetLineNumber(index);
|
||||
|
|
@ -113,6 +114,25 @@ 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>net6.0-windows</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon>FModel.ico</ApplicationIcon>
|
||||
<Version>4.4.3.4</Version>
|
||||
<AssemblyVersion>4.4.3.4</AssemblyVersion>
|
||||
<FileVersion>4.4.3.4</FileVersion>
|
||||
<Version>4.4.3.6</Version>
|
||||
<AssemblyVersion>4.4.3.6</AssemblyVersion>
|
||||
<FileVersion>4.4.3.6</FileVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsPublishable>true</IsPublishable>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
|
@ -45,6 +45,12 @@
|
|||
<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" />
|
||||
|
|
@ -110,6 +116,7 @@
|
|||
<None Remove="Resources\light.vert" />
|
||||
<None Remove="Resources\bone.frag" />
|
||||
<None Remove="Resources\bone.vert" />
|
||||
<None Remove="Resources\collision.vert" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -135,34 +142,34 @@
|
|||
<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.8.3" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.8.5" />
|
||||
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
|
||||
<PackageReference Include="CSCore" Version="1.2.1.2" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageReference Include="ImGui.NET" Version="1.89.7.1" />
|
||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.5" />
|
||||
<PackageReference Include="EpicManifestParser" Version="2.2.1" />
|
||||
<PackageReference Include="ImGui.NET" Version="1.90.1.1" />
|
||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<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.0" />
|
||||
<PackageReference Include="OpenTK" Version="4.8.2" />
|
||||
<PackageReference Include="RestSharp" Version="110.2.0" />
|
||||
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.8" />
|
||||
<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>
|
||||
|
|
@ -182,6 +189,12 @@
|
|||
<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" />
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ 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
|
||||
|
|
@ -29,10 +27,6 @@ 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
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ public class FStatus : ViewModel
|
|||
UpdateStatusLabel(label);
|
||||
}
|
||||
|
||||
public void UpdateStatusLabel(string label)
|
||||
public void UpdateStatusLabel(string label, string prefix = null)
|
||||
{
|
||||
Label = Kind == EStatusKind.Loading ? $"{Kind} {label}".Trim() : Kind.ToString();
|
||||
Label = Kind == EStatusKind.Loading ? $"{prefix ?? Kind.ToString()} {label}".Trim() : Kind.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
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;
|
||||
|
|
@ -34,7 +37,6 @@ public class ImGuiController : IDisposable
|
|||
|
||||
private int _windowWidth;
|
||||
private int _windowHeight;
|
||||
// private string _iniPath;
|
||||
|
||||
public ImFontPtr FontNormal;
|
||||
public ImFontPtr FontBold;
|
||||
|
|
@ -49,7 +51,6 @@ 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);
|
||||
|
|
@ -58,9 +59,13 @@ 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);
|
||||
|
|
@ -71,7 +76,6 @@ public class ImGuiController : IDisposable
|
|||
io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines;
|
||||
|
||||
CreateDeviceResources();
|
||||
SetKeyMappings();
|
||||
|
||||
SetPerFrameImGuiData(1f / 60f);
|
||||
|
||||
|
|
@ -271,8 +275,8 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
|||
|
||||
foreach (Keys key in Enum.GetValues(typeof(Keys)))
|
||||
{
|
||||
if (key == Keys.Unknown || io.KeyMap[(int) key] == -1) continue;
|
||||
io.AddKeyEvent((ImGuiKey) io.KeyMap[(int) key], kState.IsKeyDown(key));
|
||||
if (key == Keys.Unknown) continue;
|
||||
io.AddKeyEvent(TranslateKey(key), kState.IsKeyDown(key));
|
||||
}
|
||||
|
||||
foreach (var c in PressedChars)
|
||||
|
|
@ -292,115 +296,6 @@ 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)
|
||||
|
|
@ -440,7 +335,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.CmdListsRange[i];
|
||||
ImDrawListPtr cmd_list = draw_data.CmdLists[i];
|
||||
|
||||
int vertexSize = cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>();
|
||||
if (vertexSize > _vertexBufferSize)
|
||||
|
|
@ -490,7 +385,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
|||
// Render command lists
|
||||
for (int n = 0; n < draw_data.CmdListsCount; n++)
|
||||
{
|
||||
ImDrawListPtr cmd_list = draw_data.CmdListsRange[n];
|
||||
ImDrawListPtr cmd_list = draw_data.CmdLists[n];
|
||||
|
||||
GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>(), cmd_list.VtxBuffer.Data);
|
||||
CheckGLError($"Data Vert {n}");
|
||||
|
|
@ -643,4 +538,71 @@ 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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,32 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
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;
|
||||
|
||||
if (key.StartsWith("0x"))
|
||||
key = key[2..];
|
||||
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
|
||||
|
||||
return "0x" + key.ToUpper().Trim();
|
||||
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);
|
||||
}
|
||||
|
||||
public static void OpenWindow<T>(string windowName, Action action) where T : Window
|
||||
|
|
@ -74,9 +76,9 @@ public static class Helper
|
|||
|
||||
public static bool IsNaN(double value)
|
||||
{
|
||||
var t = new NanUnion { DoubleValue = value };
|
||||
var exp = t.UlongValue & 0xfff0000000000000;
|
||||
var man = t.UlongValue & 0x000fffffffffffff;
|
||||
var ulongValue = Unsafe.As<double, ulong>(ref value);
|
||||
var exp = ulongValue & 0xfff0000000000000;
|
||||
var man = ulongValue & 0x000fffffffffffff;
|
||||
return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0;
|
||||
}
|
||||
|
||||
|
|
@ -96,13 +98,17 @@ public static class Helper
|
|||
return -d < n && d > n;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float DegreesToRadians(float degrees)
|
||||
{
|
||||
return MathF.PI / 180f * degrees;
|
||||
const float ratio = MathF.PI / 180f;
|
||||
return ratio * degrees;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float RadiansToDegrees(float radians)
|
||||
{
|
||||
return radians* 180f / MathF.PI;
|
||||
const float ratio = 180f / MathF.PI;
|
||||
return radians * ratio;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,16 +321,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">
|
||||
|
|
@ -358,7 +358,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Folder's Packages Models (.psk)" Click="OnFolderModelClick">
|
||||
<MenuItem Header="Save Folder's Packages Models" Click="OnFolderModelClick">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -367,7 +367,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Folder's Packages Animations (.psa)" Click="OnFolderAnimationClick">
|
||||
<MenuItem Header="Save Folder's Packages Animations" Click="OnFolderAnimationClick">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -532,7 +532,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Model (.psk)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem Header="Save Model" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Models" />
|
||||
|
|
@ -547,7 +547,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Animation (.psa)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem Header="Save Animation" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Animations" />
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public partial class MainWindow
|
|||
{
|
||||
var newOrUpdated = UserSettings.Default.ShowChangelog;
|
||||
#if !DEBUG
|
||||
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
|
||||
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode, true);
|
||||
#endif
|
||||
|
||||
switch (UserSettings.Default.AesReload)
|
||||
|
|
@ -61,6 +61,8 @@ public partial class MainWindow
|
|||
break;
|
||||
}
|
||||
|
||||
await ApplicationViewModel.InitOodle();
|
||||
await ApplicationViewModel.InitZlib();
|
||||
await _applicationView.CUE4Parse.Initialize();
|
||||
await _applicationView.AesManager.InitAes();
|
||||
await _applicationView.UpdateProvider(true);
|
||||
|
|
@ -69,12 +71,10 @@ public partial class MainWindow
|
|||
#endif
|
||||
await Task.WhenAll(
|
||||
_applicationView.CUE4Parse.VerifyConsoleVariables(),
|
||||
_applicationView.CUE4Parse.VerifyVirtualCache(),
|
||||
_applicationView.CUE4Parse.VerifyContentBuildManifest(),
|
||||
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
|
||||
_applicationView.CUE4Parse.InitMappings(),
|
||||
_applicationView.InitImGuiSettings(newOrUpdated),
|
||||
_applicationView.InitVgmStream(),
|
||||
_applicationView.InitOodle(),
|
||||
ApplicationViewModel.InitVgmStream(),
|
||||
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
|
||||
|
|
@ -83,12 +83,12 @@ public partial class MainWindow
|
|||
).ConfigureAwait(false);
|
||||
|
||||
#if DEBUG
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
"fortnitegame/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01.uasset"));
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
"fortnitegame/Content/Animation/Game/MainPlayer/Emotes/Cowbell/Cowbell_CMM_Loop_M.uasset"));
|
||||
// await _threadWorkerView.Begin(cancellationToken =>
|
||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
// "fortnitegame/Content/Characters/Player/Female/Large/Bodies/F_LRG_BunnyBR/Meshes/F_LRG_BunnyBR.uasset"));
|
||||
// await _threadWorkerView.Begin(cancellationToken =>
|
||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
// "FortniteGame/Content/Environments/Helios/Props/GlacierHotel/GlacierHotel_Globe_A/Meshes/SM_GlacierHotel_Globe_A.uasset"));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
20
FModel/Resources/collision.vert
Normal file
20
FModel/Resources/collision.vert
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#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);
|
||||
}
|
||||
BIN
FModel/Resources/cube.png
Normal file
BIN
FModel/Resources/cube.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 831 B |
BIN
FModel/Resources/cube_off.png
Normal file
BIN
FModel/Resources/cube_off.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -8,7 +8,7 @@ in vec3 fPos;
|
|||
in vec3 fNormal;
|
||||
in vec3 fTangent;
|
||||
in vec2 fTexCoords;
|
||||
in float fTexLayer;
|
||||
flat in int fTexLayer;
|
||||
in vec4 fColor;
|
||||
|
||||
struct Texture
|
||||
|
|
@ -89,6 +89,7 @@ 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];
|
||||
|
|
@ -98,7 +99,7 @@ out vec4 FragColor;
|
|||
|
||||
int LayerToIndex()
|
||||
{
|
||||
return clamp(int(fTexLayer), 0, uUvCount - 1);
|
||||
return clamp(fTexLayer, 0, uUvCount - 1);
|
||||
}
|
||||
|
||||
vec4 SamplerToVector(sampler2D s, vec2 coords)
|
||||
|
|
@ -148,6 +149,11 @@ 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);
|
||||
|
|
@ -165,7 +171,7 @@ vec3 CalcLight(int layer, vec3 normals, vec3 position, vec3 color, float attenua
|
|||
|
||||
vec3 kS = f;
|
||||
vec3 kD = 1.0 - kS;
|
||||
kD *= 1.0 - specular_masks.g;
|
||||
kD *= 1.0 - cavity;
|
||||
|
||||
vec3 specBrdfNom = ggxDistribution(roughness, nDotH) * geomSmith(roughness, nDotL) * geomSmith(roughness, nDotV) * f;
|
||||
float specBrdfDenom = 4.0 * nDotV * nDotL + 0.0001;
|
||||
|
|
@ -212,30 +218,32 @@ 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(uSectionColor, 1.0);
|
||||
FragColor = vec4(diffuseFactor * uSectionColor, uOpacity);
|
||||
}
|
||||
else if (bVertexColors[2] && uHasVertexColors)
|
||||
{
|
||||
FragColor = fColor;
|
||||
FragColor = vec4(diffuseFactor * fColor.rgb, fColor.a);
|
||||
}
|
||||
else if (bVertexColors[3])
|
||||
{
|
||||
int layer = LayerToIndex();
|
||||
vec3 normals = ComputeNormals(layer);
|
||||
FragColor = vec4(normals, 1.0);
|
||||
FragColor = vec4(normals, uOpacity);
|
||||
}
|
||||
else if (bVertexColors[4])
|
||||
{
|
||||
FragColor = SamplerToVector(uParameters.Diffuse[0].Sampler);
|
||||
vec4 diffuse = SamplerToVector(uParameters.Diffuse[0].Sampler);
|
||||
FragColor = vec4(diffuseFactor * diffuse.rgb, diffuse.a);
|
||||
}
|
||||
else
|
||||
{
|
||||
int layer = LayerToIndex();
|
||||
vec3 normals = ComputeNormals(layer);
|
||||
vec4 diffuse = SamplerToVector(uParameters.Diffuse[layer].Sampler);
|
||||
vec3 result = uParameters.Diffuse[layer].Color.rgb * diffuse.rgb;
|
||||
vec3 result = diffuseFactor * diffuse.rgb * uParameters.Diffuse[layer].Color.rgb;
|
||||
|
||||
if (uParameters.HasAo)
|
||||
{
|
||||
|
|
@ -245,7 +253,7 @@ void main()
|
|||
vec3 color = uParameters.Ao.ColorBoost.Color * uParameters.Ao.ColorBoost.Exponent;
|
||||
result = mix(result, result * color, m.b);
|
||||
}
|
||||
result = vec3(uParameters.Ao.AmbientOcclusion) * result * m.r;
|
||||
result *= m.r;
|
||||
}
|
||||
|
||||
vec2 coords = fTexCoords;
|
||||
|
|
@ -263,7 +271,7 @@ void main()
|
|||
}
|
||||
|
||||
{
|
||||
result += CalcLight(layer, normals, uViewPos, vec3(0.75), 1.0, false);
|
||||
result += CalcLight(layer, normals, uViewPos, vec3(1.0), 1.0, false);
|
||||
|
||||
vec3 lights = vec3(uNumLights > 0 ? 0 : 1);
|
||||
for (int i = 0; i < uNumLights; i++)
|
||||
|
|
@ -281,6 +289,6 @@ void main()
|
|||
}
|
||||
|
||||
result = result / (result + vec3(1.0));
|
||||
FragColor = vec4(pow(result, vec3(1.0 / 2.2)), 1.0);
|
||||
FragColor = vec4(pow(result, vec3(1.0 / 2.2)), uOpacity);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 float vTexLayer;
|
||||
layout (location = 6) in vec4 vColor;
|
||||
layout (location = 7) in vec4 vBoneIds;
|
||||
layout (location = 8) in vec4 vBoneWeights;
|
||||
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 = 9) in mat4 vInstanceMatrix;
|
||||
layout (location = 13) in vec3 vMorphTargetPos;
|
||||
layout (location = 14) in vec3 vMorphTargetTangent;
|
||||
|
|
@ -30,9 +30,23 @@ out vec3 fPos;
|
|||
out vec3 fNormal;
|
||||
out vec3 fTangent;
|
||||
out vec2 fTexCoords;
|
||||
out float fTexLayer;
|
||||
flat out int 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);
|
||||
|
|
@ -44,19 +58,28 @@ void main()
|
|||
vec4 finalTangent = vec4(0.0);
|
||||
if (uIsAnimated)
|
||||
{
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
vec4 boneInfluences[2];
|
||||
boneInfluences[0] = vBoneInfluence;
|
||||
boneInfluences[1] = vBoneInfluenceExtra;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
for(int j = 0 ; j < 4; j++)
|
||||
{
|
||||
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
|
||||
int boneIndex = int(boneInfluence.x);
|
||||
float weight = boneInfluence.y;
|
||||
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
|
||||
mat4 inverseBoneMatrix = transpose(inverse(boneMatrix));
|
||||
float weight = vBoneWeights[i];
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
|
||||
mat4 inverseBoneMatrix = transpose(inverse(boneMatrix));
|
||||
|
||||
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
|
||||
{
|
||||
|
|
@ -72,5 +95,5 @@ void main()
|
|||
fTangent = vec3(transpose(inverse(vInstanceMatrix)) * finalTangent);
|
||||
fTexCoords = vTexCoords;
|
||||
fTexLayer = vTexLayer;
|
||||
fColor = vColor;
|
||||
fColor = unpackARGB(int(vColor)) / 255.0;
|
||||
}
|
||||
|
|
|
|||
BIN
FModel/Resources/light.png
Normal file
BIN
FModel/Resources/light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
FModel/Resources/light_off.png
Normal file
BIN
FModel/Resources/light_off.png
Normal file
Binary file not shown.
|
After 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 vBoneIds;
|
||||
layout (location = 8) in vec4 vBoneWeights;
|
||||
layout (location = 7) in vec4 vBoneInfluence;
|
||||
layout (location = 8) in vec4 vBoneInfluenceExtra;
|
||||
layout (location = 9) in mat4 vInstanceMatrix;
|
||||
layout (location = 13) in vec3 vMorphTargetPos;
|
||||
|
||||
|
|
@ -22,6 +22,11 @@ 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);
|
||||
|
|
@ -31,17 +36,25 @@ void main()
|
|||
vec4 finalNormal = vec4(0.0);
|
||||
if (uIsAnimated)
|
||||
{
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
vec4 boneInfluences[2];
|
||||
boneInfluences[0] = vBoneInfluence;
|
||||
boneInfluences[1] = vBoneInfluenceExtra;
|
||||
for(int i = 0 ; i < 2; i++)
|
||||
{
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
for(int j = 0; j < 4; j++)
|
||||
{
|
||||
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
|
||||
int boneIndex = int(boneInfluence.x);
|
||||
float weight = boneInfluence.y;
|
||||
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
|
||||
float weight = vBoneWeights[i];
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
|
||||
|
||||
finalPos += boneMatrix * bindPos * weight;
|
||||
finalNormal += transpose(inverse(boneMatrix)) * bindNormal * weight;
|
||||
finalPos += boneMatrix * bindPos * weight;
|
||||
finalNormal += transpose(inverse(boneMatrix)) * bindNormal * weight;
|
||||
}
|
||||
}
|
||||
finalPos = normalize(finalPos);
|
||||
finalNormal = normalize(finalNormal);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -49,10 +62,10 @@ void main()
|
|||
finalNormal = bindNormal;
|
||||
}
|
||||
|
||||
finalPos = vInstanceMatrix * finalPos;
|
||||
float scaleFactor = distance(vec3(finalPos), uViewPos) * 0.0035;
|
||||
vec4 nor = transpose(inverse(vInstanceMatrix)) * normalize(finalNormal) * scaleFactor;
|
||||
vec4 worldPos = vInstanceMatrix * finalPos;
|
||||
float scaleFactor = length(uViewPos - worldPos.xyz) * 0.0035;
|
||||
vec4 nor = transpose(inverse(vInstanceMatrix)) * finalNormal * scaleFactor;
|
||||
finalPos.xyz += nor.xyz;
|
||||
|
||||
gl_Position = uProjection * uView * finalPos;
|
||||
gl_Position = uProjection * uView * vInstanceMatrix * finalPos;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#version 460 core
|
||||
|
||||
layout (location = 1) in vec3 vPos;
|
||||
layout (location = 7) in vec4 vBoneIds;
|
||||
layout (location = 8) in vec4 vBoneWeights;
|
||||
layout (location = 7) in vec4 vBoneInfluence;
|
||||
layout (location = 8) in vec4 vBoneInfluenceExtra;
|
||||
layout (location = 9) in mat4 vInstanceMatrix;
|
||||
layout (location = 13) in vec3 vMorphTargetPos;
|
||||
|
||||
|
|
@ -20,6 +20,11 @@ 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);
|
||||
|
|
@ -27,12 +32,19 @@ void main()
|
|||
vec4 finalPos = vec4(0.0);
|
||||
if (uIsAnimated)
|
||||
{
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
vec4 boneInfluences[2];
|
||||
boneInfluences[0] = vBoneInfluence;
|
||||
boneInfluences[1] = vBoneInfluenceExtra;
|
||||
for(int i = 0 ; i < 2; i++)
|
||||
{
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
for(int j = 0; j < 4; j++)
|
||||
{
|
||||
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
|
||||
int boneIndex = int(boneInfluence.x);
|
||||
float weight = boneInfluence.y;
|
||||
|
||||
finalPos += uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]) * bindPos * vBoneWeights[i];
|
||||
finalPos += uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]) * bindPos * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
else finalPos = bindPos;
|
||||
|
|
|
|||
BIN
FModel/Resources/square.png
Normal file
BIN
FModel/Resources/square.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
FModel/Resources/square_off.png
Normal file
BIN
FModel/Resources/square_off.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -13,9 +13,9 @@ public class CustomDirectory : ViewModel
|
|||
case "Fortnite [LIVE]":
|
||||
return 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("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/")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,110 +1,109 @@
|
|||
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", "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']")
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,12 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Animations;
|
||||
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;
|
||||
|
|
@ -47,6 +50,25 @@ namespace FModel.Settings
|
|||
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
|
||||
{
|
||||
|
|
@ -159,6 +181,13 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _updateMode, value);
|
||||
}
|
||||
|
||||
private string _commitHash = Constants.APP_VERSION;
|
||||
public string CommitHash
|
||||
{
|
||||
get => _commitHash;
|
||||
set => SetProperty(ref _commitHash, value);
|
||||
}
|
||||
|
||||
private bool _keepDirectoryStructure = true;
|
||||
public bool KeepDirectoryStructure
|
||||
{
|
||||
|
|
@ -231,6 +260,8 @@ namespace FModel.Settings
|
|||
|
||||
[JsonIgnore]
|
||||
public DirectorySettings CurrentDir { get; set; }
|
||||
[JsonIgnore]
|
||||
public string ShortCommitHash => CommitHash[..7];
|
||||
|
||||
/// <summary>
|
||||
/// TO DELETEEEEEEEEEEEEE
|
||||
|
|
@ -347,6 +378,13 @@ 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using FModel.Framework;
|
|||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EpicManifestParser.Objects;
|
||||
|
||||
using EpicManifestParser.Api;
|
||||
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
|
||||
using RestSharp;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
|
@ -14,45 +18,18 @@ 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)
|
||||
{
|
||||
if (await IsExpired().ConfigureAwait(false))
|
||||
{
|
||||
var auth = await GetAuthAsync(token).ConfigureAwait(false);
|
||||
if (auth != null)
|
||||
{
|
||||
UserSettings.Default.LastAuthResponse = auth;
|
||||
}
|
||||
}
|
||||
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 ? 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;
|
||||
return response.IsSuccessful ? ManifestInfo.Deserialize(response.RawBytes) : null;
|
||||
}
|
||||
|
||||
public ManifestInfo GetManifest(CancellationToken token)
|
||||
|
|
@ -60,9 +37,16 @@ public class EpicApiEndpoint : AbstractApiProvider
|
|||
return GetManifestAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public ContentBuildManifestInfo GetContentBuildManifest(CancellationToken token, string label)
|
||||
public async Task VerifyAuth(CancellationToken token)
|
||||
{
|
||||
return GetContentBuildManifestAsync(token, label).GetAwaiter().GetResult();
|
||||
if (await IsExpired().ConfigureAwait(false))
|
||||
{
|
||||
var auth = await GetAuthAsync(token).ConfigureAwait(false);
|
||||
if (auth != null)
|
||||
{
|
||||
UserSettings.Default.LastAuthResponse = auth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<AuthResponse> GetAuthAsync(CancellationToken token)
|
||||
|
|
|
|||
|
|
@ -116,10 +116,13 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
return communityDesign;
|
||||
}
|
||||
|
||||
public void CheckForUpdates(EUpdateMode updateMode)
|
||||
public void CheckForUpdates(EUpdateMode updateMode, bool launch = false)
|
||||
{
|
||||
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
|
||||
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
|
||||
if (launch)
|
||||
{
|
||||
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
|
||||
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
|
||||
}
|
||||
AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}");
|
||||
}
|
||||
|
||||
|
|
@ -130,9 +133,14 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
args.UpdateInfo = new UpdateInfoEventArgs
|
||||
{
|
||||
CurrentVersion = _infos.Version,
|
||||
CurrentVersion = _infos.Version.SubstringBefore('-'),
|
||||
ChangelogURL = _infos.ChangelogUrl,
|
||||
DownloadURL = _infos.DownloadUrl
|
||||
DownloadURL = _infos.DownloadUrl,
|
||||
Mandatory = new CustomMandatory
|
||||
{
|
||||
Value = UserSettings.Default.UpdateMode == EUpdateMode.Qa,
|
||||
CommitHash = _infos.Version.SubstringAfter('+')
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -141,8 +149,10 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
if (args is { CurrentVersion: { } })
|
||||
{
|
||||
var qa = (CustomMandatory) args.Mandatory;
|
||||
var currentVersion = new System.Version(args.CurrentVersion);
|
||||
if (currentVersion == args.InstalledVersion)
|
||||
if ((qa.Value && qa.CommitHash == UserSettings.Default.CommitHash) || // qa branch : same commit id
|
||||
(!qa.Value && currentVersion == args.InstalledVersion && args.CurrentVersion == UserSettings.Default.CommitHash)) // stable - beta branch : same version + commit id = version
|
||||
{
|
||||
if (UserSettings.Default.ShowChangelog)
|
||||
ShowChangelog(args);
|
||||
|
|
@ -152,7 +162,7 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
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?",
|
||||
Text = $"The latest version of FModel {UserSettings.Default.UpdateMode.GetDescription()} is {(qa.Value ? qa.ShortCommitHash : args.CurrentVersion)}. You are using version {(qa.Value ? UserSettings.Default.ShortCommitHash : args.InstalledVersion)}. Do you want to {(downgrade ? "downgrade" : "update")} the application now?",
|
||||
Caption = $"{(downgrade ? "Downgrade" : "Update")} Available",
|
||||
Icon = MessageBoxImage.Question,
|
||||
Buttons = MessageBoxButtons.YesNo(),
|
||||
|
|
@ -166,7 +176,8 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
if (AutoUpdater.DownloadUpdate(args))
|
||||
{
|
||||
UserSettings.Default.ShowChangelog = true;
|
||||
UserSettings.Default.ShowChangelog = currentVersion != args.InstalledVersion;
|
||||
UserSettings.Default.CommitHash = qa.CommitHash;
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -196,3 +207,9 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
UserSettings.Default.ShowChangelog = false;
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomMandatory : Mandatory
|
||||
{
|
||||
public string CommitHash { get; set; }
|
||||
public string ShortCommitHash => CommitHash[..7];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
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;
|
||||
|
|
@ -13,7 +8,15 @@ 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 RestSharp;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
||||
|
|
@ -40,26 +43,22 @@ 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 = ZlibStream.UncompressBuffer(compressedBuffer);
|
||||
if (uncompressedBuffer.Length != Header.UncompressedSize)
|
||||
throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}");
|
||||
var uncompressedBuffer = new byte[(int)Header.UncompressedSize];
|
||||
ZlibHelper.Decompress(compressedBuffer, 0, compressedBuffer.Length, uncompressedBuffer, 0, uncompressedBuffer.Length);
|
||||
|
||||
using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
|
||||
Chunks = manifest.ReadArray<VChunk>((int) Header.ChunkCount);
|
||||
Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest));
|
||||
var manifestAr = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
|
||||
Chunks = manifestAr.ReadArray<VChunk>((int) Header.ChunkCount);
|
||||
Paks = manifestAr.ReadArray((int) Header.PakCount, () => new VPak(manifestAr));
|
||||
|
||||
if (manifest.Position != manifest.Length)
|
||||
throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}");
|
||||
if (manifestAr.Position != manifestAr.Length)
|
||||
throw new ParserException(manifestAr, $"Parsing failed, {manifestAr.Position} != {manifestAr.Length}");
|
||||
}
|
||||
|
||||
_client = new HttpClient(new HttpClientHandler
|
||||
|
|
|
|||
|
|
@ -1,3 +1,15 @@
|
|||
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;
|
||||
|
|
@ -5,17 +17,9 @@ 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;
|
||||
|
||||
|
|
@ -46,7 +50,7 @@ public class ApplicationViewModel : ViewModel
|
|||
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
|
||||
private CopyCommand _copyCommand;
|
||||
|
||||
public string InitialWindowTitle => $"FModel {UserSettings.Default.UpdateMode}";
|
||||
public string InitialWindowTitle => $"FModel {UserSettings.Default.UpdateMode.GetDescription()}";
|
||||
public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown";
|
||||
public string TitleExtra => $"({UserSettings.Default.CurrentDir.UeVersion}){(Build != EBuildKind.Release ? $" ({Build})" : "")}";
|
||||
|
||||
|
|
@ -56,7 +60,6 @@ public class ApplicationViewModel : ViewModel
|
|||
public SettingsViewModel SettingsView { get; }
|
||||
public AesManagerViewModel AesManager { get; }
|
||||
public AudioPlayerViewModel AudioPlayer { get; }
|
||||
private OodleCompressor _oodle;
|
||||
|
||||
public ApplicationViewModel()
|
||||
{
|
||||
|
|
@ -79,6 +82,24 @@ public class ApplicationViewModel : ViewModel
|
|||
}
|
||||
|
||||
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();
|
||||
AesManager = new AesManagerViewModel(CUE4Parse);
|
||||
|
|
@ -156,14 +177,23 @@ public class ApplicationViewModel : ViewModel
|
|||
CUE4Parse.ClearProvider();
|
||||
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
CUE4Parse.LoadVfs(cancellationToken, AesManager.AesKeys);
|
||||
CUE4Parse.Provider.LoadIniConfigs();
|
||||
// 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 async Task InitVgmStream()
|
||||
public static async Task InitVgmStream()
|
||||
{
|
||||
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
|
||||
if (File.Exists(vgmZipFilePath)) return;
|
||||
|
|
@ -171,9 +201,17 @@ 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 zip = ZipFile.Read(vgmZipFilePath);
|
||||
var zipDir = vgmZipFilePath.SubstringBeforeLast("\\");
|
||||
foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently);
|
||||
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.OpenRead(entryPath);
|
||||
await using var entryStream = entry.Open();
|
||||
await entryStream.CopyToAsync(entryFs);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -181,43 +219,44 @@ public class ApplicationViewModel : ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public async Task InitOodle()
|
||||
public static async Task InitImGuiSettings(bool forceDownload)
|
||||
{
|
||||
var dataDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
||||
var oodlePath = Path.Combine(dataDir.FullName, OodleCUE4.OODLE_DLL_NAME);
|
||||
var imgui = "imgui.ini";
|
||||
var imguiPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", imgui);
|
||||
|
||||
if (File.Exists(OodleCUE4.OODLE_DLL_NAME))
|
||||
{
|
||||
File.Move(OodleCUE4.OODLE_DLL_NAME, oodlePath, true);
|
||||
}
|
||||
else if (!File.Exists(oodlePath))
|
||||
{
|
||||
var result = await OodleCUE4.DownloadOodleDll(oodlePath);
|
||||
if (!result) return;
|
||||
}
|
||||
if (File.Exists(imgui)) File.Move(imgui, imguiPath, true);
|
||||
if (File.Exists(imguiPath) && !forceDownload) return;
|
||||
|
||||
if (File.Exists("oo2core_8_win64.dll"))
|
||||
File.Delete("oo2core_8_win64.dll");
|
||||
|
||||
_oodle = new OodleCompressor(oodlePath);
|
||||
|
||||
unsafe
|
||||
{
|
||||
OodleCUE4.DecompressFunc = (bufferPtr, bufferSize, outputPtr, outputSize, a, b, c, d, e, f, g, h, i, threadModule) =>
|
||||
_oodle.Decompress(new IntPtr(bufferPtr), bufferSize, new IntPtr(outputPtr), outputSize,
|
||||
(OodleLZ_FuzzSafe) a, (OodleLZ_CheckCRC) b, (OodleLZ_Verbosity) c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase) threadModule);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
await ApplicationService.ApiEndpointView.DownloadFileAsync($"https://cdn.fmodel.app/d/configurations/{imgui}", imguiPath);
|
||||
if (new FileInfo(imguiPath).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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -552,14 +552,26 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
case "opus":
|
||||
case "wem":
|
||||
case "at9":
|
||||
case "raw":
|
||||
{
|
||||
if (TryConvert(out var wavFilePath) && !string.IsNullOrEmpty(wavFilePath))
|
||||
if (TryConvert(out var 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -567,7 +579,8 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool TryConvert(out string wavFilePath)
|
||||
private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath);
|
||||
private bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath)
|
||||
{
|
||||
wavFilePath = string.Empty;
|
||||
var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe");
|
||||
|
|
@ -577,22 +590,46 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
if (!File.Exists(vgmFilePath)) return false;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(SelectedAudioFile.FilePath.SubstringBeforeLast("/"));
|
||||
File.WriteAllBytes(SelectedAudioFile.FilePath, SelectedAudioFile.Data);
|
||||
Directory.CreateDirectory(inputFilePath.SubstringBeforeLast("/"));
|
||||
File.WriteAllBytes(inputFilePath, inputFileData);
|
||||
|
||||
wavFilePath = Path.ChangeExtension(SelectedAudioFile.FilePath, ".wav");
|
||||
wavFilePath = Path.ChangeExtension(inputFilePath, ".wav");
|
||||
var vgmProcess = Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = vgmFilePath,
|
||||
Arguments = $"-o \"{wavFilePath}\" \"{SelectedAudioFile.FilePath}\"",
|
||||
Arguments = $"-o \"{wavFilePath}\" \"{inputFilePath}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
vgmProcess?.WaitForExit();
|
||||
vgmProcess?.WaitForExit(5000);
|
||||
|
||||
File.Delete(SelectedAudioFile.FilePath);
|
||||
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
|
||||
{
|
||||
FileName = binkadecPath,
|
||||
Arguments = $"-i \"{SelectedAudioFile.FilePath}\" -o \"{rawFilePath}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
binkadecProcess?.WaitForExit(5000);
|
||||
|
||||
File.Delete(SelectedAudioFile.FilePath);
|
||||
return binkadecProcess?.ExitCode == 0 && File.Exists(rawFilePath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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.Files.Count <= 0)
|
||||
if (_applicationView.CUE4Parse.Provider.Keys.Count == 0 && _applicationView.CUE4Parse.Provider.RequiredKeys.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));
|
||||
|
|
@ -61,7 +61,6 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
// filter what to show
|
||||
switch (UserSettings.Default.LoadingMode)
|
||||
{
|
||||
case ELoadingMode.Single:
|
||||
case ELoadingMode.Multiple:
|
||||
{
|
||||
var l = (IList) parameter;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
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;
|
||||
|
||||
|
|
@ -72,6 +75,17 @@ 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}";
|
||||
|
|
@ -84,31 +98,35 @@ 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)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
DirectoryFiles.Add(new FileItem(reader.Name, reader.Length)
|
||||
{
|
||||
Guid = reader.EncryptionKeyGuid,
|
||||
IsEncrypted = reader.IsEncrypted,
|
||||
IsEnabled = false,
|
||||
Key = string.Empty
|
||||
});
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,16 +33,28 @@ public class GameSelectorViewModel : ViewModel
|
|||
public IList<CustomDirectory> CustomDirectories { get; set; }
|
||||
}
|
||||
|
||||
private bool _useCustomEGames;
|
||||
public bool UseCustomEGames
|
||||
{
|
||||
get => _useCustomEGames;
|
||||
set => SetProperty(ref _useCustomEGames, value);
|
||||
}
|
||||
|
||||
private DirectorySettings _selectedDirectory;
|
||||
public DirectorySettings SelectedDirectory
|
||||
{
|
||||
get => _selectedDirectory;
|
||||
set => SetProperty(ref _selectedDirectory, value);
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedDirectory, value);
|
||||
if (_selectedDirectory != null) UseCustomEGames = EnumerateUeGames().ElementAt(1).Contains(_selectedDirectory.UeVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ObservableCollection<DirectorySettings> _detectedDirectories;
|
||||
public ReadOnlyObservableCollection<DirectorySettings> DetectedDirectories { get; }
|
||||
public ReadOnlyObservableCollection<EGame> UeVersions { get; }
|
||||
public ReadOnlyObservableCollection<EGame> UeGames { get; }
|
||||
public ReadOnlyObservableCollection<EGame> CustomUeGames { get; }
|
||||
|
||||
public GameSelectorViewModel(string gameDirectory)
|
||||
{
|
||||
|
|
@ -61,7 +73,9 @@ public class GameSelectorViewModel : ViewModel
|
|||
else
|
||||
SelectedDirectory = DetectedDirectories.FirstOrDefault();
|
||||
|
||||
UeVersions = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
|
||||
var ueGames = EnumerateUeGames().ToArray();
|
||||
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(ueGames[0]));
|
||||
CustomUeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(ueGames[1]));
|
||||
}
|
||||
|
||||
public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory);
|
||||
|
|
@ -80,15 +94,15 @@ public class GameSelectorViewModel : ViewModel
|
|||
SelectedDirectory = DetectedDirectories.Last();
|
||||
}
|
||||
|
||||
private IEnumerable<EGame> EnumerateUeGames()
|
||||
private IEnumerable<IGrouping<bool, EGame>> EnumerateUeGames()
|
||||
=> Enum.GetValues<EGame>()
|
||||
.GroupBy(value => (int)value)
|
||||
.Select(group => group.First())
|
||||
.OrderBy(value => (int)value == ((int)value & ~0xF));
|
||||
.GroupBy(value => (int)value == ((int)value & ~0xF));
|
||||
private IEnumerable<DirectorySettings> EnumerateDetectedGames()
|
||||
{
|
||||
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_3);
|
||||
yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_3);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
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.Services;
|
||||
|
|
@ -25,6 +27,13 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _useCustomOutputFolders, value);
|
||||
}
|
||||
|
||||
private bool _useCustomEGames;
|
||||
public bool UseCustomEGames
|
||||
{
|
||||
get => _useCustomEGames;
|
||||
set => SetProperty(ref _useCustomEGames, value);
|
||||
}
|
||||
|
||||
private EUpdateMode _selectedUpdateMode;
|
||||
public EUpdateMode SelectedUpdateMode
|
||||
{
|
||||
|
|
@ -120,7 +129,12 @@ public class SettingsViewModel : ViewModel
|
|||
public EMeshFormat SelectedMeshExportFormat
|
||||
{
|
||||
get => _selectedMeshExportFormat;
|
||||
set => SetProperty(ref _selectedMeshExportFormat, value);
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedMeshExportFormat, value);
|
||||
RaisePropertyChanged(nameof(SocketSettingsEnabled));
|
||||
RaisePropertyChanged(nameof(CompressionSettingsEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
private ESocketFormat _selectedSocketExportFormat;
|
||||
|
|
@ -130,6 +144,13 @@ 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
|
||||
{
|
||||
|
|
@ -151,8 +172,12 @@ 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 ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
|
||||
public ReadOnlyObservableCollection<EGame> CustomUeGames { get; private set; }
|
||||
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
|
||||
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
|
||||
public ReadOnlyObservableCollection<EDiscordRpc> DiscordRpcs { get; private set; }
|
||||
|
|
@ -160,6 +185,7 @@ 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; }
|
||||
|
|
@ -183,6 +209,7 @@ public class SettingsViewModel : ViewModel
|
|||
private EIconStyle _cosmeticStyleSnapshot;
|
||||
private EMeshFormat _meshExportFormatSnapshot;
|
||||
private ESocketFormat _socketExportFormatSnapshot;
|
||||
private EFileCompressionFormat _compressionFormatSnapshot;
|
||||
private ELodFormat _lodExportFormatSnapshot;
|
||||
private EMaterialFormat _materialExportFormatSnapshot;
|
||||
private ETextureFormat _textureExportFormatSnapshot;
|
||||
|
|
@ -223,6 +250,7 @@ public class SettingsViewModel : ViewModel
|
|||
_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;
|
||||
|
|
@ -238,14 +266,19 @@ 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;
|
||||
|
||||
var ueGames = EnumerateUeGames().ToArray();
|
||||
UseCustomEGames = ueGames[1].Contains(SelectedUeGame);
|
||||
|
||||
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
|
||||
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
|
||||
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(ueGames[0]));
|
||||
CustomUeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(ueGames[1]));
|
||||
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
|
||||
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
|
||||
DiscordRpcs = new ReadOnlyObservableCollection<EDiscordRpc>(new ObservableCollection<EDiscordRpc>(EnumerateDiscordRpcs()));
|
||||
|
|
@ -253,6 +286,7 @@ 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()));
|
||||
|
|
@ -295,6 +329,7 @@ public class SettingsViewModel : ViewModel
|
|||
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;
|
||||
|
|
@ -308,11 +343,11 @@ public class SettingsViewModel : ViewModel
|
|||
}
|
||||
|
||||
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
|
||||
private IEnumerable<EGame> EnumerateUeGames()
|
||||
private IEnumerable<IGrouping<bool, EGame>> EnumerateUeGames()
|
||||
=> Enum.GetValues<EGame>()
|
||||
.GroupBy(value => (int)value)
|
||||
.Select(group => group.First())
|
||||
.OrderBy(value => (int)value == ((int)value & ~0xF));
|
||||
.GroupBy(value => (int)value == ((int)value & ~0xF));
|
||||
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
|
||||
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
|
||||
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
|
||||
|
|
@ -320,6 +355,7 @@ 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>();
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ public class TabImage : ViewModel
|
|||
|
||||
public class TabItem : ViewModel
|
||||
{
|
||||
public string ParentExportType { get; private set; }
|
||||
|
||||
private string _header;
|
||||
public string Header
|
||||
{
|
||||
|
|
@ -211,25 +213,41 @@ public class TabItem : ViewModel
|
|||
private GoToCommand _goToCommand;
|
||||
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
|
||||
|
||||
public TabItem(string header, string directory)
|
||||
public TabItem(string header, string directory, string parentExportType)
|
||||
{
|
||||
Header = header;
|
||||
Directory = directory;
|
||||
ParentExportType = parentExportType;
|
||||
_images = new ObservableCollection<TabImage>();
|
||||
}
|
||||
|
||||
public void ClearImages()
|
||||
public void SoftReset(string header, string directory)
|
||||
{
|
||||
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)
|
||||
=> AddImage(texture.Name, texture.RenderNearestNeighbor, texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform), save, updateUi);
|
||||
{
|
||||
var img = texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform);
|
||||
if (texture is UTextureCube)
|
||||
{
|
||||
img = img?.ToPanorama();
|
||||
}
|
||||
|
||||
AddImage(texture.Name, texture.RenderNearestNeighbor, img, save, updateUi);
|
||||
}
|
||||
|
||||
public void AddImage(string name, bool rnn, SKBitmap[] img, bool save, bool updateUi)
|
||||
{
|
||||
|
|
@ -266,15 +284,6 @@ 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)
|
||||
{
|
||||
|
|
@ -360,12 +369,13 @@ public class TabControlViewModel : ViewModel
|
|||
SelectedTab = TabsItems.FirstOrDefault();
|
||||
}
|
||||
|
||||
public void AddTab(string header = null, string directory = null)
|
||||
public void AddTab(string header = null, string directory = null, string parentExportType = 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;
|
||||
|
|
@ -375,7 +385,7 @@ public class TabControlViewModel : ViewModel
|
|||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
_tabItems.Add(new TabItem(h, d));
|
||||
_tabItems.Add(new TabItem(h, d, p));
|
||||
SelectedTab = _tabItems.Last();
|
||||
});
|
||||
}
|
||||
|
|
@ -437,6 +447,6 @@ public class TabControlViewModel : ViewModel
|
|||
|
||||
private static IEnumerable<TabItem> EnumerateTabs()
|
||||
{
|
||||
yield return new TabItem("New Tab", string.Empty);
|
||||
yield return new TabItem("New Tab", string.Empty, string.Empty);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,14 +61,27 @@
|
|||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="UE Versions" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding UeVersions}" Margin="0 0 0 5"
|
||||
<ComboBox Grid.Row="1" Grid.Column="2" Margin="0 0 0 5"
|
||||
VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory.UeVersion, Mode=TwoWay}">
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
|
||||
<Setter Property="ItemsSource" Value="{Binding UeGames}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding UseCustomEGames}" Value="True">
|
||||
<Setter Property="ItemsSource" Value="{Binding CustomUeGames}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<CheckBox Grid.Row="1" Grid.Column="4" Margin="5 0 0 5" ToolTip="Enable custom UE versions"
|
||||
IsChecked="{Binding UseCustomEGames, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
BorderBrush="White" Style="{StaticResource HighlightedCheckBox}" />
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Directory" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Row="2" Grid.Column="2" Text="{Binding SelectedDirectory.GameDirectory, Mode=TwoWay}" />
|
||||
|
|
@ -83,7 +96,9 @@
|
|||
</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,6 +1,7 @@
|
|||
using FModel.ViewModels;
|
||||
using Ookii.Dialogs.Wpf;
|
||||
using System.Windows;
|
||||
using CUE4Parse.Utils;
|
||||
|
||||
namespace FModel.Views;
|
||||
|
||||
|
|
@ -38,6 +39,7 @@ public partial class DirectorySelector
|
|||
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
|
||||
if (folderBrowser.ShowDialog() == true)
|
||||
{
|
||||
HelloMyNameIsGame.Text = folderBrowser.SelectedPath.SubstringAfterLast('\\');
|
||||
HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ public partial class ImageMerger
|
|||
var fileBrowser = new OpenFileDialog
|
||||
{
|
||||
Title = "Add image(s)",
|
||||
InitialDirectory = $"{UserSettings.Default.OutputDirectory}\\Exports",
|
||||
InitialDirectory = Path.Combine(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 (*.*)|*.*"
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using FModel.Extensions;
|
||||
using ICSharpCode.AvalonEdit.Rendering;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
|
@ -29,8 +30,10 @@ public class GamePathElementGenerator : VisualLineElementGenerator
|
|||
public override VisualLineElement ConstructElement(int offset)
|
||||
{
|
||||
var m = FindMatch(offset);
|
||||
if (!m.Success || m.Index != 0) return null;
|
||||
if (!m.Success || m.Index != 0 ||
|
||||
!m.Groups.TryGetValue("target", out var g)) return null;
|
||||
|
||||
return m.Groups.TryGetValue("target", out var g) ? new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) : null;
|
||||
var parentExportType = CurrentContext.Document.GetParentExportType(offset);
|
||||
return new GamePathVisualLineText(g.Value, parentExportType, CurrentContext.VisualLine, g.Length + g.Index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,16 @@ public class GamePathVisualLineText : VisualLineText
|
|||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
||||
public delegate void GamePathOnClick(string gamePath);
|
||||
public delegate void GamePathOnClick(string gamePath, string parentExportType);
|
||||
|
||||
public event GamePathOnClick OnGamePathClicked;
|
||||
private readonly string _gamePath;
|
||||
private readonly string _parentExportType;
|
||||
|
||||
public GamePathVisualLineText(string gamePath, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
|
||||
public GamePathVisualLineText(string gamePath, string parentExportType, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
|
||||
{
|
||||
_gamePath = gamePath;
|
||||
_parentExportType = parentExportType;
|
||||
}
|
||||
|
||||
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
|
||||
|
|
@ -56,14 +58,14 @@ public class GamePathVisualLineText : VisualLineText
|
|||
if (e.Handled || OnGamePathClicked == null)
|
||||
return;
|
||||
|
||||
OnGamePathClicked(_gamePath);
|
||||
OnGamePathClicked(_gamePath, _parentExportType);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override VisualLineText CreateInstance(int length)
|
||||
{
|
||||
var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length);
|
||||
a.OnGamePathClicked += async gamePath =>
|
||||
var a = new GamePathVisualLineText(_gamePath, _parentExportType, ParentVisualLine, length);
|
||||
a.OnGamePathClicked += async (gamePath, parentExportType) =>
|
||||
{
|
||||
var obj = gamePath.SubstringAfterLast('.');
|
||||
var package = gamePath.SubstringBeforeLast('.');
|
||||
|
|
@ -80,17 +82,17 @@ public class GamePathVisualLineText : VisualLineText
|
|||
}
|
||||
else
|
||||
{
|
||||
lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj);
|
||||
lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumber(obj);
|
||||
line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
|
||||
}
|
||||
|
||||
|
||||
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
|
||||
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
_applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj));
|
||||
_applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType));
|
||||
}
|
||||
};
|
||||
return a;
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ public partial class AvalonEditor
|
|||
|
||||
if (!tabItem.ShouldScroll) return;
|
||||
|
||||
var lineNumber = avalonEditor.Document.Text.GetLineNumber(tabItem.ScrollTrigger);
|
||||
var lineNumber = avalonEditor.Document.Text.GetNameLineNumber(tabItem.ScrollTrigger);
|
||||
var line = avalonEditor.Document.GetLineByNumber(lineNumber);
|
||||
avalonEditor.Select(line.Offset, line.Length);
|
||||
avalonEditor.ScrollToLine(lineNumber);
|
||||
|
|
@ -223,10 +223,9 @@ public partial class AvalonEditor
|
|||
|
||||
private void OnTabClose(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is not TabControlViewModel.TabEventArgs e || e.TabToRemove.Document == null)
|
||||
if (eventArgs is not TabControlViewModel.TabEventArgs e || e.TabToRemove.Document?.FileName is not { } fileName)
|
||||
return;
|
||||
|
||||
var fileName = e.TabToRemove.Document.FileName;
|
||||
if (_savedCarets.ContainsKey(fileName))
|
||||
_savedCarets.Remove(fileName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,31 @@
|
|||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HighlightedCheckBox" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="White" />
|
||||
<Style.Triggers>
|
||||
<EventTrigger RoutedEvent="CheckBox.Loaded">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<ColorAnimation From="YellowGreen"
|
||||
To="Goldenrod"
|
||||
Storyboard.TargetProperty="BorderBrush.Color"
|
||||
AutoReverse="True"
|
||||
RepeatBehavior="Forever"
|
||||
Duration="0:0:1" />
|
||||
<ThicknessAnimation From="0"
|
||||
To="2"
|
||||
Storyboard.TargetProperty="BorderThickness"
|
||||
AutoReverse="True"
|
||||
RepeatBehavior="Forever"
|
||||
Duration="0:0:1" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CustomSeparator" TargetType="Separator" BasedOn="{StaticResource {x:Type Separator}}">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.Layer1BorderBrush}}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"/>
|
||||
|
|
@ -183,9 +208,6 @@
|
|||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Single}">
|
||||
<Setter Property="SelectionMode" Value="Single" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Multiple}">
|
||||
<Setter Property="SelectionMode" Value="Extended" />
|
||||
</DataTrigger>
|
||||
|
|
@ -859,7 +881,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Model (.psk)" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Models">
|
||||
<MenuItem Header="Save Model" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Models">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -868,7 +890,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Animation (.psa)" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Animations">
|
||||
<MenuItem Header="Save Animation" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Animations">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Model (.psk)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem Header="Save Model" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Models" />
|
||||
|
|
@ -215,7 +215,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Animation (.psa)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem Header="Save Animation" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Animations" />
|
||||
|
|
|
|||
|
|
@ -122,15 +122,29 @@
|
|||
<Button Grid.Row="5" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" Margin="0 0 0 5" />
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Text="UE Versions *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the UE version to use when parsing packages" />
|
||||
<ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.UeGames}" SelectedItem="{Binding SettingsView.SelectedUeGame, Mode=TwoWay}"
|
||||
<ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" SelectedItem="{Binding SettingsView.SelectedUeGame, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Margin="0 0 0 5">
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
|
||||
<Setter Property="ItemsSource" Value="{Binding SettingsView.UeGames}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding SettingsView.UseCustomEGames}" Value="True">
|
||||
<Setter Property="ItemsSource" Value="{Binding SettingsView.CustomUeGames}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<CheckBox Grid.Row="6" Grid.Column="6" Margin="5 0 0 5" ToolTip="Enable custom UE versions"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
IsChecked="{Binding SettingsView.UseCustomEGames, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
BorderBrush="White" Style="{StaticResource HighlightedCheckBox}" />
|
||||
|
||||
<TextBlock Grid.Row="7" Grid.Column="0" Text="Texture Platform *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the game's platform to ensure texture compatibility" />
|
||||
<ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.Platforms}" SelectedItem="{Binding SettingsView.SelectedUePlatform, Mode=TwoWay}"
|
||||
|
|
@ -316,6 +330,7 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
|
@ -330,7 +345,7 @@
|
|||
<TextBox Grid.Row="0" Grid.Column="2" Text="{Binding ModelDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
|
||||
<Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseModels" Margin="0 0 0 5" />
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Mesh Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Mesh Format" VerticalAlignment="Center" Margin="0 0 0 5"/>
|
||||
<ComboBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MeshExportFormats}" SelectedItem="{Binding SettingsView.SelectedMeshExportFormat, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
|
|
@ -340,8 +355,28 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Socket Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Socket Format (ActorX)" VerticalAlignment="Center" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.SocketSettingsEnabled}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.SocketExportFormats}" SelectedItem="{Binding SettingsView.SelectedSocketExportFormat, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.SocketSettingsEnabled}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="Compression Format (UEFormat)" VerticalAlignment="Center" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.CompressionSettingsEnabled}"/>
|
||||
<ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.CompressionFormats}" SelectedItem="{Binding SettingsView.SelectedCompressionFormat, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.CompressionSettingsEnabled}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Text="Level Of Detail Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.LodExportFormats}" SelectedItem="{Binding SettingsView.SelectedLodExportFormat, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
@ -350,62 +385,52 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="Level Of Detail Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.LodExportFormats}" SelectedItem="{Binding SettingsView.SelectedLodExportFormat, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<Separator Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
|
||||
|
||||
<Separator Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
|
||||
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Text="Preview Max Texture Size" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Slider Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" TickPlacement="None" Minimum="4" Maximum="4096" Ticks="4,8,16,32,64,128,256,512,1024,2048,4096"
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Text="Preview Max Texture Size" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Slider Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" TickPlacement="None" Minimum="4" Maximum="4096" Ticks="4,8,16,32,64,128,256,512,1024,2048,4096"
|
||||
AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 5 0 5"
|
||||
Value="{Binding PreviewMaxTextureSize, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Text="Preview Static Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="7" Grid.Column="0" Text="Preview Static Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding PreviewStaticMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
|
||||
|
||||
<TextBlock Grid.Row="7" Grid.Column="0" Text="Preview Skeletal Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="8" Grid.Column="0" Text="Preview Skeletal Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding PreviewSkeletalMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
|
||||
|
||||
<TextBlock Grid.Row="8" Grid.Column="0" Text="Preview Materials" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Text="Preview Materials" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding PreviewMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
|
||||
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Text="Preview Levels (.umap)" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="10" Grid.Column="0" Text="Preview Levels (.umap)" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding PreviewWorlds, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
|
||||
|
||||
<TextBlock Grid.Row="10" Grid.Column="0" Text="Save Materials Embedded within Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="11" Grid.Column="0" Text="Save Materials Embedded within Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding SaveEmbeddedMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
|
||||
|
||||
<TextBlock Grid.Row="11" Grid.Column="0" Text="Save Morph Targets in Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="12" Grid.Column="0" Text="Save Morph Targets in Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding SaveMorphTargets, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
|
||||
|
||||
<TextBlock Grid.Row="12" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" />
|
||||
<CheckBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="13" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" />
|
||||
<CheckBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding SaveSkeletonAsMesh, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 0"/>
|
||||
|
||||
<Separator Grid.Row="13" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
|
||||
<Separator Grid.Row="14" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
|
||||
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Text="Material Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MaterialExportFormats}" SelectedItem="{Binding SettingsView.SelectedMaterialExportFormat, Mode=TwoWay}"
|
||||
<TextBlock Grid.Row="15" Grid.Column="0" Text="Material Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MaterialExportFormats}" SelectedItem="{Binding SettingsView.SelectedMaterialExportFormat, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
@ -414,8 +439,8 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="15" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}"
|
||||
<TextBlock Grid.Row="16" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ public class Animation : IDisposable
|
|||
if (ImGui.MenuItem("Save"))
|
||||
{
|
||||
s.WindowShouldFreeze(true);
|
||||
saver.Value = new Exporter(_export).TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out saver.Label, out saver.Path);
|
||||
saver.Value = new Exporter(_export, UserSettings.Default.ExportOptions).TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out saver.Label, out saver.Path);
|
||||
s.WindowShouldFreeze(false);
|
||||
}
|
||||
ImGui.Separator();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public class TimeTracker : IDisposable
|
|||
public bool IsActive;
|
||||
public float ElapsedTime;
|
||||
public float MaxElapsedTime;
|
||||
public int TimeMultiplier;
|
||||
|
||||
public TimeTracker()
|
||||
{
|
||||
|
|
@ -29,7 +30,7 @@ public class TimeTracker : IDisposable
|
|||
public void Update(float deltaSeconds)
|
||||
{
|
||||
if (IsPaused || IsActive) return;
|
||||
ElapsedTime += deltaSeconds;
|
||||
ElapsedTime += deltaSeconds * TimeMultiplier;
|
||||
if (ElapsedTime >= MaxElapsedTime) Reset(false);
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +48,11 @@ public class TimeTracker : IDisposable
|
|||
{
|
||||
IsPaused = false;
|
||||
ElapsedTime = 0.0f;
|
||||
if (doMet) MaxElapsedTime = 0.01f;
|
||||
if (doMet)
|
||||
{
|
||||
MaxElapsedTime = 0.01f;
|
||||
TimeMultiplier = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ public class VertexArrayObject<TVertexType, TIndexType> : IDisposable where TVer
|
|||
switch (type)
|
||||
{
|
||||
case VertexAttribPointerType.Int:
|
||||
GL.VertexAttribIPointer(index, count, VertexAttribIntegerType.Int, vertexSize * _sizeOfVertex, (IntPtr) (offset * _sizeOfVertex));
|
||||
case VertexAttribPointerType.UnsignedInt:
|
||||
GL.VertexAttribIPointer(index, count, (VertexAttribIntegerType) type, vertexSize * _sizeOfVertex, offset * _sizeOfVertex);
|
||||
break;
|
||||
default:
|
||||
GL.VertexAttribPointer(index, count, type, false, vertexSize * _sizeOfVertex, offset * _sizeOfVertex);
|
||||
|
|
|
|||
228
FModel/Views/Snooper/Models/Collision.cs
Normal file
228
FModel/Views/Snooper/Models/Collision.cs
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.PhysicsEngine;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class Collision : IDisposable
|
||||
{
|
||||
private const int Slices = 16;
|
||||
private const int Stacks = 8;
|
||||
private const float SectorStep = 2 * MathF.PI / Slices;
|
||||
private const float StackStep = MathF.PI / Stacks;
|
||||
|
||||
private readonly int[] _indexData;
|
||||
private readonly FVector[] _vertexData;
|
||||
private readonly Transform _transform;
|
||||
public readonly string LoweredBoneName;
|
||||
|
||||
private int _handle;
|
||||
private BufferObject<int> _ebo { get; set; }
|
||||
private BufferObject<FVector> _vbo { get; set; }
|
||||
private VertexArrayObject<FVector, int> _vao { get; set; }
|
||||
|
||||
private Collision()
|
||||
{
|
||||
_indexData = [];
|
||||
_vertexData = [];
|
||||
_transform = Transform.Identity;
|
||||
}
|
||||
|
||||
private Collision(FName boneName) : this()
|
||||
{
|
||||
LoweredBoneName = boneName.Text.ToLower();
|
||||
}
|
||||
|
||||
public Collision(FKConvexElem convexElems, FName boneName = default) : this(boneName)
|
||||
{
|
||||
_indexData = convexElems.IndexData;
|
||||
_vertexData = convexElems.VertexData;
|
||||
_transform = new Transform
|
||||
{
|
||||
Position = convexElems.Transform.Translation * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = convexElems.Transform.Rotation,
|
||||
Scale = convexElems.Transform.Scale3D
|
||||
};
|
||||
}
|
||||
|
||||
public Collision(FKSphereElem sphereElem, FName boneName = default) : this(boneName)
|
||||
{
|
||||
_vertexData = new FVector[(Slices + 1) * (Stacks + 1)];
|
||||
for (var i = 0; i <= Stacks; i++)
|
||||
{
|
||||
var stackAngle = MathF.PI / 2 - i * StackStep;
|
||||
var xy = MathF.Cos(stackAngle);
|
||||
var z = MathF.Sin(stackAngle);
|
||||
|
||||
for (var j = 0; j <= Slices; j++)
|
||||
{
|
||||
var sectorAngle = j * SectorStep;
|
||||
var x = xy * MathF.Cos(sectorAngle);
|
||||
var y = xy * MathF.Sin(sectorAngle);
|
||||
_vertexData[i * (Slices + 1) + j] = new FVector(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
_indexData = new int[Stacks * Slices * 6];
|
||||
for (var i = 0; i < Stacks; i++)
|
||||
{
|
||||
for (var j = 0; j < Slices; j++)
|
||||
{
|
||||
var a = i * (Slices + 1) + j;
|
||||
var b = a + Slices + 1;
|
||||
_indexData[(i * Slices + j) * 6 + 0] = a;
|
||||
_indexData[(i * Slices + j) * 6 + 1] = b;
|
||||
_indexData[(i * Slices + j) * 6 + 2] = a + 1;
|
||||
_indexData[(i * Slices + j) * 6 + 3] = b;
|
||||
_indexData[(i * Slices + j) * 6 + 4] = b + 1;
|
||||
_indexData[(i * Slices + j) * 6 + 5] = a + 1;
|
||||
}
|
||||
}
|
||||
|
||||
_transform = new Transform
|
||||
{
|
||||
Position = sphereElem.Center * Constants.SCALE_DOWN_RATIO,
|
||||
Scale = new FVector(sphereElem.Radius)
|
||||
};
|
||||
}
|
||||
|
||||
public Collision(FKBoxElem boxElem, FName boneName = default) : this(boneName)
|
||||
{
|
||||
_vertexData =
|
||||
[
|
||||
new FVector(-boxElem.X, -boxElem.Y, -boxElem.Z),
|
||||
new FVector(boxElem.X, -boxElem.Y, -boxElem.Z),
|
||||
new FVector(boxElem.X, boxElem.Y, -boxElem.Z),
|
||||
new FVector(-boxElem.X, boxElem.Y, -boxElem.Z),
|
||||
new FVector(-boxElem.X, -boxElem.Y, boxElem.Z),
|
||||
new FVector(boxElem.X, -boxElem.Y, boxElem.Z),
|
||||
new FVector(boxElem.X, boxElem.Y, boxElem.Z),
|
||||
new FVector(-boxElem.X, boxElem.Y, boxElem.Z)
|
||||
];
|
||||
|
||||
_indexData =
|
||||
[
|
||||
0, 1, 2, 2, 3, 0,
|
||||
1, 5, 6, 6, 2, 1,
|
||||
5, 4, 7, 7, 6, 5,
|
||||
4, 0, 3, 3, 7, 4,
|
||||
3, 2, 6, 6, 7, 3,
|
||||
4, 5, 1, 1, 0, 4
|
||||
];
|
||||
|
||||
_transform = new Transform
|
||||
{
|
||||
Position = boxElem.Center * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = boxElem.Rotation.Quaternion(),
|
||||
Scale = new FVector(.5f)
|
||||
};
|
||||
}
|
||||
|
||||
public Collision(FKSphylElem sphylElem, FName boneName = default)
|
||||
: this(sphylElem.Length, [sphylElem.Radius, sphylElem.Radius], sphylElem.Center, sphylElem.Rotation, boneName) {}
|
||||
public Collision(FKTaperedCapsuleElem taperedCapsuleElem, FName boneName = default)
|
||||
: this(taperedCapsuleElem.Length, [taperedCapsuleElem.Radius1, taperedCapsuleElem.Radius0], taperedCapsuleElem.Center, taperedCapsuleElem.Rotation, boneName) {}
|
||||
|
||||
private Collision(float length, float[] radius, FVector center = default, FRotator rotator = default, FName boneName = default) : this(boneName)
|
||||
{
|
||||
int vLength = 0;
|
||||
int half = Slices / 2;
|
||||
int k2 = (Slices + 1) * (Stacks + 1);
|
||||
|
||||
_vertexData = new FVector[k2 + Slices + 1];
|
||||
for(int i = 0; i < 2; ++i)
|
||||
{
|
||||
float h = -length / 2.0f + i * length;
|
||||
int start = i == 0 ? Stacks / 2 : 0;
|
||||
int end = i == 0 ? Stacks : Stacks / 2;
|
||||
|
||||
for(int j = start; j <= end; ++j)
|
||||
{
|
||||
var stackAngle = MathF.PI / 2 - j * StackStep;
|
||||
var xy = radius[i] * MathF.Cos(stackAngle);
|
||||
var z = radius[i] * MathF.Sin(stackAngle) + h;
|
||||
|
||||
for(int k = 0; k <= Slices; ++k)
|
||||
{
|
||||
var sectorAngle = k * SectorStep;
|
||||
var x = xy * MathF.Cos(sectorAngle);
|
||||
var y = xy * MathF.Sin(sectorAngle);
|
||||
_vertexData[vLength++] = new FVector(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var indices = new List<int>();
|
||||
AddIndicesForSlices(indices, ref k2);
|
||||
indices.AddRange(new[] {0, k2, k2, k2 - half, half, half});
|
||||
AddIndicesForStacks(indices);
|
||||
half /= 2;
|
||||
indices.AddRange(new[] {half, k2 - half * 3, k2 - half * 3, half * 3, k2 - half, k2 - half});
|
||||
AddIndicesForStacks(indices, Stacks / 2);
|
||||
_indexData = indices.ToArray();
|
||||
|
||||
_transform = new Transform
|
||||
{
|
||||
Position = center * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = rotator.Quaternion()
|
||||
};
|
||||
}
|
||||
|
||||
private void AddIndicesForSlices(List<int> indices, ref int k2)
|
||||
{
|
||||
for(int k1 = 0; k1 < Slices; ++k1, ++k2)
|
||||
{
|
||||
indices.AddRange(new[] {k1, k1 + 1, k1 + 1, k2, k2 + 1, k2 + 1});
|
||||
}
|
||||
}
|
||||
|
||||
private void AddIndicesForStacks(List<int> indices, int start = 0)
|
||||
{
|
||||
for (int k1 = start; k1 < Stacks * Slices + Slices; k1 += Slices + 1)
|
||||
{
|
||||
if (k1 == Stacks / 2 * (Slices + 1) + start) continue;
|
||||
indices.AddRange(new[] {k1, k1 + Slices + 1, k1 + Slices + 1, k1 + Slices / 2, k1 + Slices / 2 + Slices + 1, k1 + Slices / 2 + Slices + 1});
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
_ebo = new BufferObject<int>(_indexData, BufferTarget.ElementArrayBuffer);
|
||||
_vbo = new BufferObject<FVector>(_vertexData, BufferTarget.ArrayBuffer);
|
||||
_vao = new VertexArrayObject<FVector, int>(_vbo, _ebo);
|
||||
|
||||
_vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 1, 0);
|
||||
_vao.Unbind();
|
||||
}
|
||||
|
||||
public void Render(Shader shader, Matrix4x4 boneMatrix)
|
||||
{
|
||||
shader.SetUniform("uCollisionMatrix", _transform.Matrix * boneMatrix);
|
||||
|
||||
_vao.Bind();
|
||||
if (_indexData.Length > 0)
|
||||
{
|
||||
GL.DrawElements(PrimitiveType.Triangles, _ebo.Size, DrawElementsType.UnsignedInt, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.DrawArrays(PrimitiveType.Points, 0, _vbo.Size);
|
||||
}
|
||||
_vao.Unbind();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_ebo?.Dispose();
|
||||
_vbo?.Dispose();
|
||||
_vao?.Dispose();
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,12 +16,11 @@ public class Section
|
|||
|
||||
public Section(int index, int facesCount, int firstFaceIndex)
|
||||
{
|
||||
MaterialIndex = index;
|
||||
MaterialIndex = Math.Max(0, index);
|
||||
FacesCount = facesCount;
|
||||
FirstFaceIndex = firstFaceIndex;
|
||||
FirstFaceIndexPtr = new IntPtr(FirstFaceIndex * sizeof(uint));
|
||||
Color = Constants.COLOR_PALETTE[index % Constants.PALETTE_LENGTH];
|
||||
Show = true;
|
||||
Color = Constants.COLOR_PALETTE[MaterialIndex % Constants.PALETTE_LENGTH];
|
||||
}
|
||||
|
||||
public void SetupMaterial(Material material)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.PhysicsEngine;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Views.Snooper.Animations;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
|
|
@ -44,6 +46,34 @@ public class SkeletalModel : UModel
|
|||
Sockets.Add(new Socket(socket));
|
||||
}
|
||||
|
||||
if (export.PhysicsAsset.TryLoad(out UPhysicsAsset physicsAsset))
|
||||
{
|
||||
foreach (var skeletalBodySetup in physicsAsset.SkeletalBodySetups)
|
||||
{
|
||||
if (!skeletalBodySetup.TryLoad(out USkeletalBodySetup bodySetup) || bodySetup.AggGeom == null) continue;
|
||||
foreach (var convexElem in bodySetup.AggGeom.ConvexElems)
|
||||
{
|
||||
Collisions.Add(new Collision(convexElem, bodySetup.BoneName));
|
||||
}
|
||||
foreach (var sphereElem in bodySetup.AggGeom.SphereElems)
|
||||
{
|
||||
Collisions.Add(new Collision(sphereElem, bodySetup.BoneName));
|
||||
}
|
||||
foreach (var boxElem in bodySetup.AggGeom.BoxElems)
|
||||
{
|
||||
Collisions.Add(new Collision(boxElem, bodySetup.BoneName));
|
||||
}
|
||||
foreach (var sphylElem in bodySetup.AggGeom.SphylElems)
|
||||
{
|
||||
Collisions.Add(new Collision(sphylElem, bodySetup.BoneName));
|
||||
}
|
||||
foreach (var taperedCapsuleElem in bodySetup.AggGeom.TaperedCapsuleElems)
|
||||
{
|
||||
Collisions.Add(new Collision(taperedCapsuleElem, bodySetup.BoneName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Morphs = new List<Morph>();
|
||||
for (var i = 0; i < export.MorphTargets.Length; i++)
|
||||
{
|
||||
|
|
@ -95,6 +125,26 @@ public class SkeletalModel : UModel
|
|||
Vao.Unbind();
|
||||
}
|
||||
|
||||
public override void RenderCollision(Shader shader)
|
||||
{
|
||||
base.RenderCollision(shader);
|
||||
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
|
||||
foreach (var collision in Collisions)
|
||||
{
|
||||
var boneMatrix = Matrix4x4.Identity;
|
||||
if (Skeleton.BonesByLoweredName.TryGetValue(collision.LoweredBoneName, out var bone))
|
||||
boneMatrix = Skeleton.GetBoneMatrix(bone);
|
||||
|
||||
collision.Render(shader, boneMatrix);
|
||||
}
|
||||
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
}
|
||||
|
||||
public void Render(Shader shader)
|
||||
{
|
||||
shader.SetUniform("uMorphTime", MorphTime);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Objects.PhysicsEngine;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
|
|
@ -52,6 +55,30 @@ public class StaticModel : UModel
|
|||
public StaticModel(UStaticMesh export, CStaticMesh staticMesh, Transform transform = null)
|
||||
: base(export, staticMesh.LODs[LodLevel], export.Materials, staticMesh.LODs[LodLevel].Verts, staticMesh.LODs.Count, transform)
|
||||
{
|
||||
if (export.BodySetup.TryLoad(out UBodySetup bodySetup) && bodySetup.AggGeom != null)
|
||||
{
|
||||
foreach (var convexElem in bodySetup.AggGeom.ConvexElems)
|
||||
{
|
||||
Collisions.Add(new Collision(convexElem));
|
||||
}
|
||||
foreach (var sphereElem in bodySetup.AggGeom.SphereElems)
|
||||
{
|
||||
Collisions.Add(new Collision(sphereElem));
|
||||
}
|
||||
foreach (var boxElem in bodySetup.AggGeom.BoxElems)
|
||||
{
|
||||
Collisions.Add(new Collision(boxElem));
|
||||
}
|
||||
foreach (var sphylElem in bodySetup.AggGeom.SphylElems)
|
||||
{
|
||||
Collisions.Add(new Collision(sphylElem));
|
||||
}
|
||||
foreach (var taperedCapsuleElem in bodySetup.AggGeom.TaperedCapsuleElems)
|
||||
{
|
||||
Collisions.Add(new Collision(taperedCapsuleElem));
|
||||
}
|
||||
}
|
||||
|
||||
Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
|
||||
for (int i = 0; i < export.Sockets.Length; i++)
|
||||
{
|
||||
|
|
@ -59,4 +86,18 @@ public class StaticModel : UModel
|
|||
Sockets.Add(new Socket(socket));
|
||||
}
|
||||
}
|
||||
|
||||
public override void RenderCollision(Shader shader)
|
||||
{
|
||||
base.RenderCollision(shader);
|
||||
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
|
||||
foreach (var collision in Collisions)
|
||||
{
|
||||
collision.Render(shader, Matrix4x4.Identity);
|
||||
}
|
||||
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace FModel.Views.Snooper.Models;
|
|||
public class VertexAttribute
|
||||
{
|
||||
public int Size;
|
||||
public VertexAttribPointerType Type;
|
||||
public bool Enabled;
|
||||
}
|
||||
|
||||
|
|
@ -28,18 +29,18 @@ public abstract class UModel : IRenderableModel
|
|||
protected const int LodLevel = 0;
|
||||
|
||||
private readonly UObject _export;
|
||||
private readonly List<VertexAttribute> _vertexAttributes = new()
|
||||
{
|
||||
new VertexAttribute { Size = 1, Enabled = false }, // VertexIndex
|
||||
new VertexAttribute { Size = 3, Enabled = true }, // Position
|
||||
new VertexAttribute { Size = 3, Enabled = false }, // Normal
|
||||
new VertexAttribute { Size = 3, Enabled = false }, // Tangent
|
||||
new VertexAttribute { Size = 2, Enabled = false }, // UV
|
||||
new VertexAttribute { Size = 1, Enabled = false }, // TextureLayer
|
||||
new VertexAttribute { Size = 4, Enabled = false }, // Colors
|
||||
new VertexAttribute { Size = 4, Enabled = false }, // BoneIds
|
||||
new VertexAttribute { Size = 4, Enabled = false } // BoneWeights
|
||||
};
|
||||
private readonly List<VertexAttribute> _vertexAttributes =
|
||||
[
|
||||
new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Int, Enabled = false }, // VertexIndex
|
||||
new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = true }, // Position
|
||||
new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Normal
|
||||
new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Tangent
|
||||
new VertexAttribute { Size = 2, Type = VertexAttribPointerType.Float, Enabled = false }, // UV
|
||||
new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // TextureLayer
|
||||
new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // Colors
|
||||
new VertexAttribute { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false }, // BoneIds
|
||||
new VertexAttribute { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false } // BoneWeights
|
||||
];
|
||||
|
||||
public int Handle { get; set; }
|
||||
public BufferObject<uint> Ebo { get; set; }
|
||||
|
|
@ -59,6 +60,7 @@ public abstract class UModel : IRenderableModel
|
|||
|
||||
public FBox Box;
|
||||
public readonly List<Socket> Sockets;
|
||||
public readonly List<Collision> Collisions;
|
||||
public Material[] Materials;
|
||||
public bool IsTwoSided;
|
||||
public bool IsProp;
|
||||
|
|
@ -66,12 +68,14 @@ public abstract class UModel : IRenderableModel
|
|||
public int VertexSize => _vertexAttributes.Where(x => x.Enabled).Sum(x => x.Size);
|
||||
public bool HasVertexColors => _vertexAttributes[(int) EAttribute.Colors].Enabled;
|
||||
public bool HasSockets => Sockets.Count > 0;
|
||||
public bool HasCollisions => Collisions.Count > 0;
|
||||
public int TransformsCount => Transforms.Count;
|
||||
|
||||
public bool IsSetup { get; set; }
|
||||
public bool IsVisible { get; set; }
|
||||
public bool IsSelected { get; set; }
|
||||
public bool ShowWireframe { get; set; }
|
||||
public bool ShowCollisions { get; set; }
|
||||
public int SelectedInstance;
|
||||
|
||||
protected UModel()
|
||||
|
|
@ -81,6 +85,7 @@ public abstract class UModel : IRenderableModel
|
|||
|
||||
Box = new FBox(new FVector(-2f), new FVector(2f));
|
||||
Sockets = new List<Socket>();
|
||||
Collisions = new List<Collision>();
|
||||
Transforms = new List<Transform>();
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +99,7 @@ public abstract class UModel : IRenderableModel
|
|||
|
||||
Box = new FBox(new FVector(-2f), new FVector(2f));
|
||||
Sockets = new List<Socket>();
|
||||
Collisions = new List<Collision>();
|
||||
Transforms = new List<Transform>();
|
||||
Attachments = new Attachment(Name);
|
||||
|
||||
|
|
@ -145,28 +151,24 @@ public abstract class UModel : IRenderableModel
|
|||
Vertices[baseIndex + count++] = vert.Tangent.Y;
|
||||
Vertices[baseIndex + count++] = vert.UV.U;
|
||||
Vertices[baseIndex + count++] = vert.UV.V;
|
||||
Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUV.Value[0][i].U : .5f;
|
||||
Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUV.Value[0][i].U - 1 : .5f;
|
||||
|
||||
if (HasVertexColors)
|
||||
{
|
||||
var color = lod.VertexColors[i];
|
||||
Vertices[baseIndex + count++] = color.R;
|
||||
Vertices[baseIndex + count++] = color.G;
|
||||
Vertices[baseIndex + count++] = color.B;
|
||||
Vertices[baseIndex + count++] = color.A;
|
||||
Vertices[baseIndex + count++] = lod.VertexColors[i].ToPackedARGB();
|
||||
}
|
||||
|
||||
if (vert is CSkelMeshVertex skelVert)
|
||||
{
|
||||
var weightsHash = skelVert.UnpackWeights();
|
||||
Vertices[baseIndex + count++] = skelVert.Bone[0];
|
||||
Vertices[baseIndex + count++] = skelVert.Bone[1];
|
||||
Vertices[baseIndex + count++] = skelVert.Bone[2];
|
||||
Vertices[baseIndex + count++] = skelVert.Bone[3];
|
||||
Vertices[baseIndex + count++] = weightsHash[0];
|
||||
Vertices[baseIndex + count++] = weightsHash[1];
|
||||
Vertices[baseIndex + count++] = weightsHash[2];
|
||||
Vertices[baseIndex + count++] = weightsHash[3];
|
||||
int max = skelVert.Influences.Count;
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
var boneID = j < max ? skelVert.Influences[j].Bone : (short) 0;
|
||||
var weight = j < max ? skelVert.Influences[j].RawWeight : (byte) 0;
|
||||
|
||||
// Pack bone ID and weight
|
||||
Vertices[baseIndex + count++] = (boneID << 16) | weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -197,8 +199,9 @@ public abstract class UModel : IRenderableModel
|
|||
|
||||
if (i != 5 || !broken)
|
||||
{
|
||||
Vao.VertexAttributePointer((uint) i, attribute.Size, i == 0 ? VertexAttribPointerType.Int : VertexAttribPointerType.Float, VertexSize, offset);
|
||||
Vao.VertexAttributePointer((uint) i, attribute.Size, attribute.Type, VertexSize, offset);
|
||||
}
|
||||
|
||||
offset += attribute.Size;
|
||||
}
|
||||
|
||||
|
|
@ -211,6 +214,11 @@ public abstract class UModel : IRenderableModel
|
|||
Materials[i].Setup(options, broken ? 1 : UvCount);
|
||||
}
|
||||
|
||||
foreach (var collision in Collisions)
|
||||
{
|
||||
collision.Setup();
|
||||
}
|
||||
|
||||
if (options.Models.Count == 1 && Sections.All(x => !x.Show))
|
||||
{
|
||||
IsVisible = true;
|
||||
|
|
@ -242,6 +250,7 @@ public abstract class UModel : IRenderableModel
|
|||
if (!outline)
|
||||
{
|
||||
shader.SetUniform("uUvCount", UvCount);
|
||||
shader.SetUniform("uOpacity", ShowCollisions && IsSelected ? 0.75f : 1f);
|
||||
shader.SetUniform("uHasVertexColors", HasVertexColors);
|
||||
}
|
||||
|
||||
|
|
@ -296,6 +305,12 @@ public abstract class UModel : IRenderableModel
|
|||
if (IsTwoSided) GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
|
||||
public virtual void RenderCollision(Shader shader)
|
||||
{
|
||||
shader.SetUniform("uInstanceMatrix", GetTransform().Matrix);
|
||||
shader.SetUniform("uScaleDown", Constants.SCALE_DOWN_RATIO);
|
||||
}
|
||||
|
||||
public void Update(Options options)
|
||||
{
|
||||
MatrixVbo.Bind();
|
||||
|
|
@ -357,18 +372,7 @@ public abstract class UModel : IRenderableModel
|
|||
|
||||
public bool Save(out string label, out string savedFilePath)
|
||||
{
|
||||
var exportOptions = new ExporterOptions
|
||||
{
|
||||
LodFormat = UserSettings.Default.LodExportFormat,
|
||||
MeshFormat = UserSettings.Default.MeshExportFormat,
|
||||
MaterialFormat = UserSettings.Default.MaterialExportFormat,
|
||||
TextureFormat = UserSettings.Default.TextureExportFormat,
|
||||
SocketFormat = UserSettings.Default.SocketExportFormat,
|
||||
Platform = UserSettings.Default.CurrentDir.TexturePlatform,
|
||||
ExportMorphTargets = UserSettings.Default.SaveMorphTargets,
|
||||
ExportMaterials = UserSettings.Default.SaveEmbeddedMaterials
|
||||
};
|
||||
var toSave = new Exporter(_export, exportOptions);
|
||||
var toSave = new Exporter(_export, UserSettings.Default.ExportOptions);
|
||||
return toSave.TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out label, out savedFilePath);
|
||||
}
|
||||
|
||||
|
|
@ -383,6 +387,11 @@ public abstract class UModel : IRenderableModel
|
|||
socket?.Dispose();
|
||||
}
|
||||
Sockets.Clear();
|
||||
foreach (var collision in Collisions)
|
||||
{
|
||||
collision?.Dispose();
|
||||
}
|
||||
Collisions.Clear();
|
||||
|
||||
GL.DeleteProgram(Handle);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@ public class Options
|
|||
Icons = new Dictionary<string, Texture>
|
||||
{
|
||||
["material"] = new ("materialicon"),
|
||||
["square"] = new ("square"),
|
||||
["square_off"] = new ("square_off"),
|
||||
["cube"] = new ("cube"),
|
||||
["cube_off"] = new ("cube_off"),
|
||||
["light"] = new ("light"),
|
||||
["light_off"] = new ("light_off"),
|
||||
["noimage"] = new ("T_Placeholder_Item_Image"),
|
||||
["checker"] = new ("checker"),
|
||||
["pointlight"] = new ("pointlight"),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
|
|
@ -8,6 +9,7 @@ using CUE4Parse_Conversion.Meshes;
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Assets.Exports.Component.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.GeometryCollection;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
|
|
@ -46,6 +48,7 @@ public class Renderer : IDisposable
|
|||
private Shader _outline;
|
||||
private Shader _light;
|
||||
private Shader _bone;
|
||||
private Shader _collision;
|
||||
private bool _saveCameraMode;
|
||||
|
||||
public bool ShowSkybox;
|
||||
|
|
@ -77,8 +80,8 @@ public class Renderer : IDisposable
|
|||
public void Load(CancellationToken cancellationToken, UObject export)
|
||||
{
|
||||
ShowLights = false;
|
||||
_saveCameraMode = export is not UWorld;
|
||||
CameraOp.Mode = _saveCameraMode ? UserSettings.Default.CameraMode : Camera.WorldMode.FlyCam;
|
||||
Color = VertexColor.Default;
|
||||
_saveCameraMode = export is not UWorld and not UBlueprintGeneratedClass;
|
||||
switch (export)
|
||||
{
|
||||
case UStaticMesh st:
|
||||
|
|
@ -96,7 +99,12 @@ public class Renderer : IDisposable
|
|||
case UWorld wd:
|
||||
LoadWorld(cancellationToken, wd, Transform.Identity);
|
||||
break;
|
||||
case UBlueprintGeneratedClass bp:
|
||||
LoadJunoWorld(cancellationToken, bp, Transform.Identity);
|
||||
Color = VertexColor.Colors;
|
||||
break;
|
||||
}
|
||||
CameraOp.Mode = _saveCameraMode ? UserSettings.Default.CameraMode : Camera.WorldMode.FlyCam;
|
||||
SetupCamera();
|
||||
}
|
||||
|
||||
|
|
@ -227,6 +235,7 @@ public class Renderer : IDisposable
|
|||
_outline = new Shader("outline");
|
||||
_light = new Shader("light");
|
||||
_bone = new Shader("bone");
|
||||
_collision = new Shader("collision", "bone");
|
||||
|
||||
Picking.Setup();
|
||||
Options.SetupModelsAndLights();
|
||||
|
|
@ -272,6 +281,11 @@ public class Renderer : IDisposable
|
|||
_bone.Render(viewMatrix, projMatrix);
|
||||
skeletalModel.RenderBones(_bone);
|
||||
}
|
||||
else if (selected.ShowCollisions)
|
||||
{
|
||||
_collision.Render(viewMatrix, projMatrix);
|
||||
selected.RenderCollision(_collision);
|
||||
}
|
||||
|
||||
_outline.Render(viewMatrix, CameraOp.Position, projMatrix);
|
||||
selected.Render(_outline, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null, true);
|
||||
|
|
@ -422,6 +436,49 @@ public class Renderer : IDisposable
|
|||
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}");
|
||||
}
|
||||
|
||||
private void LoadJunoWorld(CancellationToken cancellationToken, UBlueprintGeneratedClass original, Transform transform)
|
||||
{
|
||||
CameraOp.Setup(new FBox(FVector.ZeroVector, new FVector(0, 10, 10)));
|
||||
|
||||
var length = 0;
|
||||
FPackageIndex[] allNodes = [];
|
||||
IPropertyHolder[] records = [];
|
||||
if (original.TryGetValue(out FPackageIndex simpleConstructionScript, "SimpleConstructionScript") &&
|
||||
simpleConstructionScript.TryLoad(out var scs) && scs.TryGetValue(out allNodes, "AllNodes"))
|
||||
length = allNodes.Length;
|
||||
else if (original.TryGetValue(out FPackageIndex inheritableComponentHandler, "InheritableComponentHandler") &&
|
||||
inheritableComponentHandler.TryLoad(out var ich) && ich.TryGetValue(out records, "Records"))
|
||||
length = records.Length;
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
IPropertyHolder actor;
|
||||
if (allNodes is {Length: > 0} && allNodes[i].TryLoad(out UObject node))
|
||||
{
|
||||
actor = node;
|
||||
}
|
||||
else if (records is {Length: > 0})
|
||||
{
|
||||
actor = records[i];
|
||||
}
|
||||
else continue;
|
||||
|
||||
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {i}/{length}");
|
||||
WorldMesh(actor, transform, true);
|
||||
}
|
||||
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}");
|
||||
|
||||
if (Options.Models.Count == 1)
|
||||
{
|
||||
var (guid, model) = Options.Models.First();
|
||||
Options.SelectModel(guid);
|
||||
CameraOp.Setup(model.Box);
|
||||
_saveCameraMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void WorldCamera(UObject actor)
|
||||
{
|
||||
if (actor.ExportType != "LevelBounds" || !actor.TryGetValue(out FPackageIndex boxComponent, "BoxComponent") ||
|
||||
|
|
@ -452,36 +509,79 @@ public class Renderer : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private void WorldMesh(UObject actor, Transform transform)
|
||||
private void WorldMesh(IPropertyHolder actor, Transform transform, bool forceShow = false)
|
||||
{
|
||||
if (!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "StaticMesh", "Mesh", "LightMesh") ||
|
||||
!staticMeshComponent.TryLoad(out UStaticMeshComponent staticMeshComp) ||
|
||||
!staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) || m.Materials.Length < 1)
|
||||
return;
|
||||
|
||||
var guid = m.LightingGuid;
|
||||
var t = new Transform
|
||||
if (actor.TryGetValue(out FPackageIndex[] instanceComponents, "InstanceComponents"))
|
||||
{
|
||||
Relation = transform.Matrix,
|
||||
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion(),
|
||||
Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector)
|
||||
};
|
||||
foreach (var component in instanceComponents)
|
||||
{
|
||||
if (!component.TryLoad(out UInstancedStaticMeshComponent staticMeshComp) ||
|
||||
!staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) || m.Materials.Length < 1)
|
||||
continue;
|
||||
|
||||
if (staticMeshComp.PerInstanceSMData is { Length: > 0 })
|
||||
{
|
||||
|
||||
var relation = CalculateTransform(staticMeshComp, transform);
|
||||
foreach (var perInstanceData in staticMeshComp.PerInstanceSMData)
|
||||
{
|
||||
ProcessMesh(actor, staticMeshComp, m, new Transform
|
||||
{
|
||||
Relation = relation.Matrix,
|
||||
Position = perInstanceData.TransformData.Translation * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = perInstanceData.TransformData.Rotation,
|
||||
Scale = perInstanceData.TransformData.Scale3D
|
||||
});
|
||||
}
|
||||
}
|
||||
else ProcessMesh(actor, staticMeshComp, m, CalculateTransform(staticMeshComp, transform));
|
||||
}
|
||||
}
|
||||
else if (actor.TryGetValue(out FPackageIndex componentTemplate, "ComponentTemplate") &&
|
||||
componentTemplate.TryLoad(out UObject compTemplate))
|
||||
{
|
||||
UGeometryCollection geometryCollection = null;
|
||||
if (!compTemplate.TryGetValue(out UStaticMesh m, "StaticMesh") &&
|
||||
compTemplate.TryGetValue(out FPackageIndex restCollection, "RestCollection") &&
|
||||
restCollection.TryLoad(out geometryCollection) && geometryCollection.RootProxyData is { ProxyMeshes.Length: > 0 } rootProxyData)
|
||||
{
|
||||
rootProxyData.ProxyMeshes[0].TryLoad(out m);
|
||||
}
|
||||
|
||||
if (m is { Materials.Length: > 0 })
|
||||
{
|
||||
OverrideJunoVertexColors(m, geometryCollection);
|
||||
ProcessMesh(actor, compTemplate, m, CalculateTransform(compTemplate, transform), forceShow);
|
||||
}
|
||||
}
|
||||
else if (actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "ComponentTemplate", "StaticMesh", "Mesh", "LightMesh") &&
|
||||
staticMeshComponent.TryLoad(out UStaticMeshComponent staticMeshComp) &&
|
||||
staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) && m.Materials.Length > 0)
|
||||
{
|
||||
ProcessMesh(actor, staticMeshComp, m, CalculateTransform(staticMeshComp, transform));
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessMesh(IPropertyHolder actor, UStaticMeshComponent staticMeshComp, UStaticMesh m, Transform transform)
|
||||
{
|
||||
OverrideVertexColors(staticMeshComp, m);
|
||||
ProcessMesh(actor, staticMeshComp, m, transform, false);
|
||||
}
|
||||
private void ProcessMesh(IPropertyHolder actor, UObject staticMeshComp, UStaticMesh m, Transform transform, bool forceShow)
|
||||
{
|
||||
var guid = m.LightingGuid;
|
||||
if (Options.TryGetModel(guid, out var model))
|
||||
{
|
||||
model.AddInstance(t);
|
||||
model.AddInstance(transform);
|
||||
}
|
||||
else if (m.TryConvert(out var mesh))
|
||||
{
|
||||
model = new StaticModel(m, mesh, t);
|
||||
model = new StaticModel(m, mesh, transform);
|
||||
model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided));
|
||||
|
||||
if (actor.TryGetValue(out FPackageIndex baseMaterial, "BaseMaterial") &&
|
||||
actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
|
||||
if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
|
||||
{
|
||||
var material = model.Materials.FirstOrDefault(x => x.Name == baseMaterial.Name);
|
||||
var material = model.Materials.FirstOrDefault();
|
||||
if (material is { IsUsed: true })
|
||||
{
|
||||
for (int j = 0; j < textureData.Length; j++)
|
||||
|
|
@ -517,28 +617,100 @@ public class Renderer : IDisposable
|
|||
|
||||
if (staticMeshComp.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials"))
|
||||
{
|
||||
var max = model.Sections.Length - 1;
|
||||
for (var j = 0; j < overrideMaterials.Length; j++)
|
||||
for (var j = 0; j < overrideMaterials.Length && j < model.Sections.Length; j++)
|
||||
{
|
||||
if (j > max) break;
|
||||
if (!model.Materials[model.Sections[j].MaterialIndex].IsUsed ||
|
||||
overrideMaterials[j].Load() is not UMaterialInterface unrealMaterial) continue;
|
||||
model.Materials[model.Sections[j].MaterialIndex].SwapMaterial(unrealMaterial);
|
||||
var matIndex = model.Sections[j].MaterialIndex;
|
||||
if (matIndex < 0 || matIndex >= model.Materials.Length || matIndex >= overrideMaterials.Length ||
|
||||
overrideMaterials[matIndex].Load() is not UMaterialInterface unrealMaterial) continue;
|
||||
|
||||
model.Materials[matIndex].SwapMaterial(unrealMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
if (forceShow)
|
||||
{
|
||||
foreach (var section in model.Sections)
|
||||
{
|
||||
section.Show = true;
|
||||
}
|
||||
}
|
||||
Options.Models[guid] = model;
|
||||
}
|
||||
|
||||
if (actor.TryGetValue(out FPackageIndex treasureLight, "PointLight", "TreasureLight") &&
|
||||
treasureLight.TryLoad(out var pl1) && pl1.Template.TryLoad(out var pl2))
|
||||
{
|
||||
Options.Lights.Add(new PointLight(guid, Options.Icons["pointlight"], pl1, pl2, t));
|
||||
Options.Lights.Add(new PointLight(guid, Options.Icons["pointlight"], pl1, pl2, transform));
|
||||
}
|
||||
if (actor.TryGetValue(out FPackageIndex spotLight, "SpotLight") &&
|
||||
spotLight.TryLoad(out var sl1) && sl1.Template.TryLoad(out var sl2))
|
||||
{
|
||||
Options.Lights.Add(new SpotLight(guid, Options.Icons["spotlight"], sl1, sl2, t));
|
||||
Options.Lights.Add(new SpotLight(guid, Options.Icons["spotlight"], sl1, sl2, transform));
|
||||
}
|
||||
}
|
||||
|
||||
private Transform CalculateTransform(IPropertyHolder staticMeshComp, Transform relation)
|
||||
{
|
||||
return new Transform
|
||||
{
|
||||
Relation = relation.Matrix,
|
||||
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion(),
|
||||
Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector)
|
||||
};
|
||||
}
|
||||
|
||||
private void OverrideJunoVertexColors(UStaticMesh staticMesh, UGeometryCollection geometryCollection = null)
|
||||
{
|
||||
if (staticMesh.RenderData is not { LODs.Length: > 0 } || staticMesh.RenderData.LODs[0].ColorVertexBuffer == null)
|
||||
return;
|
||||
|
||||
var dico = new Dictionary<byte, FColor>();
|
||||
if (geometryCollection?.Materials is not { Length: > 0 })
|
||||
{
|
||||
var distinctReds = new HashSet<byte>();
|
||||
for (int i = 0; i < staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data.Length; i++)
|
||||
{
|
||||
ref var vertexColor = ref staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data[i];
|
||||
var indexAsByte = vertexColor.R;
|
||||
if (indexAsByte == 255) indexAsByte = vertexColor.A;
|
||||
distinctReds.Add(indexAsByte);
|
||||
}
|
||||
|
||||
foreach (var indexAsByte in distinctReds)
|
||||
{
|
||||
var path = string.Concat("/JunoAtomAssets/Materials/MI_LegoStandard_", indexAsByte, ".MI_LegoStandard_", indexAsByte);
|
||||
if (!Utils.TryLoadObject(path, out UMaterialInterface unrealMaterial))
|
||||
continue;
|
||||
|
||||
var parameters = new CMaterialParams2();
|
||||
unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer);
|
||||
|
||||
if (!parameters.TryGetLinearColor(out var color, "Color"))
|
||||
color = FLinearColor.Gray;
|
||||
|
||||
dico[indexAsByte] = color.ToFColor(true);
|
||||
}
|
||||
}
|
||||
else foreach (var material in geometryCollection.Materials)
|
||||
{
|
||||
if (!material.TryLoad(out UMaterialInterface unrealMaterial)) continue;
|
||||
|
||||
var parameters = new CMaterialParams2();
|
||||
unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer);
|
||||
|
||||
if (!byte.TryParse(material.Name.SubstringAfterLast("_"), out var indexAsByte))
|
||||
indexAsByte = byte.MaxValue;
|
||||
if (!parameters.TryGetLinearColor(out var color, "Color"))
|
||||
color = FLinearColor.Gray;
|
||||
|
||||
dico[indexAsByte] = color.ToFColor(true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data.Length; i++)
|
||||
{
|
||||
ref var vertexColor = ref staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data[i];
|
||||
vertexColor = dico.TryGetValue(vertexColor.R, out var color) ? color : FColor.Gray;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -608,6 +780,7 @@ public class Renderer : IDisposable
|
|||
_outline?.Dispose();
|
||||
_light?.Dispose();
|
||||
_bone?.Dispose();
|
||||
_collision?.Dispose();
|
||||
Picking?.Dispose();
|
||||
Options?.Dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,12 +76,12 @@ public class Material : IDisposable
|
|||
|
||||
if (uvCount < 1 || Parameters.IsNull)
|
||||
{
|
||||
Diffuse = new[] { options.Icons["checker"] };
|
||||
Normals = new[] { new Texture(new FLinearColor(0.498f, 0.498f, 0.996f, 1f)) };
|
||||
SpecularMasks = new [] { new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f)) };
|
||||
Diffuse = [new Texture(FLinearColor.Gray)];
|
||||
Normals = [new Texture(new FLinearColor(0.5f, 0.5f, 1f, 1f))];
|
||||
SpecularMasks = [new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f))];
|
||||
Emissive = new Texture[1];
|
||||
DiffuseColor = new[] { Vector4.One };
|
||||
EmissiveColor = new[] { Vector4.One };
|
||||
DiffuseColor = [Vector4.One];
|
||||
EmissiveColor = [Vector4.One];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -102,7 +102,7 @@ public class Material : IDisposable
|
|||
!original.Name.Equals("T_BlackMask") && options.TryGetTexture(original, false, out var transformed))
|
||||
{
|
||||
HasAo = true;
|
||||
Ao = new AoParams { Texture = transformed, AmbientOcclusion = 0.7f };
|
||||
Ao = new AoParams { Texture = transformed };
|
||||
if (Parameters.TryGetLinearColor(out var l, "Skin Boost Color And Exponent"))
|
||||
{
|
||||
Ao.HasColorBoost = true;
|
||||
|
|
@ -242,7 +242,6 @@ public class Material : IDisposable
|
|||
shader.SetUniform("uParameters.Ao.HasColorBoost", Ao.HasColorBoost);
|
||||
shader.SetUniform("uParameters.Ao.ColorBoost.Color", Ao.ColorBoost.Color);
|
||||
shader.SetUniform("uParameters.Ao.ColorBoost.Exponent", Ao.ColorBoost.Exponent);
|
||||
shader.SetUniform("uParameters.Ao.AmbientOcclusion", Ao.AmbientOcclusion);
|
||||
shader.SetUniform("uParameters.HasAo", HasAo);
|
||||
|
||||
shader.SetUniform("uParameters.EmissiveRegion", EmissiveRegion);
|
||||
|
|
@ -269,18 +268,13 @@ public class Material : IDisposable
|
|||
ImGui.DragFloat("", ref EmissiveMult, _step, _zero, _infinite, _mult, _clamp);
|
||||
ImGui.PopID();
|
||||
|
||||
if (HasAo)
|
||||
if (HasAo && Ao.HasColorBoost)
|
||||
{
|
||||
SnimGui.Layout("Ambient Occlusion");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref Ao.AmbientOcclusion, _step, _zero, 1.0f, _mult, _clamp);ImGui.PopID();
|
||||
if (Ao.HasColorBoost)
|
||||
{
|
||||
SnimGui.Layout("Color Boost");ImGui.PushID(id++);
|
||||
ImGui.ColorEdit3("", ref Ao.ColorBoost.Color);ImGui.PopID();
|
||||
SnimGui.Layout("Color Boost Exponent");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref Ao.ColorBoost.Exponent, _step, _zero, _infinite, _mult, _clamp);
|
||||
ImGui.PopID();
|
||||
}
|
||||
SnimGui.Layout("Color Boost");ImGui.PushID(id++);
|
||||
ImGui.ColorEdit3("", ref Ao.ColorBoost.Color);ImGui.PopID();
|
||||
SnimGui.Layout("Color Boost Exponent");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref Ao.ColorBoost.Exponent, _step, _zero, _infinite, _mult, _clamp);
|
||||
ImGui.PopID();
|
||||
}
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
|
@ -329,12 +323,12 @@ public class Material : IDisposable
|
|||
|
||||
switch (SelectedTexture)
|
||||
{
|
||||
case 0:
|
||||
case 0 when DiffuseColor.Length > 0:
|
||||
SnimGui.Layout("Color");ImGui.PushID(3);
|
||||
ImGui.ColorEdit4("", ref DiffuseColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha);
|
||||
ImGui.PopID();
|
||||
break;
|
||||
case 4:
|
||||
case 4 when EmissiveColor.Length > 0:
|
||||
SnimGui.Layout("Color");ImGui.PushID(3);
|
||||
ImGui.ColorEdit4("", ref EmissiveColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha);
|
||||
ImGui.PopID();SnimGui.Layout("Region");ImGui.PushID(4);
|
||||
|
|
@ -357,11 +351,11 @@ public class Material : IDisposable
|
|||
{
|
||||
return SelectedTexture switch
|
||||
{
|
||||
0 => Diffuse[SelectedChannel],
|
||||
1 => Normals[SelectedChannel],
|
||||
2 => SpecularMasks[SelectedChannel],
|
||||
0 when Diffuse.Length > 0 => Diffuse[SelectedChannel],
|
||||
1 when Normals.Length > 0 => Normals[SelectedChannel],
|
||||
2 when SpecularMasks.Length > 0 => SpecularMasks[SelectedChannel],
|
||||
3 => Ao.Texture,
|
||||
4 => Emissive[SelectedChannel],
|
||||
4 when Emissive.Length > 0 => Emissive[SelectedChannel],
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
|
@ -401,7 +395,6 @@ public class Material : IDisposable
|
|||
public struct AoParams
|
||||
{
|
||||
public Texture Texture;
|
||||
public float AmbientOcclusion;
|
||||
|
||||
public Boost ColorBoost;
|
||||
public bool HasColorBoost;
|
||||
|
|
|
|||
|
|
@ -10,28 +10,38 @@ namespace FModel.Views.Snooper.Shading;
|
|||
public class Shader : IDisposable
|
||||
{
|
||||
private readonly int _handle;
|
||||
private readonly int _vHandle;
|
||||
private readonly int _fHandle;
|
||||
private readonly Dictionary<string, int> _uniformsLocation = new ();
|
||||
|
||||
public Shader() : this("default") {}
|
||||
|
||||
public Shader(string name)
|
||||
public Shader(string name1, string name2 = null)
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
_vHandle = LoadShader(ShaderType.VertexShader, $"{name1}.vert");
|
||||
_fHandle = LoadShader(ShaderType.FragmentShader, $"{name2 ?? name1}.frag");
|
||||
Attach();
|
||||
}
|
||||
|
||||
var v = LoadShader(ShaderType.VertexShader, $"{name}.vert");
|
||||
var f = LoadShader(ShaderType.FragmentShader, $"{name}.frag");
|
||||
GL.AttachShader(_handle, v);
|
||||
GL.AttachShader(_handle, f);
|
||||
private void Attach()
|
||||
{
|
||||
GL.AttachShader(_handle, _vHandle);
|
||||
GL.AttachShader(_handle, _fHandle);
|
||||
GL.LinkProgram(_handle);
|
||||
GL.GetProgram(_handle, GetProgramParameterName.LinkStatus, out var status);
|
||||
if (status == 0)
|
||||
{
|
||||
throw new Exception($"Program failed to link with error: {GL.GetProgramInfoLog(_handle)}");
|
||||
}
|
||||
GL.DetachShader(_handle, v);
|
||||
GL.DetachShader(_handle, f);
|
||||
GL.DeleteShader(v);
|
||||
GL.DeleteShader(f);
|
||||
}
|
||||
|
||||
private void Detach()
|
||||
{
|
||||
GL.DetachShader(_handle, _vHandle);
|
||||
GL.DetachShader(_handle, _fHandle);
|
||||
GL.DeleteShader(_vHandle);
|
||||
GL.DeleteShader(_fHandle);
|
||||
}
|
||||
|
||||
private int LoadShader(ShaderType type, string file)
|
||||
|
|
@ -130,6 +140,7 @@ public class Shader : IDisposable
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
Detach();
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,17 +29,17 @@ public class Texture : IDisposable
|
|||
public int Height;
|
||||
|
||||
private const int DisabledChannel = (int)BlendingFactor.Zero;
|
||||
private readonly bool[] _values = { true, true, true, true };
|
||||
private readonly string[] _labels = { "R", "G", "B", "A" };
|
||||
private readonly bool[] _values = [true, true, true, true];
|
||||
private readonly string[] _labels = ["R", "G", "B", "A"];
|
||||
public int[] SwizzleMask =
|
||||
{
|
||||
[
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Alpha
|
||||
};
|
||||
];
|
||||
|
||||
public Texture(TextureType type)
|
||||
private Texture(TextureType type)
|
||||
{
|
||||
_handle = GL.GenTexture();
|
||||
_type = type;
|
||||
|
|
|
|||
|
|
@ -16,18 +16,19 @@ public static class TextureHelper
|
|||
// R: Whatever (AO / S / E / ...)
|
||||
// G: Roughness
|
||||
// B: Metallic
|
||||
case "GAMEFACE":
|
||||
case "HK_PROJECT":
|
||||
case "COSMICSHAKE":
|
||||
case "PHOENIX":
|
||||
case "ATOMICHEART":
|
||||
{
|
||||
texture.SwizzleMask = new []
|
||||
{
|
||||
texture.SwizzleMask =
|
||||
[
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Alpha
|
||||
};
|
||||
];
|
||||
break;
|
||||
}
|
||||
// R: Metallic
|
||||
|
|
@ -37,13 +38,13 @@ public static class TextureHelper
|
|||
case "DIVINEKNOCKOUT":
|
||||
case "MOONMAN":
|
||||
{
|
||||
texture.SwizzleMask = new []
|
||||
{
|
||||
texture.SwizzleMask =
|
||||
[
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Alpha
|
||||
};
|
||||
];
|
||||
break;
|
||||
}
|
||||
// R: Roughness
|
||||
|
|
@ -52,13 +53,13 @@ public static class TextureHelper
|
|||
case "CCFF7R":
|
||||
case "PJ033":
|
||||
{
|
||||
texture.SwizzleMask = new []
|
||||
{
|
||||
texture.SwizzleMask =
|
||||
[
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Alpha
|
||||
};
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,15 +215,11 @@ public class SnimGui
|
|||
ImGui.SeparatorText("Editor");
|
||||
if (ImGui.BeginTable("world_editor", 2))
|
||||
{
|
||||
Layout("Skybox");ImGui.PushID(1);
|
||||
ImGui.Checkbox("", ref s.Renderer.ShowSkybox);
|
||||
ImGui.PopID();Layout("Grid");ImGui.PushID(2);
|
||||
ImGui.Checkbox("", ref s.Renderer.ShowGrid);
|
||||
ImGui.PopID();Layout("Lights");ImGui.PushID(3);
|
||||
ImGui.Checkbox("", ref s.Renderer.ShowLights);
|
||||
ImGui.PopID();Layout("Animate With Rotation Only");ImGui.PushID(4);
|
||||
Layout("Animate With Rotation Only");ImGui.PushID(1);
|
||||
ImGui.Checkbox("", ref s.Renderer.AnimateWithRotationOnly);
|
||||
ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(5);
|
||||
ImGui.PopID();Layout("Time Multiplier");ImGui.PushID(2);
|
||||
ImGui.DragInt("", ref s.Renderer.Options.Tracker.TimeMultiplier, 0.1f, 1, 8, "x%i", ImGuiSliderFlags.NoInput);
|
||||
ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(3);
|
||||
var c = (int) s.Renderer.Color;
|
||||
ImGui.Combo("vertex_colors", ref c,
|
||||
"Default\0Sections\0Colors\0Normals\0Texture Coordinates\0");
|
||||
|
|
@ -418,15 +414,18 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
ImGui.TableNextColumn();
|
||||
ImGui.Text(model.UvCount.ToString("D"));
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.Selectable(model.Name, s.Renderer.Options.SelectedModel == guid, ImGuiSelectableFlags.SpanAllColumns))
|
||||
var doubleClick = false;
|
||||
if (ImGui.Selectable(model.Name, s.Renderer.Options.SelectedModel == guid, ImGuiSelectableFlags.SpanAllColumns | ImGuiSelectableFlags.AllowDoubleClick))
|
||||
{
|
||||
s.Renderer.Options.SelectModel(guid);
|
||||
doubleClick = ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left);
|
||||
}
|
||||
Popup(() =>
|
||||
{
|
||||
s.Renderer.Options.SelectModel(guid);
|
||||
if (ImGui.MenuItem("Show", null, model.IsVisible)) model.IsVisible = !model.IsVisible;
|
||||
if (ImGui.MenuItem("Wireframe", null, model.ShowWireframe)) model.ShowWireframe = !model.ShowWireframe;
|
||||
if (ImGui.MenuItem("Collisions", null, model.ShowCollisions, model.HasCollisions)) model.ShowCollisions = !model.ShowCollisions;
|
||||
ImGui.Separator();
|
||||
if (ImGui.MenuItem("Save"))
|
||||
{
|
||||
|
|
@ -459,16 +458,17 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
s.Renderer.IsSkeletonTreeOpen = true;
|
||||
ImGui.SetWindowFocus("Skeleton Tree");
|
||||
}
|
||||
if (ImGui.MenuItem("Teleport To"))
|
||||
{
|
||||
s.Renderer.CameraOp.Teleport(model.GetTransform().Matrix.Translation, model.Box);
|
||||
}
|
||||
doubleClick = ImGui.MenuItem("Teleport To");
|
||||
|
||||
if (ImGui.MenuItem("Delete")) s.Renderer.Options.RemoveModel(guid);
|
||||
if (ImGui.MenuItem("Deselect")) s.Renderer.Options.SelectModel(Guid.Empty);
|
||||
ImGui.Separator();
|
||||
if (ImGui.MenuItem("Copy Path to Clipboard")) ImGui.SetClipboardText(model.Path);
|
||||
});
|
||||
if (doubleClick)
|
||||
{
|
||||
s.Renderer.CameraOp.Teleport(model.GetTransform().Matrix.Translation, model.Box);
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Image(s.Renderer.Options.Icons[model.Attachments.Icon].GetPointer(), new Vector2(_tableWidth));
|
||||
|
|
@ -783,13 +783,36 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
s.CursorState = CursorState.Normal;
|
||||
}
|
||||
|
||||
const float margin = 7.5f;
|
||||
var buttonWidth = 14.0f * ImGui.GetWindowDpiScale();
|
||||
var basePos = new Vector2( size.X - buttonWidth - margin * 2, ImGui.GetFrameHeight() + margin);
|
||||
ImGui.SetCursorPos(basePos);
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.2f));
|
||||
ImGui.ImageButton("skybox_btn", s.Renderer.Options.Icons[s.Renderer.ShowSkybox ? "cube" : "cube_off"].GetPointer(), new Vector2(buttonWidth));
|
||||
TooltipCheckbox("Skybox", ref s.Renderer.ShowSkybox);
|
||||
|
||||
basePos.X -= buttonWidth + margin;
|
||||
ImGui.SetCursorPos(basePos);
|
||||
ImGui.ImageButton("grid_btn", s.Renderer.Options.Icons[s.Renderer.ShowGrid ? "square" : "square_off"].GetPointer(), new Vector2(buttonWidth));
|
||||
TooltipCheckbox("Grid", ref s.Renderer.ShowGrid);
|
||||
|
||||
basePos.X -= buttonWidth + margin;
|
||||
ImGui.SetCursorPos(basePos);
|
||||
ImGui.ImageButton("lights_btn", s.Renderer.Options.Icons[s.Renderer.ShowLights ? "light" : "light_off"].GetPointer(), new Vector2(buttonWidth));
|
||||
TooltipCheckbox("Lights", ref s.Renderer.ShowLights);
|
||||
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
|
||||
float framerate = ImGui.GetIO().Framerate;
|
||||
ImGui.SetCursorPos(size with { X = 7.5f });
|
||||
ImGui.SetCursorPos(size with { X = margin });
|
||||
ImGui.Text($"FPS: {framerate:0} ({1000.0f / framerate:0.##} ms)");
|
||||
|
||||
const string label = "Previewed content may differ from final version saved or used in-game.";
|
||||
ImGui.SetCursorPos(size with { X = size.X - ImGui.CalcTextSize(label).X - 7.5f });
|
||||
ImGui.SetCursorPos(size with { X = size.X - ImGui.CalcTextSize(label).X - margin });
|
||||
ImGui.TextColored(new Vector4(0.50f, 0.50f, 0.50f, 1.00f), label);
|
||||
|
||||
}, false);
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
|
@ -906,6 +929,17 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
if (ImGui.IsItemClicked()) ImGui.SetClipboardText(text ?? label);
|
||||
}
|
||||
|
||||
private static void TooltipCheckbox(string tooltip, ref bool value)
|
||||
{
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.Text($"{tooltip}: {value}");
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
if (ImGui.IsItemClicked()) value = !value;
|
||||
}
|
||||
|
||||
private void Theme()
|
||||
{
|
||||
var style = ImGui.GetStyle();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user