Merge pull request #213 from iAmAsval/dev

Dev
This commit is contained in:
Valentin 2021-11-21 18:04:59 +01:00 committed by GitHub
commit bc2285adf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1744 additions and 785 deletions

164
.editorconfig Normal file
View File

@ -0,0 +1,164 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# C# files
[*.cs]
indent_size = 4
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_within_query_expression_clauses = true
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current
# avoid this. unless absolutely necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# only use var when it's obvious what the variable type is
csharp_style_var_for_built_in_types = false:none
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_elsewhere = false:suggestion
# use language keywords instead of BCL types
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
# Code style defaults
dotnet_sort_system_directives_first = true
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
# Expression-bodied members
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
# Null checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Space preferences
csharp_space_after_cast = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
[*.{asm,inc}]
indent_size = 8
# Visual Studio Solution Files
[*.sln]
indent_style = tab
# Visual Studio XML Project Files
[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# XML Configuration Files
[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}]
indent_size = 2
[CMakeLists.txt]
indent_size = 2
# Makefiles
[Makefile]
indent_style = tab
# Batch Files
[*.{cmd,bat}]
indent_size = 2
end_of_line = crlf
# Bash Files
[*.sh]
end_of_line = lf
# Web Files
[*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,pcss,svg,vue}]
indent_size = 2
# Markdown Files
[*.md]
trim_trailing_whitespace = false
# JSON Files
[*.{json,json5,webmanifest}]
indent_size = 2

View File

@ -30,7 +30,7 @@ jobs:
run: dotnet restore FModel
- name: .NET Publish
run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net5.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }}
run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net6.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }}
- name: ZIP File
uses: papeloto/action-zip@v1

@ -1 +1 @@
Subproject commit 6d911668e730b3f4c1eab0b3f7e9a99bc3dde943
Subproject commit d2163de354e97a0e62222221b96666a678255884

View File

@ -123,7 +123,7 @@ namespace FModel
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
}
private string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
public static string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
{
using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path);
if (rk != null)

View File

@ -30,12 +30,12 @@ namespace FModel.Creator.Bases.FN
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset == EEnabledDisabled.Enabled);
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export))
_rarityName = export.Name;
else
_rarityName = GetRarityName(Object.GetOrDefault<FName>("Rarity"));
_rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription();
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
@ -108,44 +108,6 @@ namespace FModel.Creator.Bases.FN
return number > 10 ? $"C{number / 10 + 1} S{s[^1..]}" : $"C1 S{s}";
}
private string GetRarityName(FName r)
{
var rarity = EFortRarity.Uncommon;
switch (r.Text)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
rarity = EFortRarity.Common;
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
rarity = EFortRarity.Rare;
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
rarity = EFortRarity.Epic;
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
rarity = EFortRarity.Legendary;
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
rarity = EFortRarity.Mythic;
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
rarity = EFortRarity.Unattainable;
break;
}
return rarity.GetDescription();
}
private new void DrawBackground(SKCanvas c)
{
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))

View File

@ -33,14 +33,14 @@ namespace FModel.Creator.Bases.FN
{
// rarity
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
else GetRarity(Object.GetOrDefault<FName>("Rarity")); // default is uncommon
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", "SidePanelIcon", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
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);
@ -78,7 +78,7 @@ namespace FModel.Creator.Bases.FN
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset == EEnabledDisabled.Enabled);
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
@ -165,44 +165,11 @@ namespace FModel.Creator.Bases.FN
}
}
private void GetRarity(FName r)
private void GetRarity(EFortRarity r)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
var rarity = EFortRarity.Uncommon;
switch (r.Text)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
rarity = EFortRarity.Common;
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
rarity = EFortRarity.Rare;
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
rarity = EFortRarity.Epic;
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
rarity = EFortRarity.Legendary;
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
rarity = EFortRarity.Mythic;
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
rarity = EFortRarity.Unattainable;
break;
}
if (export.GetByIndex<FStructFallback>((int) rarity) is { } data &&
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"))

View File

@ -114,7 +114,7 @@ namespace FModel.Creator.Bases.FN
weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") &&
weaponRowValue.TryGetValue(out FName durabilityRowName, "DurabilityRowName") &&
durabilityTable.TryGetDataTableRow(durabilityRowName.Text, StringComparison.OrdinalIgnoreCase, out var durability) &&
durability.TryGetValue(out int duraByRarity, GetRarityName(Object.GetOrDefault<FName>("Rarity"))))
durability.TryGetValue(out int duraByRarity, Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription()))
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20));
}
@ -145,7 +145,7 @@ namespace FModel.Creator.Bases.FN
curveTable.TryGetCurveTableRow(rowName.Text, StringComparison.OrdinalIgnoreCase, out var rowValue) &&
rowValue.TryGetValue(out FSimpleCurveKey[] keys, "Keys") && keys.Length > 0)
{
statValue = keys[0].KeyValue;
statValue = keys[0].Value;
return true;
}
@ -153,44 +153,6 @@ namespace FModel.Creator.Bases.FN
return false;
}
private string GetRarityName(FName r)
{
var rarity = EFortRarity.Uncommon;
switch (r.Text)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
rarity = EFortRarity.Common;
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
rarity = EFortRarity.Rare;
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
rarity = EFortRarity.Epic;
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
rarity = EFortRarity.Legendary;
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
rarity = EFortRarity.Mythic;
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
rarity = EFortRarity.Unattainable;
break;
}
return rarity.GetDescription();
}
private readonly SKPaint _informationPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
@ -293,7 +255,7 @@ namespace FModel.Creator.Bases.FN
public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y)
{
while (_statPaint.MeasureText(_statName) > height * 2)
while (_statPaint.MeasureText(_statName) > height * 2 - 40)
{
_statPaint.TextSize -= 1;
}

View File

@ -2,6 +2,8 @@
using System.Linq;
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.UObject;
@ -72,9 +74,15 @@ namespace FModel.Creator.Bases.FN
Description += "\n" + completionText.Text;
if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "SidePanelIcon", "EntryListIcon", "ToastIcon"))
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") &&
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out UObject iconObject))
{
Preview = Utils.GetBitmap(tandemIcon);
Preview = iconObject switch
{
UTexture2D text => Utils.GetBitmap(text),
UMaterialInstanceConstant mat => Utils.GetBitmap(mat),
_ => Preview
};
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
@ -93,6 +93,7 @@ namespace FModel.Creator
case "TextureB":
case "OfferImage":
case "KeyArtTexture":
case "NPC-Portrait":
{
return GetBitmap(texture);
}
@ -123,10 +124,10 @@ namespace FModel.Creator
return bmp;
}
public static void ClearToTransparent(this SKBitmap me) {
public static void ClearToTransparent(this SKBitmap me, SKColor colorToDelete) {
var colors = me.Pixels;
for (var n = 0; n < colors.Length; n++) {
if (colors[n] != SKColors.Black) continue;
if (colors[n] != colorToDelete) continue;
colors[n] = SKColors.Transparent;
}
me.Pixels = colors;

View File

@ -51,14 +51,6 @@ namespace FModel
Never
}
public enum EEnabledDisabled
{
[Description("Disabled")]
Disabled,
[Description("Enabled")]
Enabled
}
public enum FGame
{
[Description("Unknown")]
@ -90,7 +82,13 @@ namespace FModel
[Description("Core")]
Platform,
[Description("Days Gone")]
BendGame
BendGame,
[Description("PLAYERUNKNOWN'S BATTLEGROUNDS")]
TslGame,
[Description("Splitgate")]
PortalWars,
[Description("GTA: The Trilogy - Definitive Edition")]
Gameface
}
public enum ELoadingMode

View File

@ -2,12 +2,12 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationIcon>FModel.ico</ApplicationIcon>
<Version>4.0.0</Version>
<AssemblyVersion>4.0.1.1</AssemblyVersion>
<FileVersion>4.0.1.1</FileVersion>
<AssemblyVersion>4.0.2.0</AssemblyVersion>
<FileVersion>4.0.2.0</FileVersion>
<IsPackable>false</IsPackable>
<IsPublishable>true</IsPublishable>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
@ -102,20 +102,20 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AdonisUI.ClassicTheme.NET5" Version="1.17.1" />
<PackageReference Include="AdonisUI.NET5" Version="1.17.1" />
<PackageReference Include="AdonisUI" Version="1.17.1" />
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.0" />
<PackageReference Include="AvalonEdit" Version="6.1.2.30" />
<PackageReference Include="CSCore" Version="1.2.1.2" />
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="EpicManifestParser" Version="1.2.69-temp" />
<PackageReference Include="HelixToolkit.SharpDX.Core.Wpf" Version="2.17.0" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.2.12" />
<PackageReference Include="EpicManifestParser" Version="1.2.70-temp" />
<PackageReference Include="HelixToolkit.SharpDX.Core.Wpf" Version="2.20.0" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.2.15" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NVorbis" Version="0.10.3" />
<PackageReference Include="NVorbis" Version="0.10.4" />
<PackageReference Include="Oodle.NET" Version="1.0.1" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="3.1.0" />
<PackageReference Include="RestSharp" Version="106.12.0" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.0" />
<PackageReference Include="RestSharp" Version="106.13.0" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.3" />
@ -186,6 +186,7 @@
<Resource Include="Resources\delete.png" />
<Resource Include="Resources\edit.png" />
<Resource Include="Resources\go_to_directory.png" />
<Resource Include="Resources\approaching_storm_cubemap.dds" />
</ItemGroup>
<ItemGroup>

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel.csproj", "{B1F494EA-90A6-4C24-800E-2F724A1884CA}"
EndProject

View File

@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace FModel.Framework
{
public class NavigationList<T> : List<T>
{
private int _currentIndex = 0;
public int CurrentIndex
{
get
{
if (_currentIndex > Count - 1) { _currentIndex = Count - 1; }
if (_currentIndex < 0) { _currentIndex = 0; }
return _currentIndex;
}
set { _currentIndex = value; }
}
public T MoveNext
{
get { _currentIndex++; return this[CurrentIndex]; }
}
public T MovePrevious
{
get { _currentIndex--; return this[CurrentIndex]; }
}
public T Current
{
get { return this[CurrentIndex]; }
}
}
}

View File

@ -64,6 +64,15 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Archives Info" Command="{Binding MenuCommand}" CommandParameter="Directory_ArchivesInfo" IsEnabled="{Binding IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Assets">
<MenuItem Header="Search" IsEnabled="{Binding IsReady}" InputGestureText="Ctrl+Shift+F" Click="OnSearchViewClick">
@ -147,18 +156,15 @@
<MenuItem Header="Save Textures" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveTextures, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveTextures, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Save Materials" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveMaterials, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveMaterials, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Save Meshes" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveMeshes, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveMeshes, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Save Animations" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveAnimations, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveAnimations, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Open Sounds" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoOpenSounds, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoOpenSounds, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Open Meshes &amp; Materials" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoOpenMeshes, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoOpenMeshes, Source={x:Static local:Settings.UserSettings.Default}}" />
</MenuItem>
</MenuItem>
<MenuItem Header="Views">
@ -504,7 +510,7 @@
<controls:Breadcrumb Grid.Row="0" MaxWidth="{Binding ActualWidth, ElementName=AssetsSearchName}" HorizontalAlignment="Left" Margin="0 5 0 5"
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName, FallbackValue='No/Directory/Detected/In/Folder'}"/>
<ListBox Grid.Row="1" x:Name="AssetsListName" Style="{StaticResource AssetsListBox}" PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick">
<ListBox Grid.Row="1" x:Name="AssetsListName" Style="{StaticResource AssetsListBox}" PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick" PreviewKeyDown="OnPreviewKeyDown">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Extract in New Tab" Command="{Binding DataContext.RightClickMenuCommand}">
@ -835,32 +841,6 @@
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="TEX" />
</StatusBarItem>
<StatusBarItem Width="30" HorizontalContentAlignment="Stretch" ToolTip="Auto Save Materials Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoSaveMaterials, Source={x:Static local:Settings.UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="MAT" />
</StatusBarItem>
<StatusBarItem Width="30" HorizontalContentAlignment="Stretch" ToolTip="Auto Save Meshes Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoSaveMeshes, Source={x:Static local:Settings.UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="MSH" />
</StatusBarItem>
<StatusBarItem Width="35" HorizontalContentAlignment="Stretch" ToolTip="Auto Save Animations Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
@ -886,6 +866,19 @@
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="SND" />
</StatusBarItem>
<StatusBarItem Width="35" HorizontalContentAlignment="Stretch" ToolTip="Auto Open Meshes &amp; Materials Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoOpenMeshes, Source={x:Static local:Settings.UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="MSH" />
</StatusBarItem>
</StackPanel>
</StatusBarItem>
</StatusBar>

View File

@ -33,14 +33,12 @@ namespace FModel
{new KeyGesture(UserSettings.Default.AutoSaveProps.Key, UserSettings.Default.AutoSaveProps.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveTextures", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoSaveTextures.Key, UserSettings.Default.AutoSaveTextures.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveMaterials", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoSaveMaterials.Key, UserSettings.Default.AutoSaveMaterials.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveMeshes", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoSaveMeshes.Key, UserSettings.Default.AutoSaveMeshes.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveAnimations", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoSaveAnimations.Key, UserSettings.Default.AutoSaveAnimations.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoOpenSounds", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoOpenSounds.Key, UserSettings.Default.AutoOpenSounds.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoOpenMeshes", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoOpenMeshes.Key, UserSettings.Default.AutoOpenMeshes.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection {new KeyGesture(Key.F12)}), OnMappingsReload));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (s, e) => OnOpenAvalonFinder()));
@ -148,18 +146,15 @@ namespace FModel
case "AutoSaveTextures":
UserSettings.Default.IsAutoSaveTextures = !UserSettings.Default.IsAutoSaveTextures;
break;
case "AutoSaveMaterials":
UserSettings.Default.IsAutoSaveMaterials = !UserSettings.Default.IsAutoSaveMaterials;
break;
case "AutoSaveMeshes":
UserSettings.Default.IsAutoSaveMeshes = !UserSettings.Default.IsAutoSaveMeshes;
break;
case "AutoSaveAnimations":
UserSettings.Default.IsAutoSaveAnimations = !UserSettings.Default.IsAutoSaveAnimations;
break;
case "AutoOpenSounds":
UserSettings.Default.IsAutoOpenSounds = !UserSettings.Default.IsAutoOpenSounds;
break;
case "AutoOpenMeshes":
UserSettings.Default.IsAutoOpenMeshes = !UserSettings.Default.IsAutoOpenMeshes;
break;
}
}
@ -248,5 +243,18 @@ namespace FModel
UserSettings.Default.LoadingMode = ELoadingMode.Multiple;
_applicationView.LoadingModes.LoadCommand.Execute(listBox.SelectedItems);
}
private async void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (!_applicationView.IsReady || sender is not ListBox listBox) return;
switch (e.Key)
{
case Key.Enter:
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
break;
}
}
}
}

Binary file not shown.

View File

@ -86,20 +86,6 @@ namespace FModel.Settings
set => SetProperty(ref _isAutoSaveTextures, value);
}
private bool _isAutoSaveMaterials;
public bool IsAutoSaveMaterials
{
get => _isAutoSaveMaterials;
set => SetProperty(ref _isAutoSaveMaterials, value);
}
private bool _isAutoSaveMeshes;
public bool IsAutoSaveMeshes
{
get => _isAutoSaveMeshes;
set => SetProperty(ref _isAutoSaveMeshes, value);
}
private bool _isAutoSaveAnimations;
public bool IsAutoSaveAnimations
{
@ -114,6 +100,13 @@ namespace FModel.Settings
set => SetProperty(ref _isAutoOpenSounds, value);
}
private bool _isAutoOpenMeshes = true;
public bool IsAutoOpenMeshes
{
get => _isAutoOpenMeshes;
set => SetProperty(ref _isAutoOpenMeshes, value);
}
private bool _isLoggerExpanded = true;
public bool IsLoggerExpanded
{
@ -156,8 +149,8 @@ namespace FModel.Settings
set => SetProperty(ref _updateMode, value);
}
private EEnabledDisabled _keepDirectoryStructure = EEnabledDisabled.Enabled;
public EEnabledDisabled KeepDirectoryStructure
private bool _keepDirectoryStructure = true;
public bool KeepDirectoryStructure
{
get => _keepDirectoryStructure;
set => SetProperty(ref _keepDirectoryStructure, value);
@ -198,8 +191,8 @@ namespace FModel.Settings
set => SetProperty(ref _cosmeticStyle, value);
}
private EEnabledDisabled _cosmeticDisplayAsset = EEnabledDisabled.Disabled;
public EEnabledDisabled CosmeticDisplayAsset
private bool _cosmeticDisplayAsset;
public bool CosmeticDisplayAsset
{
get => _cosmeticDisplayAsset;
set => SetProperty(ref _cosmeticDisplayAsset, value);
@ -228,7 +221,10 @@ namespace FModel.Settings
{FGame.RogueCompany, Constants._NO_PRESET_TRIGGER},
{FGame.SwGame, Constants._NO_PRESET_TRIGGER},
{FGame.Platform, Constants._NO_PRESET_TRIGGER},
{FGame.BendGame, Constants._NO_PRESET_TRIGGER}
{FGame.BendGame, Constants._NO_PRESET_TRIGGER},
{FGame.TslGame, Constants._NO_PRESET_TRIGGER},
{FGame.PortalWars, Constants._NO_PRESET_TRIGGER},
{FGame.Gameface, Constants._NO_PRESET_TRIGGER}
};
public IDictionary<FGame, string> Presets
{
@ -252,7 +248,10 @@ namespace FModel.Settings
{FGame.RogueCompany, EGame.GAME_RogueCompany},
{FGame.SwGame, EGame.GAME_UE4_LATEST},
{FGame.Platform, EGame.GAME_UE4_25},
{FGame.BendGame, EGame.GAME_UE4_11}
{FGame.BendGame, EGame.GAME_UE4_11},
{FGame.TslGame, EGame.GAME_PlayerUnknownsBattlegrounds},
{FGame.PortalWars, EGame.GAME_UE4_LATEST},
{FGame.Gameface, EGame.GAME_GTATheTrilogyDefinitiveEdition}
};
public IDictionary<FGame, EGame> OverridedGame
{
@ -276,7 +275,10 @@ namespace FModel.Settings
{FGame.RogueCompany, UE4Version.VER_UE4_DETERMINE_BY_GAME},
{FGame.SwGame, UE4Version.VER_UE4_DETERMINE_BY_GAME},
{FGame.Platform, UE4Version.VER_UE4_DETERMINE_BY_GAME},
{FGame.BendGame, UE4Version.VER_UE4_DETERMINE_BY_GAME}
{FGame.BendGame, UE4Version.VER_UE4_DETERMINE_BY_GAME},
{FGame.TslGame, UE4Version.VER_UE4_DETERMINE_BY_GAME},
{FGame.PortalWars, UE4Version.VER_UE4_DETERMINE_BY_GAME},
{FGame.Gameface, UE4Version.VER_UE4_DETERMINE_BY_GAME}
};
public IDictionary<FGame, UE4Version> OverridedUEVersion
{
@ -300,7 +302,10 @@ namespace FModel.Settings
{FGame.RogueCompany, null},
{FGame.SwGame, null},
{FGame.Platform, null},
{FGame.BendGame, null}
{FGame.BendGame, null},
{FGame.TslGame, null},
{FGame.PortalWars, null},
{FGame.Gameface, null}
};
public IDictionary<FGame, List<FCustomVersion>> OverridedCustomVersions
{
@ -324,7 +329,10 @@ namespace FModel.Settings
{FGame.RogueCompany, null},
{FGame.SwGame, null},
{FGame.Platform, null},
{FGame.BendGame, null}
{FGame.BendGame, null},
{FGame.TslGame, null},
{FGame.PortalWars, null},
{FGame.Gameface, null}
};
public IDictionary<FGame, Dictionary<string, bool>> OverridedOptions
{
@ -387,7 +395,10 @@ namespace FModel.Settings
{FGame.RogueCompany, new List<CustomDirectory>()},
{FGame.SwGame, new List<CustomDirectory>()},
{FGame.Platform, new List<CustomDirectory>()},
{FGame.BendGame, new List<CustomDirectory>()}
{FGame.BendGame, new List<CustomDirectory>()},
{FGame.TslGame, new List<CustomDirectory>()},
{FGame.PortalWars, new List<CustomDirectory>()},
{FGame.Gameface, new List<CustomDirectory>()}
};
public IDictionary<FGame, IList<CustomDirectory>> CustomDirectories
{
@ -472,34 +483,27 @@ namespace FModel.Settings
set => SetProperty(ref _autoSaveTextures, value);
}
private Hotkey _autoSaveMaterials = new(Key.F4);
public Hotkey AutoSaveMaterials
{
get => _autoSaveMaterials;
set => SetProperty(ref _autoSaveMaterials, value);
}
private Hotkey _autoSaveMeshes = new(Key.F5);
public Hotkey AutoSaveMeshes
{
get => _autoSaveMeshes;
set => SetProperty(ref _autoSaveMeshes, value);
}
private Hotkey _autoSaveAnimations = new(Key.F6);
private Hotkey _autoSaveAnimations = new(Key.F4);
public Hotkey AutoSaveAnimations
{
get => _autoSaveAnimations;
set => SetProperty(ref _autoSaveAnimations, value);
}
private Hotkey _autoOpenSounds = new(Key.F7);
private Hotkey _autoOpenSounds = new(Key.F5);
public Hotkey AutoOpenSounds
{
get => _autoOpenSounds;
set => SetProperty(ref _autoOpenSounds, value);
}
private Hotkey _autoOpenMeshes = new(Key.F6);
public Hotkey AutoOpenMeshes
{
get => _autoOpenMeshes;
set => SetProperty(ref _autoOpenMeshes, value);
}
private Hotkey _addAudio = new(Key.N, ModifierKeys.Control);
public Hotkey AddAudio
{
@ -542,6 +546,13 @@ namespace FModel.Settings
set => SetProperty(ref _lodExportFormat, value);
}
private bool _saveSkeletonAsMesh;
public bool SaveSkeletonAsMesh
{
get => _saveSkeletonAsMesh;
set => SetProperty(ref _saveSkeletonAsMesh, value);
}
private ETextureFormat _textureExportFormat = ETextureFormat.Png;
public ETextureFormat TextureExportFormat
{

View File

@ -1,4 +1,5 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using FModel.ViewModels.ApiEndpoints.Models;
@ -45,6 +46,23 @@ namespace FModel.ViewModels.ApiEndpoints
return GetMappingsAsync(token).GetAwaiter().GetResult();
}
public async Task<Dictionary<string, Dictionary<string, string>>> GetHotfixesAsync(CancellationToken token, string language = "en-US")
{
var request = new RestRequest("https://benbot.app/api/v1/hotfixes", Method.GET)
{
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
request.AddParameter("lang", language);
var response = await _client.ExecuteAsync<Dictionary<string, Dictionary<string, string>>>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int)response.StatusCode, request.Resource);
return response.Data;
}
public Dictionary<string, Dictionary<string, string>> GetHotfixes(CancellationToken token, string language = "en-US")
{
return GetHotfixesAsync(token, language).GetAwaiter().GetResult();
}
public async Task DownloadFileAsync(string fileLink, string installationPath)
{
var request = new RestRequest(fileLink, Method.GET);

View File

@ -92,7 +92,7 @@ namespace FModel.ViewModels
AesManager = new AesManagerViewModel(CUE4Parse);
MapViewer = new MapViewerViewModel(CUE4Parse);
AudioPlayer = new AudioPlayerViewModel();
ModelViewer = new ModelViewerViewModel();
ModelViewer = new ModelViewerViewModel(CUE4Parse.Game);
Status = EStatusKind.Ready;
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -20,6 +20,7 @@ using CUE4Parse.UE4.Assets.Exports.Sound;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Exports.Wwise;
using CUE4Parse.UE4.IO;
using CUE4Parse.UE4.Localization;
using CUE4Parse.UE4.Oodle.Objects;
using CUE4Parse.UE4.Shaders;
@ -58,12 +59,20 @@ namespace FModel.ViewModels
set => SetProperty(ref _game, value);
}
private bool _modelIsSwappingMaterial;
public bool ModelIsSwappingMaterial
{
get => _modelIsSwappingMaterial;
set => SetProperty(ref _modelIsSwappingMaterial, value);
}
public AbstractVfsFileProvider Provider { get; }
public GameDirectoryViewModel GameDirectory { get; }
public AssetsFolderViewModel AssetsFolder { get; }
public SearchViewModel SearchVm { get; }
public TabControlViewModel TabControl { get; }
public int LocalizedResourcesCount { get; set; }
public bool HotfixedResourcesDone { get; set; } = false;
public int VirtualPathCount { get; set; }
public CUE4ParseViewModel(string gameDirectory)
@ -138,7 +147,7 @@ namespace FModel.ViewModels
byte[] manifestData;
var chunksDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
var manifestPath = Path.Combine(chunksDir.FullName, manifestInfo.Filename);
var manifestPath = Path.Combine(chunksDir.FullName, manifestInfo.FileName);
if (File.Exists(manifestPath))
{
manifestData = File.ReadAllBytes(manifestPath);
@ -229,7 +238,12 @@ namespace FModel.ViewModels
{
cancellationToken.ThrowIfCancellationRequested();
if (Provider.MountedVfs.FirstOrDefault(x => x.Name == file.Name) is not { } vfs)
{
if (Provider.UnloadedVfs.FirstOrDefault(x => x.Name == file.Name) is IoStoreReader store)
file.FileCount = (int)store.Info.TocEntryCount - 1;
continue;
}
file.IsEnabled = true;
file.MountPoint = vfs.MountPoint;
@ -320,32 +334,68 @@ namespace FModel.ViewModels
}
public async Task LoadLocalizedResources()
{
await LoadGameLocalizedResources();
await LoadHotfixedLocalizedResources();
if (LocalizedResourcesCount > 0)
{
FLogger.AppendInformation();
FLogger.AppendText($"{LocalizedResourcesCount} localized resources loaded for '{UserSettings.Default.AssetLanguage.GetDescription()}'", Constants.WHITE, true);
}
else
{
FLogger.AppendWarning();
FLogger.AppendText($"Could not load localized resources in '{UserSettings.Default.AssetLanguage.GetDescription()}', language may not exist", Constants.WHITE, true);
}
}
private async Task LoadGameLocalizedResources()
{
if (LocalizedResourcesCount > 0) return;
await _threadWorkerView.Begin(cancellationToken =>
{
LocalizedResourcesCount = Provider.LoadLocalization(UserSettings.Default.AssetLanguage, cancellationToken);
if (LocalizedResourcesCount > 0)
{
FLogger.AppendInformation();
FLogger.AppendText($"{LocalizedResourcesCount} localized resources loaded for '{UserSettings.Default.AssetLanguage.GetDescription()}'", Constants.WHITE, true);
}
else
{
FLogger.AppendWarning();
FLogger.AppendText($"Could not load localized resources in '{UserSettings.Default.AssetLanguage.GetDescription()}', language may not exist", Constants.WHITE, true);
}
Utils.Typefaces = new Typefaces(this);
});
}
/// <summary>
/// Load hotfixed localized resources
/// </summary>
/// <remarks>Functions only when LoadLocalizedResources is used prior to this (Asval: Why?).</remarks>
private async Task LoadHotfixedLocalizedResources()
{
if (Game != FGame.FortniteGame) return;
if (HotfixedResourcesDone) return;
await _threadWorkerView.Begin(cancellationToken =>
{
var hotfixes = ApplicationService.ApiEndpointView.BenbotApi.GetHotfixes(cancellationToken, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage));
if (hotfixes == null) return;
HotfixedResourcesDone = true;
foreach (var entries in hotfixes)
{
cancellationToken.ThrowIfCancellationRequested();
if (!Provider.LocalizedResources.ContainsKey(entries.Key))
Provider.LocalizedResources[entries.Key] = new Dictionary<string, string>();
foreach (var keyValue in entries.Value)
{
cancellationToken.ThrowIfCancellationRequested();
Provider.LocalizedResources[entries.Key][keyValue.Key] = keyValue.Value;
LocalizedResourcesCount++;
}
}
});
}
public async Task LoadVirtualPaths()
{
if (VirtualPathCount > 0) return;
await _threadWorkerView.Begin(cancellationToken =>
{
VirtualPathCount = Provider.LoadVirtualPaths(cancellationToken);
VirtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.OverridedUEVersion[Game], cancellationToken);
if (VirtualPathCount > 0)
{
FLogger.AppendInformation();
@ -365,7 +415,7 @@ namespace FModel.ViewModels
{
Thread.Sleep(10);
cancellationToken.ThrowIfCancellationRequested();
Extract(asset.FullPath, TabControl.HasNoTabs);
try {Extract(asset.FullPath, TabControl.HasNoTabs);} catch {/**/}
}
foreach (var f in folder.Folders) ExtractFolder(cancellationToken, f);
@ -389,7 +439,7 @@ namespace FModel.ViewModels
{
Thread.Sleep(10);
cancellationToken.ThrowIfCancellationRequested();
Extract(asset.FullPath, TabControl.HasNoTabs, true);
try {Extract(asset.FullPath, TabControl.HasNoTabs, true);} catch {/**/}
}
foreach (var f in folder.Folders) SaveFolder(cancellationToken, f);
@ -443,6 +493,11 @@ namespace FModel.ViewModels
case "log":
case "po":
case "bat":
case "dat":
case "cfg":
case "ide":
case "ipl":
case "zon":
case "xml":
case "h":
case "uproject":
@ -570,7 +625,7 @@ namespace FModel.ViewModels
default:
{
FLogger.AppendWarning();
FLogger.AppendText($"The file '{fileName}' is of an unknown type. If this is a mistake, we are already working to fix it!", Constants.WHITE, true);
FLogger.AppendText($"The file '{fileName}' is of an unknown type.", Constants.WHITE, true);
break;
}
}
@ -617,36 +672,33 @@ namespace FModel.ViewModels
SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Directory, TabControl.SelectedTab.Header.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data);
return false;
}
case UStaticMesh:
case USkeletalMesh:
case UMaterialInterface when UserSettings.Default.IsAutoSaveMaterials: // don't trigger model viewer if false
case UAnimSequence when UserSettings.Default.IsAutoSaveAnimations: // don't trigger model viewer if false
case UStaticMesh when UserSettings.Default.IsAutoOpenMeshes:
case USkeletalMesh when UserSettings.Default.IsAutoOpenMeshes:
case UMaterialInstance when UserSettings.Default.IsAutoOpenMeshes && !ModelIsSwappingMaterial &&
!(Game == FGame.FortniteGame && export.Owner != null && (export.Owner.Name.EndsWith($"/MI_OfferImages/{export.Name}", StringComparison.OrdinalIgnoreCase) ||
export.Owner.Name.EndsWith($"/RenderSwitch_Materials/{export.Name}", StringComparison.OrdinalIgnoreCase) ||
export.Owner.Name.EndsWith($"/MI_BPTile/{export.Name}", StringComparison.OrdinalIgnoreCase))):
{
if (UserSettings.Default.IsAutoSaveMaterials || UserSettings.Default.IsAutoSaveMeshes || UserSettings.Default.IsAutoSaveAnimations)
Application.Current.Dispatcher.InvokeAsync(() =>
{
var toSave = new Exporter(export, UserSettings.Default.TextureExportFormat, UserSettings.Default.LodExportFormat);
var toSaveDirectory = new DirectoryInfo(Path.Combine(UserSettings.Default.OutputDirectory, "Saves"));
if (toSave.TryWriteToDir(toSaveDirectory, out var savedFileName))
{
Log.Information("Successfully saved {FileName}", savedFileName);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true);
}
else
{
Log.Error("{FileName} could not be saved", savedFileName);
FLogger.AppendError();
FLogger.AppendText($"Could not save '{savedFileName}'", Constants.WHITE, true);
}
}
else
var modelViewer = Helper.GetWindow<ModelViewer>("Model Viewer", () => new ModelViewer().Show());
modelViewer.Load(export);
});
return true;
}
case UMaterialInstance m when ModelIsSwappingMaterial:
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
Application.Current.Dispatcher.Invoke(delegate
{
var modelViewer = Helper.GetWindow<ModelViewer>("Model Viewer", () => new ModelViewer().Show());
modelViewer.Load(export);
});
}
var modelViewer = Helper.GetWindow<ModelViewer>("Model Viewer", () => new ModelViewer().Show());
modelViewer.Swap(m);
});
return true;
}
case USkeleton when UserSettings.Default.SaveSkeletonAsMesh:
case UAnimSequence when UserSettings.Default.IsAutoSaveAnimations:
{
SaveExport(export);
return true;
}
default:
@ -666,8 +718,7 @@ namespace FModel.ViewModels
var userDir = Path.Combine(UserSettings.Default.OutputDirectory, "Sounds");
if (fullPath.StartsWith("/")) fullPath = fullPath[1..];
var savedAudioPath = Path.Combine(userDir,
UserSettings.Default.KeepDirectoryStructure == EEnabledDisabled.Enabled
? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLower()}";
UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLower()}";
if (!UserSettings.Default.IsAutoOpenSounds)
{
@ -690,6 +741,24 @@ namespace FModel.ViewModels
});
}
private void SaveExport(UObject export)
{
var toSave = new Exporter(export, UserSettings.Default.TextureExportFormat, UserSettings.Default.LodExportFormat, UserSettings.Default.MeshExportFormat);
var toSaveDirectory = new DirectoryInfo(Path.Combine(UserSettings.Default.OutputDirectory, "Saves"));
if (toSave.TryWriteToDir(toSaveDirectory, out var savedFileName))
{
Log.Information("Successfully saved {FileName}", savedFileName);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true);
}
else
{
Log.Error("{FileName} could not be saved", savedFileName);
FLogger.AppendError();
FLogger.AppendText($"Could not save '{savedFileName}'", Constants.WHITE, true);
}
}
public void ExportData(string fullPath)
{
var fileName = fullPath.SubstringAfterLast('/');
@ -699,8 +768,7 @@ namespace FModel.ViewModels
{
foreach (var kvp in assets)
{
var path = Path.Combine(directory, UserSettings.Default.KeepDirectoryStructure == EEnabledDisabled.Enabled
? kvp.Key : kvp.Key.SubstringAfterLast('/')).Replace('\\', '/');
var path = Path.Combine(directory, UserSettings.Default.KeepDirectoryStructure ? kvp.Key : kvp.Key.SubstringAfterLast('/')).Replace('\\', '/');
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
File.WriteAllBytes(path, kvp.Value);
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

View File

@ -1,11 +1,13 @@
using System.Diagnostics;
using System.Threading;
using AdonisUI.Controls;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using FModel.Views;
using FModel.Views.Resources.Controls;
using Newtonsoft.Json;
namespace FModel.ViewModels.Commands
{
@ -28,6 +30,11 @@ namespace FModel.ViewModels.Commands
case "Directory_Backup":
Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.GameName).Show());
break;
case "Directory_ArchivesInfo":
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false);
break;
case "Views_AudioPlayer":
Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show());
break;

View File

@ -1,4 +1,4 @@
using FModel.Extensions;
using FModel.Extensions;
using FModel.Framework;
using Newtonsoft.Json;
using Serilog;
@ -7,6 +7,8 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Win32;
namespace FModel.ViewModels
{
@ -43,14 +45,14 @@ namespace FModel.ViewModels
public void AddUnknownGame(string gameDirectory)
{
_autoDetectedGames.Add(new DetectedGame {GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory});
_autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory });
SelectedDetectedGame = AutoDetectedGames.Last();
}
private IEnumerable<DetectedGame> EnumerateDetectedGames()
{
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks");
yield return new DetectedGame {GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER};
yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER };
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks");
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks");
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks");
@ -62,20 +64,25 @@ namespace FModel.ViewModels
yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks");
yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content");
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks");
yield return new DetectedGame {GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER};
yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER };
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks");
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks");
}
private LauncherInstalled _launcherInstalled;
private DetectedGame GetUnrealEngineGame(string gameName, string pakDirectory)
{
_launcherInstalled ??= GetDrivedLauncherInstalls<LauncherInstalled>("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat");
_launcherInstalled ??= GetDriveLauncherInstalls<LauncherInstalled>("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat");
if (_launcherInstalled?.InstallationList != null)
{
foreach (var installationList in _launcherInstalled.InstallationList)
{
if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase))
return new DetectedGame {GameName = installationList.AppName, GameDirectory = $"{installationList.InstallLocation}{pakDirectory}"};
return new DetectedGame { GameName = installationList.AppName, GameDirectory = $"{installationList.InstallLocation}{pakDirectory}" };
}
}
@ -86,8 +93,8 @@ namespace FModel.ViewModels
private RiotClientInstalls _riotClientInstalls;
private DetectedGame GetRiotGame(string gameName, string pakDirectory)
{
_riotClientInstalls ??= GetDrivedLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json");
if (_riotClientInstalls is {AssociatedClient: { }})
_riotClientInstalls ??= GetDriveLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json");
if (_riotClientInstalls is { AssociatedClient: { } })
{
foreach (var (key, _) in _riotClientInstalls.AssociatedClient)
{
@ -101,18 +108,46 @@ namespace FModel.ViewModels
}
private LauncherSettings _launcherSettings;
private DetectedGame GetMojangGame(string gameName, string pakDirectory)
{
_launcherSettings ??= GetDataLauncherInstalls<LauncherSettings>("\\.minecraft\\launcher_settings.json");
if (_launcherSettings is {ProductLibraryDir: { }})
return new DetectedGame {GameName = gameName, GameDirectory = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}"};
if (_launcherSettings is { ProductLibraryDir: { } })
return new DetectedGame { GameName = gameName, GameDirectory = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}" };
Log.Warning("Could not find {GameName} in launcher_settings.json", gameName);
return null;
}
private T GetDrivedLauncherInstalls<T>(string jsonFile)
private DetectedGame GetSteamGame(int id, string pakDirectory)
{
var steamInfo = SteamDetection.GetSteamGameById(id);
if (steamInfo is not null)
return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" };
Log.Warning("Could not find {GameId} in steam manifests", id);
return null;
}
private DetectedGame GetRockstarGamesGame(string key, string pakDirectory)
{
var installLocation = string.Empty;
try
{
installLocation = App.GetRegistryValue(@$"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{key}", "InstallLocation", RegistryHive.LocalMachine);
}
catch
{
// ignored
}
if (!string.IsNullOrEmpty(installLocation))
return new DetectedGame { GameName = key, GameDirectory = $"{installLocation}{pakDirectory}" };
Log.Warning("Could not find {GameName} in the registry", key);
return null;
}
private T GetDriveLauncherInstalls<T>(string jsonFile)
{
foreach (var drive in DriveInfo.GetDrives())
{
@ -158,10 +193,13 @@ namespace FModel.ViewModels
{
[JsonProperty("associated_client", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> AssociatedClient;
[JsonProperty("patchlines", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> Patchlines;
[JsonProperty("rc_default", NullValueHandling = NullValueHandling.Ignore)]
public string RcDefault;
[JsonProperty("rc_live", NullValueHandling = NullValueHandling.Ignore)]
public string RcLive;
}
@ -170,17 +208,113 @@ namespace FModel.ViewModels
{
[JsonProperty("channel", NullValueHandling = NullValueHandling.Ignore)]
public string Channel;
[JsonProperty("customChannels", NullValueHandling = NullValueHandling.Ignore)]
public object[] CustomChannels;
[JsonProperty("deviceId", NullValueHandling = NullValueHandling.Ignore)]
public string DeviceId;
[JsonProperty("formatVersion", NullValueHandling = NullValueHandling.Ignore)]
public int FormatVersion;
[JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
public string Locale;
[JsonProperty("productLibraryDir", NullValueHandling = NullValueHandling.Ignore)]
public string ProductLibraryDir;
}
#pragma warning restore 649
// https://stackoverflow.com/questions/54767662/finding-game-launcher-executables-in-directory-c-sharp/67679123#67679123
public static class SteamDetection
{
private static readonly List<AppInfo> _steamApps;
static SteamDetection()
{
_steamApps = GetSteamApps(GetSteamLibs());
}
public static AppInfo GetSteamGameById(int id) => _steamApps.FirstOrDefault(app => app.Id == id.ToString());
private static List<AppInfo> GetSteamApps(IEnumerable<string> steamLibs)
{
var apps = new List<AppInfo>();
foreach (var files in steamLibs.Select(lib => Path.Combine(lib, "SteamApps")).Select(appMetaDataPath => Directory.GetFiles(appMetaDataPath, "*.acf")))
{
apps.AddRange(files.Select(GetAppInfo).Where(appInfo => appInfo != null));
}
return apps;
}
private static AppInfo GetAppInfo(string appMetaFile)
{
var fileDataLines = File.ReadAllLines(appMetaFile);
var dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var line in fileDataLines)
{
var match = Regex.Match(line, @"\s*""(?<key>\w+)""\s+""(?<val>.*)""");
if (!match.Success) continue;
var key = match.Groups["key"].Value;
var val = match.Groups["val"].Value;
dic[key] = val;
}
if (dic.Keys.Count <= 0) return null;
AppInfo appInfo = new();
var appId = dic["appid"];
var name = dic["name"];
var installDir = dic["installDir"];
var path = Path.GetDirectoryName(appMetaFile);
var libGameRoot = Path.Combine(path, "common", installDir);
if (!Directory.Exists(libGameRoot)) return null;
appInfo.Id = appId;
appInfo.Name = name;
appInfo.GameRoot = libGameRoot;
return appInfo;
}
private static List<string> GetSteamLibs()
{
var steamPath = GetSteamPath();
if (steamPath == null) return new List<string>();
var libraries = new List<string> { steamPath };
var listFile = Path.Combine(steamPath, @"steamapps\libraryfolders.vdf");
var lines = File.ReadAllLines(listFile);
foreach (var line in lines)
{
var match = Regex.Match(line, @"""(?<path>\w:\\\\.*)""");
if (!match.Success) continue;
var path = match.Groups["path"].Value.Replace(@"\\", @"\");
if (Directory.Exists(path))
{
libraries.Add(path);
}
}
return libraries;
}
private static string GetSteamPath() => (string) Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath", ""); // Win64, we don't support Win32
public class AppInfo
{
public string Id { get; internal set; }
public string Name { get; internal set; }
public string GameRoot { get; internal set; }
public override string ToString()
{
return $"{Name} ({Id})";
}
}
}
}
}

View File

@ -95,13 +95,6 @@ namespace FModel.ViewModels
set => SetProperty(ref _brFireflies, value, "ApolloGameplay_Fireflies");
}
private bool _brCorruptionZones;
public bool BrCorruptionZones
{
get => _brCorruptionZones;
set => SetProperty(ref _brCorruptionZones, value, "ApolloGameplay_CorruptionZones");
}
private bool _brCubeMovements;
public bool BrCubeMovements
{
@ -237,11 +230,7 @@ namespace FModel.ViewModels
if (!value.IsEnabled || !withMap && key == _FIRST_BITMAP)
continue;
SKPaint p = null;
if (key == "ApolloGameplay_CorruptionZones")
p = new SKPaint { BlendMode = SKBlendMode.Color };
c.DrawBitmap(value.Layer, new SKRect(0, 0, _widthHeight, _widthHeight), p);
c.DrawBitmap(value.Layer, new SKRect(0, 0, _widthHeight, _widthHeight));
}
return ret;
@ -287,9 +276,6 @@ namespace FModel.ViewModels
case "ApolloGameplay_Fireflies":
await LoadFireflies();
break;
case "ApolloGameplay_CorruptionZones":
await LoadCorruptionZones();
break;
case "ApolloGameplay_CubeMovements":
await LoadCubeMovements();
break;
@ -889,37 +875,6 @@ namespace FModel.ViewModels
});
}
private async Task LoadCorruptionZones()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Apollo/Environments/Landscape/Materials/Corruption/T_InitialCorruptionAreas.T_InitialCorruptionAreas", out UTexture2D corruption))
return;
var overlay = Utils.GetBitmap(corruption);
var width = overlay.Width;
var height = overlay.Height;
var rotatedBitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(rotatedBitmap);
c.Clear();
c.Translate(0, width);
c.RotateDegrees(-90);
c.DrawRect(0, 0, width, height, new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateCompose(SKShader.CreateSweepGradient(new SKPoint(width / 2f, height / 2f),new [] {
SKColor.Parse("#352176"), SKColor.Parse("#fd78fa"), SKColor.Parse("#f0b843"), SKColor.Parse("#e54a21")
}, null), SKShader.CreatePerlinNoiseTurbulence(0.05f, 0.05f, 4, 0), SKBlendMode.SrcOver)
});
c.DrawBitmap(overlay, 0, 0, new SKPaint { BlendMode = SKBlendMode.Darken });
rotatedBitmap.ClearToTransparent();
_bitmaps[0]["ApolloGameplay_CorruptionZones"] = new MapLayer {Layer = rotatedBitmap.Resize(_widthHeight, _widthHeight), IsEnabled = false};
});
}
/// <summary>
/// FortniteGame/Plugins/GameFeatures/CorruptionGameplay/Content/CorruptionGameplay_LevelOverlay.uasset
/// too lazy to filters

View File

@ -1,6 +1,12 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media.Media3D;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
@ -10,16 +16,27 @@ using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse_Conversion.Meshes;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse_Conversion.Textures;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using FModel.Views.Resources.Controls;
using HelixToolkit.SharpDX.Core;
using HelixToolkit.Wpf.SharpDX;
using Ookii.Dialogs.Wpf;
using Serilog;
using SharpDX;
using SkiaSharp;
using Camera = HelixToolkit.Wpf.SharpDX.Camera;
using Geometry3D = HelixToolkit.SharpDX.Core.Geometry3D;
using PerspectiveCamera = HelixToolkit.Wpf.SharpDX.PerspectiveCamera;
namespace FModel.ViewModels
{
public class ModelViewerViewModel : ViewModel
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
#region BINDINGS
private EffectsManager _effectManager;
public EffectsManager EffectManager
{
@ -55,162 +72,469 @@ namespace FModel.ViewModels
set => SetProperty(ref _zAxis, value);
}
private bool _showWireframe;
public bool ShowWireframe
private ModelAndCam _selectedModel; // selected mesh
public ModelAndCam SelectedModel
{
get => _showWireframe;
set => SetProperty(ref _showWireframe, value);
get => _selectedModel;
set
{
SetProperty(ref _selectedModel, value);
if (_selectedModel == null) return;
XAxis = _selectedModel.XAxis;
YAxis = _selectedModel.YAxis;
ZAxis = _selectedModel.ZAxis;
Cam.UpDirection = new Vector3D(0, 1, 0);
Cam.Position = _selectedModel.Position;
Cam.LookDirection = _selectedModel.LookDirection;
}
}
private Geometry3D _mesh;
public Geometry3D Mesh
private readonly ObservableCollection<ModelAndCam> _loadedModels; // mesh list
public ICollectionView LoadedModelsView { get; }
private bool _appendMode;
public bool AppendMode
{
get => _mesh;
set => SetProperty(ref _mesh, value);
get => _appendMode;
set => SetProperty(ref _appendMode, value);
}
private Material _meshMat;
public Material MeshMat
{
get => _meshMat;
set => SetProperty(ref _meshMat, value);
}
public bool CanAppend => SelectedModel != null;
public ObservableCollection<Geometry3D> Lods { get; }
public TextureModel HDRi { get; private set; }
#endregion
public ModelViewerViewModel()
private readonly FGame _game;
private readonly int[] _facesIndex = { 1, 0, 2 };
public ModelViewerViewModel(FGame game)
{
Lods = new ObservableCollection<Geometry3D>();
_game = game;
_loadedModels = new ObservableCollection<ModelAndCam>();
EffectManager = new DefaultEffectsManager();
Cam = new PerspectiveCamera
{
NearPlaneDistance = 0.1,
FarPlaneDistance = 10000000,
FieldOfView = 80
};
LoadedModelsView = new ListCollectionView(_loadedModels);
Cam = new PerspectiveCamera { NearPlaneDistance = 0.1, FarPlaneDistance = double.PositiveInfinity, FieldOfView = 90 };
LoadHDRi();
}
public void NextLod() => Mesh = Lods.Next(Mesh);
public void PreviousLod() => Mesh = Lods.Previous(Mesh);
public void LoadExport(UObject export)
private void LoadHDRi()
{
Lods.Clear();
Mesh = null;
MeshMat = PhongMaterials.Bisque;
switch (export)
{
case UStaticMesh st:
LoadStaticMesh(st);
break;
case USkeletalMesh sk:
LoadSkeletalMesh(sk);
break;
default:
throw new ArgumentOutOfRangeException();
}
var cubeMap = Application.GetResourceStream(new Uri("/FModel;component/Resources/approaching_storm_cubemap.dds", UriKind.Relative));
HDRi = TextureModel.Create(cubeMap?.Stream);
}
private void LoadStaticMesh(UStaticMesh mesh)
public async Task LoadExport(UObject export)
{
if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0)
#if DEBUG
LoadHDRi();
#endif
ModelAndCam p;
if (AppendMode && CanAppend)
p = SelectedModel;
else
{
return;
p = new ModelAndCam(export);
_loadedModels.Add(p);
}
SetupCameraAndAxis(convertedMesh.BoundingBox.Min, convertedMesh.BoundingBox.Max);
var pushedMaterial = false;
foreach (var lod in convertedMesh.LODs)
bool valid = false;
await _threadWorkerView.Begin(_ =>
{
if (lod.SkipLod) continue;
PushLod(lod.Verts, lod.Indices.Value);
if (!pushedMaterial)
valid = export switch
{
PushMaterial(lod.Sections.Value);
pushedMaterial = true;
}
}
Mesh = Lods.First();
UStaticMesh st => TryLoadStaticMesh(st, p),
USkeletalMesh sk => TryLoadSkeletalMesh(sk, p),
UMaterialInstance mi => TryLoadMaterialInstance(mi, p),
_ => throw new ArgumentOutOfRangeException(nameof(export))
};
});
if (!valid) return;
SelectedModel = p;
}
private void LoadSkeletalMesh(USkeletalMesh mesh)
#region PUBLIC METHODS
public void RenderingToggle()
{
if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0)
if (SelectedModel == null) return;
foreach (var g in SelectedModel.Group3d)
{
return;
}
SetupCameraAndAxis(convertedMesh.BoundingBox.Min, convertedMesh.BoundingBox.Max);
var pushedMaterial = false;
foreach (var lod in convertedMesh.LODs)
{
if (lod.SkipLod) continue;
PushLod(lod.Verts, lod.Indices.Value);
if (!pushedMaterial)
{
PushMaterial(lod.Sections.Value);
pushedMaterial = true;
}
}
Mesh = Lods.First();
}
private void PushLod(CMeshVertex[] verts, FRawStaticIndexBuffer indices)
{
var builder = new MeshBuilder {TextureCoordinates = new Vector2Collection()};
for (var i = 0; i < verts.Length; i++)
{
builder.AddNode(
new Vector3(verts[i].Position.X, -verts[i].Position.Y, verts[i].Position.Z),
new Vector3(verts[i].Normal.X, verts[i].Normal.Y, verts[i].Normal.Z),
new Vector2(verts[i].UV.U, verts[i].UV.V));
}
for (var i = 0; i < indices.Length; i++)
{
builder.TriangleIndices.Add(indices[i]);
}
Lods.Add(builder.ToMesh());
}
private void PushMaterial(CMeshSection[] sections)
{
for (var j = 0; j < sections.Length; j++)
{
if (sections[j].Material == null || !sections[j].Material.TryLoad<UMaterialInterface>(out var unrealMaterial))
if (g is not MeshGeometryModel3D geometryModel)
continue;
var parameters = new CMaterialParams();
unrealMaterial.GetParams(parameters);
if (parameters.Diffuse is not UTexture2D diffuse) continue;
MeshMat = new DiffuseMaterial {DiffuseMap = new TextureModel(diffuse.Decode()?.Encode().AsStream())};
break;
geometryModel.IsRendering = !geometryModel.IsRendering;
}
}
private void SetupCameraAndAxis(FVector min, FVector max)
public void WirefreameToggle()
{
var minOfMin = min.Min();
var maxOfMax = max.Max();
Cam.UpDirection = new System.Windows.Media.Media3D.Vector3D(0, 0, 1);
Cam.Position = new System.Windows.Media.Media3D.Point3D(maxOfMax, maxOfMax, (minOfMin + maxOfMax) / 1.25);
Cam.LookDirection = new System.Windows.Media.Media3D.Vector3D(-Cam.Position.X, -Cam.Position.Y, 0);
if (SelectedModel == null) return;
foreach (var g in SelectedModel.Group3d)
{
if (g is not MeshGeometryModel3D geometryModel)
continue;
geometryModel.RenderWireframe = !geometryModel.RenderWireframe;
}
}
public void DiffuseOnlyToggle()
{
if (SelectedModel == null) return;
foreach (var g in SelectedModel.Group3d)
{
if (g is not MeshGeometryModel3D { Material: PBRMaterial mat })
continue;
//mat.RenderAmbientOcclusionMap = !mat.RenderAmbientOcclusionMap;
mat.RenderDisplacementMap = !mat.RenderDisplacementMap;
//mat.RenderEmissiveMap = !mat.RenderEmissiveMap;
mat.RenderEnvironmentMap = !mat.RenderEnvironmentMap;
mat.RenderIrradianceMap = !mat.RenderIrradianceMap;
mat.RenderRoughnessMetallicMap = !mat.RenderRoughnessMetallicMap;
mat.RenderShadowMap = !mat.RenderShadowMap;
mat.RenderNormalMap = !mat.RenderNormalMap;
}
}
public void FocusOnSelectedMesh()
{
Cam.AnimateTo(SelectedModel.Position, SelectedModel.LookDirection, new Vector3D(0, 1, 0), 500);
}
public void SaveLoadedModels()
{
if (_loadedModels.Count < 1) return;
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = true};
if (folderBrowser.ShowDialog() == false) return;
foreach (var model in _loadedModels)
{
var toSave = new CUE4Parse_Conversion.Exporter(model.Export, UserSettings.Default.TextureExportFormat, UserSettings.Default.LodExportFormat, UserSettings.Default.MeshExportFormat);
if (toSave.TryWriteToDir(new DirectoryInfo(folderBrowser.SelectedPath), out var savedFileName))
{
Log.Information("Successfully saved {FileName}", savedFileName);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true);
}
else
{
Log.Error("{FileName} could not be saved", savedFileName);
FLogger.AppendError();
FLogger.AppendText($"Could not save '{savedFileName}'", Constants.WHITE, true);
}
}
}
public void CopySelectedMaterialName()
{
if (SelectedModel is not { } m || m.SelectedGeometry is null)
return;
Clipboard.SetText(m.SelectedGeometry.Name.TrimEnd());
}
public async Task<bool> TryChangeSelectedMaterial(UMaterialInstance materialInstance)
{
if (SelectedModel is not { } model || model.SelectedGeometry is null)
return false;
PBRMaterial m = null;
await _threadWorkerView.Begin(_ =>
{
var (material, _, _) = LoadMaterial(materialInstance);
m = material;
});
if (m == null) return false;
model.SelectedGeometry.Material = m;
return true;
}
#endregion
private bool TryLoadMaterialInstance(UMaterialInstance materialInstance, ModelAndCam cam)
{
var builder = new MeshBuilder();
builder.AddSphere(Vector3.Zero, 10);
cam.TriangleCount = 1984; // no need to count
SetupCameraAndAxis(new FBox(new FVector(-11), new FVector(11)), cam);
var (m, isRendering, isTransparent) = LoadMaterial(materialInstance);
Application.Current.Dispatcher.Invoke(() =>
{
cam.Group3d.Add(new MeshGeometryModel3D
{
Transform = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(1,0,0), -90)),
Name = FixName(materialInstance.Name), Geometry = builder.ToMeshGeometry3D(),
Material = m, IsTransparent = isTransparent, IsRendering = isRendering
});
});
return true;
}
private bool TryLoadStaticMesh(UStaticMesh mesh, ModelAndCam cam)
{
if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0)
{
return false;
}
SetupCameraAndAxis(convertedMesh.BoundingBox, cam);
foreach (var lod in convertedMesh.LODs)
{
if (lod.SkipLod) continue;
PushLod(lod.Sections.Value, lod.Verts, lod.Indices.Value, cam);
break;
}
return true;
}
private bool TryLoadSkeletalMesh(USkeletalMesh mesh, ModelAndCam cam)
{
if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0)
{
return false;
}
SetupCameraAndAxis(convertedMesh.BoundingBox, cam);
foreach (var lod in convertedMesh.LODs)
{
if (lod.SkipLod) continue;
PushLod(lod.Sections.Value, lod.Verts, lod.Indices.Value, cam);
break;
}
return true;
}
private void PushLod(CMeshSection[] sections, CMeshVertex[] verts, FRawStaticIndexBuffer indices, ModelAndCam cam)
{
foreach (var section in sections) // each section is a mesh part with its own material
{
var builder = new MeshBuilder();
cam.TriangleCount += section.NumFaces; // NumFaces * 3 (triangle) = next section FirstIndex
for (var j = 0; j < section.NumFaces; j++) // draw a triangle for each face
{
foreach (var t in _facesIndex) // triangle face 1 then 0 then 2
{
var id = section.FirstIndex + j * 3 + t;
var vert = verts[indices[id]];
var p = new Vector3(vert.Position.X, vert.Position.Z, vert.Position.Y); // up direction is Y
var n = new Vector3(vert.Normal.X, vert.Normal.Z, vert.Normal.Y);
n.Normalize();
var uv = new Vector2(vert.UV.U, vert.UV.V);
builder.AddNode(p, n, uv);
builder.TriangleIndices.Add(j * 3 + t); // one mesh part is "j * 3 + t" use "id" if you're building the full mesh
}
}
if (section.Material == null || !section.Material.TryLoad<UMaterialInterface>(out var unrealMaterial))
continue;
var (m, isRendering, isTransparent) = LoadMaterial(unrealMaterial);
Application.Current.Dispatcher.Invoke(() =>
{
cam.Group3d.Add(new MeshGeometryModel3D
{
Name = FixName(unrealMaterial.Name), Geometry = builder.ToMeshGeometry3D(),
Material = m, IsTransparent = isTransparent, IsRendering = isRendering
});
});
}
}
private (PBRMaterial material, bool isRendering, bool isTransparent) LoadMaterial(UMaterialInterface unrealMaterial)
{
var m = new PBRMaterial { RenderShadowMap = true, EnableAutoTangent = true, RenderEnvironmentMap = true };
var parameters = new CMaterialParams();
unrealMaterial.GetParams(parameters);
var isRendering = !parameters.IsNull;
if (isRendering)
{
if (parameters.Diffuse is UTexture2D diffuse)
m.AlbedoMap = new TextureModel(diffuse.Decode()?.Encode().AsStream());
if (parameters.Normal is UTexture2D normal)
m.NormalMap = new TextureModel(normal.Decode()?.Encode().AsStream());
if (parameters.Specular is UTexture2D specular)
{
var mip = specular.GetFirstMip();
TextureDecoder.DecodeTexture(mip, specular.Format, specular.isNormalMap,
out var data, out var colorType);
switch (_game)
{
case FGame.FortniteGame:
{
// Fortnite's Specular Texture Channels
// R Specular
// G Metallic
// B Roughness
unsafe
{
var offset = 0;
fixed (byte* d = data)
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
{
d[offset] = 0;
(d[offset+1], d[offset+2]) = (d[offset+2], d[offset+1]); // swap G and B
offset += 4;
}
}
parameters.RoughnessValue = 1;
parameters.MetallicValue = 1;
break;
}
case FGame.ShooterGame:
{
// Valorant's Specular Texture Channels
// R Metallic
// G Specular
// B Roughness
unsafe
{
var offset = 0;
fixed (byte* d = data)
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
{
(d[offset], d[offset+2]) = (d[offset+2], d[offset]); // swap R and B
(d[offset], d[offset+1]) = (d[offset+1], d[offset]); // swap B and G
offset += 4;
}
}
parameters.RoughnessValue = 1;
parameters.MetallicValue = 1;
break;
}
case FGame.Gameface:
{
// GTA's Specular Texture Channels
// R Metallic
// G Roughness
// B Specular
unsafe
{
var offset = 0;
fixed (byte* d = data)
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
{
(d[offset], d[offset+2]) = (d[offset+2], d[offset]); // swap R and B
offset += 4;
}
}
break;
}
}
using var bitmap = new SKBitmap(new SKImageInfo(mip.SizeX, mip.SizeY, colorType, SKAlphaType.Unpremul));
unsafe
{
fixed (byte* p = data)
{
bitmap.SetPixels(new IntPtr(p));
}
}
// R -> AO G -> Roughness B -> Metallic
m.RoughnessMetallicMap = new TextureModel(bitmap.Encode(SKEncodedImageFormat.Png, 100).AsStream());
m.RoughnessFactor = parameters.RoughnessValue;
m.MetallicFactor = parameters.MetallicValue;
m.RenderAmbientOcclusionMap = parameters.SpecularValue > 0;
}
}
else
{
m.AlbedoColor = new Color4(1, 0, 0, 1);
}
return (m, isRendering, parameters.IsTransparent);
}
private void SetupCameraAndAxis(FBox box, ModelAndCam cam)
{
if (AppendMode && CanAppend) return;
var center = box.GetCenter();
var lineBuilder = new LineBuilder();
lineBuilder.AddLine(new Vector3(0, 0, 0), new Vector3(max.X, 0, 0));
XAxis = lineBuilder.ToLineGeometry3D();
lineBuilder.AddLine(new Vector3(box.Min.X, center.Z, center.Y), new Vector3(box.Max.X, center.Z, center.Y));
cam.XAxis = lineBuilder.ToLineGeometry3D();
lineBuilder = new LineBuilder();
lineBuilder.AddLine(new Vector3(0, 0, 0), new Vector3(0, max.Y, 0));
YAxis = lineBuilder.ToLineGeometry3D();
lineBuilder.AddLine(new Vector3(center.X, box.Min.Z, center.Y), new Vector3(center.X, box.Max.Z, center.Y));
cam.YAxis = lineBuilder.ToLineGeometry3D();
lineBuilder = new LineBuilder();
lineBuilder.AddLine(new Vector3(0, 0, 0), new Vector3(0, 0, max.Z));
ZAxis = lineBuilder.ToLineGeometry3D();
lineBuilder.AddLine(new Vector3(center.X, center.Z, box.Min.Y), new Vector3(center.X, center.Z, box.Max.Y));
cam.ZAxis = lineBuilder.ToLineGeometry3D();
cam.Position = new Point3D(box.Max.X + center.X * 2, center.Z, box.Min.Y + center.Y * 2);
cam.LookDirection = new Vector3D(-cam.Position.X + center.X, 0, -cam.Position.Z + center.Y);
}
private string FixName(string input)
{
if (input.Length < 1)
return "Material_Has_No_Name";
if (int.TryParse(input[0].ToString(), out _))
input = input[1..];
return input.Replace('-', '_');
}
public void Clear()
{
foreach (var g in _loadedModels.ToList())
{
g.Dispose();
_loadedModels.Remove(g);
}
}
}
public class ModelAndCam : ViewModel
{
public UObject Export { get; }
public Point3D Position { get; set; }
public Vector3D LookDirection { get; set; }
public Geometry3D XAxis { get; set; }
public Geometry3D YAxis { get; set; }
public Geometry3D ZAxis { get; set; }
public int TriangleCount { get; set; }
private MeshGeometryModel3D _selectedGeometry; // selected material
public MeshGeometryModel3D SelectedGeometry
{
get => _selectedGeometry;
set => SetProperty(ref _selectedGeometry, value);
}
private ObservableElement3DCollection _group3d; // material list
public ObservableElement3DCollection Group3d
{
get => _group3d;
set => SetProperty(ref _group3d, value);
}
public ModelAndCam(UObject export)
{
Export = export;
TriangleCount = 0;
Group3d = new ObservableElement3DCollection();
}
public void Dispose()
{
TriangleCount = 0;
SelectedGeometry = null;
foreach (var g in Group3d.ToList())
{
g.Dispose();
Group3d.Remove(g);
}
}
}
}

View File

@ -89,13 +89,6 @@ namespace FModel.ViewModels
set => SetProperty(ref _selectedDiscordRpc, value);
}
private EEnabledDisabled _selectedDirectoryStructure;
public EEnabledDisabled SelectedDirectoryStructure
{
get => _selectedDirectoryStructure;
set => SetProperty(ref _selectedDirectoryStructure, value);
}
private ECompressedAudio _selectedCompressedAudio;
public ECompressedAudio SelectedCompressedAudio
{
@ -110,13 +103,6 @@ namespace FModel.ViewModels
set => SetProperty(ref _selectedCosmeticStyle, value);
}
private EEnabledDisabled _selectedCosmeticDisplayAsset;
public EEnabledDisabled SelectedCosmeticDisplayAsset
{
get => _selectedCosmeticDisplayAsset;
set => SetProperty(ref _selectedCosmeticDisplayAsset, value);
}
private EMeshFormat _selectedMeshExportFormat;
public EMeshFormat SelectedMeshExportFormat
{
@ -145,10 +131,8 @@ namespace FModel.ViewModels
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
public ReadOnlyObservableCollection<EDiscordRpc> DiscordRpcs { get; private set; }
public ReadOnlyObservableCollection<EEnabledDisabled> DirectoryStructures { get; private set; }
public ReadOnlyObservableCollection<ECompressedAudio> CompressedAudios { get; private set; }
public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; }
public ReadOnlyObservableCollection<EEnabledDisabled> CosmeticDisplayAssets { get; private set; }
public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { get; private set; }
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
@ -166,10 +150,8 @@ namespace FModel.ViewModels
private List<FCustomVersion> _customVersionsSnapshot;
private Dictionary<string, bool> _optionsSnapshot;
private ELanguage _assetLanguageSnapshot;
private EEnabledDisabled _directoryStructureSnapshot;
private ECompressedAudio _compressedAudioSnapshot;
private EIconStyle _cosmeticStyleSnapshot;
private EEnabledDisabled _cosmeticDisplayAssetSnapshot;
private EMeshFormat _meshExportFormatSnapshot;
private ELodFormat _lodExportFormatSnapshot;
private ETextureFormat _textureExportFormatSnapshot;
@ -190,10 +172,8 @@ namespace FModel.ViewModels
_customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game];
_optionsSnapshot = UserSettings.Default.OverridedOptions[_game];
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
_directoryStructureSnapshot = UserSettings.Default.KeepDirectoryStructure;
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
_cosmeticDisplayAssetSnapshot = UserSettings.Default.CosmeticDisplayAsset;
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
@ -205,10 +185,8 @@ namespace FModel.ViewModels
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
SelectedAssetLanguage = _assetLanguageSnapshot;
SelectedDirectoryStructure = _directoryStructureSnapshot;
SelectedCompressedAudio = _compressedAudioSnapshot;
SelectedCosmeticStyle = _cosmeticStyleSnapshot;
SelectedCosmeticDisplayAsset = _cosmeticDisplayAssetSnapshot;
SelectedMeshExportFormat = _meshExportFormatSnapshot;
SelectedLodExportFormat = _lodExportFormatSnapshot;
SelectedTextureExportFormat = _textureExportFormatSnapshot;
@ -222,10 +200,8 @@ namespace FModel.ViewModels
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
DiscordRpcs = new ReadOnlyObservableCollection<EDiscordRpc>(new ObservableCollection<EDiscordRpc>(EnumerateDiscordRpcs()));
DirectoryStructures = new ReadOnlyObservableCollection<EEnabledDisabled>(new ObservableCollection<EEnabledDisabled>(EnumerateEnabledDisabled()));
CompressedAudios = new ReadOnlyObservableCollection<ECompressedAudio>(new ObservableCollection<ECompressedAudio>(EnumerateCompressedAudios()));
CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles()));
CosmeticDisplayAssets = new ReadOnlyObservableCollection<EEnabledDisabled>(new ObservableCollection<EEnabledDisabled>(EnumerateEnabledDisabled()));
MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat()));
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
@ -293,10 +269,8 @@ namespace FModel.ViewModels
UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions;
UserSettings.Default.OverridedOptions[_game] = SelectedOptions;
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
UserSettings.Default.KeepDirectoryStructure = SelectedDirectoryStructure;
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle;
UserSettings.Default.CosmeticDisplayAsset = SelectedCosmeticDisplayAsset;
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
@ -321,7 +295,6 @@ namespace FModel.ViewModels
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues(SelectedDiscordRpc.GetType()).Cast<EDiscordRpc>();
private IEnumerable<ECompressedAudio> EnumerateCompressedAudios() => Enum.GetValues(SelectedCompressedAudio.GetType()).Cast<ECompressedAudio>();
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues(SelectedCosmeticStyle.GetType()).Cast<EIconStyle>();
private IEnumerable<EEnabledDisabled> EnumerateEnabledDisabled() => Enum.GetValues(SelectedCosmeticDisplayAsset.GetType()).Cast<EEnabledDisabled>();
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues(SelectedMeshExportFormat.GetType()).Cast<EMeshFormat>();
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues(SelectedLodExportFormat.GetType()).Cast<ELodFormat>();
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues(SelectedTextureExportFormat.GetType()).Cast<ETextureFormat>();

View File

@ -1,4 +1,5 @@
using FModel.Extensions;
using System;
using FModel.Extensions;
using FModel.Framework;
using FModel.Settings;
using FModel.ViewModels.Commands;
@ -193,7 +194,7 @@ namespace FModel.ViewModels
{
var fileName = Path.ChangeExtension(Header, ".json");
var directory = Path.Combine(UserSettings.Default.OutputDirectory, "Saves",
UserSettings.Default.KeepDirectoryStructure == EEnabledDisabled.Enabled ? Directory : "", fileName).Replace('\\', '/');
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/');
if (!autoSave)
{
@ -242,7 +243,7 @@ namespace FModel.ViewModels
if (!HasImage) return;
var fileName = Path.ChangeExtension(Header, ".png");
var directory = Path.Combine(UserSettings.Default.OutputDirectory, "Textures",
UserSettings.Default.KeepDirectoryStructure == EEnabledDisabled.Enabled ? Directory : "", fileName!).Replace('\\', '/');
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
if (!autoSave)
{
@ -315,9 +316,19 @@ namespace FModel.ViewModels
public void AddTab(string header = null, string directory = null)
{
if (!CanAddTabs) return;
var h = header ?? "New Tab";
var d = directory ?? string.Empty;
if (SelectedTab is { Header : "New Tab" })
{
SelectedTab.Header = h;
SelectedTab.Directory = d;
return;
}
Application.Current.Dispatcher.Invoke(() =>
{
_tabItems.Add(new TabItem(header ?? "New Tab", directory ?? string.Empty));
_tabItems.Add(new TabItem(h, d));
SelectedTab = _tabItems.Last();
});
}
@ -339,9 +350,16 @@ namespace FModel.ViewModels
}
_tabItems.Remove(tabToDelete);
OnTabRemove?.Invoke(this, new TabEventArgs(tabToDelete));
});
}
public class TabEventArgs : EventArgs
{
public TabItem TabToRemove { get; set; }
public TabEventArgs(TabItem tab) { TabToRemove = tab; }
}
public event EventHandler OnTabRemove;
public void GoLeftTab() => SelectedTab = _tabItems.Previous(SelectedTab);
public void GoRightTab() => SelectedTab = _tabItems.Next(SelectedTab);

View File

@ -1,9 +1,9 @@
using FModel.Framework;
using FModel.Services;
using System;
using System.Threading;
using System.Threading.Tasks;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Views.Resources.Controls;
using Serilog;

View File

@ -39,9 +39,6 @@
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
<CheckBox Content="Fireflies" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrFireflies}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
<CheckBox Content="Corruption Zones" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrCorruptionZones}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}"
ToolTip="Saving the image with Corruption Zones enabled will smooth these ugly sharp edges"/>
<CheckBox Content="Cube Movements" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrCubeMovements}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
</StackPanel>

View File

@ -1,13 +1,14 @@
<adonisControls:AdonisWindow x:Class="FModel.Views.ModelViewer"
<adonisControls:AdonisWindow x:Class="FModel.Views.ModelViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
xmlns:helix="http://helix-toolkit.org/wpf/SharpDX"
WindowStartupLocation="CenterScreen" ResizeMode="CanResize" IconVisibility="Collapsed" Background="#262630"
PreviewKeyDown="OnWindowKeyDown"
WindowStartupLocation="CenterScreen" ResizeMode="CanResize" IconVisibility="Collapsed"
PreviewKeyDown="OnWindowKeyDown" Closing="OnClosing"
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.60'}"
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.50'}">
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.60'}">
<adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
<Setter Property="Title" Value="Model Viewer" />
@ -21,27 +22,118 @@
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid>
<helix:Viewport3DX EffectsManager="{Binding ModelViewer.EffectManager}" Camera="{Binding ModelViewer.Cam}"
IsChangeFieldOfViewEnabled="False" IsMoveEnabled="False" UseDefaultGestures="False"
ShowViewCube="False" ShowCameraTarget="False" ModelUpDirection="0,0,1"
EnableSSAO="True" MSAA="Maximum" FXAALevel="Ultra" SSAOQuality="High"
BackgroundColor="#262630">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="350" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<GroupBox Grid.Column="0" Padding="{adonisUi:Space 0}" Background="Transparent">
<DockPanel Margin="10">
<Grid DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Models" VerticalAlignment="Center" Margin="0 0 0 10" />
<ComboBox Grid.Row="0" Grid.Column="2" ItemsSource="{Binding ModelViewer.LoadedModelsView, IsAsync=True}"
SelectedItem="{Binding ModelViewer.SelectedModel, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Export.Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Triangles" VerticalAlignment="Center" Margin="0 0 0 10" />
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding ModelViewer.SelectedModel.TriangleCount, FallbackValue=0, StringFormat={}{0:### ### ### ###}}" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Materials" VerticalAlignment="Center" Margin="0 0 0 10" />
<TextBlock Grid.Row="2" Grid.Column="2" Text="{Binding ModelViewer.SelectedModel.Group3d.Count, FallbackValue=0, StringFormat={}{0:### ###}}" VerticalAlignment="Center" HorizontalAlignment="Right" />
<Grid Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="Focus" Click="OnFocusClick" />
<ToggleButton Grid.Row="0" Grid.Column="2" IsChecked="{Binding ModelViewer.AppendMode}" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarToggleButton}}">
<TextBlock Text="{Binding IsChecked, Converter={x:Static converters:BoolToToggleConverter.Instance},
StringFormat={}Append {0}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ToggleButton}}}" />
</ToggleButton>
<Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Click="Save"
Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}">
<TextBlock Text="{Binding ModelViewer.LoadedModelsView.Count, StringFormat={}Save All Loaded Models ({0})}" />
</Button>
</Grid>
</Grid>
<Separator DockPanel.Dock="Top" Tag="MATERIALS" Style="{StaticResource CustomSeparator}" />
<ListBox DockPanel.Dock="Top" Style="{StaticResource MaterialsListBox}">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Copy Name" Click="OnCopyClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Change Material" Click="OnChangeMaterialClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SwapIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</DockPanel>
</GroupBox>
<GridSplitter Grid.Column="1" ResizeDirection="Columns" Width="4" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}" />
<helix:Viewport3DX Grid.Column="2" EffectsManager="{Binding ModelViewer.EffectManager}" Camera="{Binding ModelViewer.Cam}"
IsChangeFieldOfViewEnabled="False" IsMoveEnabled="False" UseDefaultGestures="False" ShowViewCube="False"
ShowCameraTarget="False" FXAALevel="Ultra" MSAA="Maximum" BackgroundColor="#2A2B34"
EnableSSAO="True" SSAOIntensity="1" EnableSwapChainRendering="True">
<helix:Viewport3DX.InputBindings>
<MouseBinding Command="helix:ViewportCommands.Rotate" Gesture="LeftClick" />
<MouseBinding Command="helix:ViewportCommands.Zoom" Gesture="RightClick" />
<MouseBinding Command="helix:ViewportCommands.Pan" Gesture="MiddleClick" />
<KeyBinding Command="helix:ViewportCommands.ZoomExtents" Modifiers="Shift" Key="C"/>
</helix:Viewport3DX.InputBindings>
<helix:DirectionalLight3D Direction="0, 0, -1" Color="White" />
<helix:DirectionalLight3D Direction="0, -1, 0" Color="White"/>
<helix:EnvironmentMap3D Texture="{Binding ModelViewer.HDRi}" />
<helix:DirectionalLight3D Color="White" Direction="{Binding Camera.LookDirection,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type helix:Viewport3DX}}}" />
<helix:LineGeometryModel3D Geometry="{Binding ModelViewer.XAxis}" Color="#FC3854" />
<helix:LineGeometryModel3D Geometry="{Binding ModelViewer.YAxis}" Color="#85CB22" />
<helix:LineGeometryModel3D Geometry="{Binding ModelViewer.ZAxis}" Color="#388EED" />
<helix:LineGeometryModel3D Geometry="{Binding ModelViewer.SelectedModel.XAxis}" Color="#FC3854" />
<helix:LineGeometryModel3D Geometry="{Binding ModelViewer.SelectedModel.YAxis}" Color="#85CB22" />
<helix:LineGeometryModel3D Geometry="{Binding ModelViewer.SelectedModel.ZAxis}" Color="#388EED" />
<helix:MeshGeometryModel3D Geometry="{Binding ModelViewer.Mesh}"
Material="{Binding ModelViewer.MeshMat}"
RenderWireframe="{Binding ModelViewer.ShowWireframe}" />
<helix:GroupModel3D x:Name="MyAntiCrashGroup" ItemsSource="{Binding ModelViewer.SelectedModel.Group3d}" />
</helix:Viewport3DX>
</Grid>
</adonisControls:AdonisWindow>

View File

@ -1,13 +1,19 @@
using System.Windows.Input;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using AdonisUI.Controls;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
namespace FModel.Views
{
public partial class ModelViewer
{
private bool _messageShown;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public ModelViewer()
@ -16,16 +22,80 @@ namespace FModel.Views
InitializeComponent();
}
public void Load(UObject export) => _applicationView.ModelViewer.LoadExport(export);
public async void Load(UObject export) => await _applicationView.ModelViewer.LoadExport(export);
public async void Swap(UMaterialInstance materialInstance)
{
var sucess = await _applicationView.ModelViewer.TryChangeSelectedMaterial(materialInstance);
if (sucess)
{
_applicationView.CUE4Parse.ModelIsSwappingMaterial = false;
}
else
{
MessageBox.Show(new MessageBoxModel
{
Text = "An attempt to load a material failed.",
Caption = "Error",
Icon = MessageBoxImage.Error,
Buttons = MessageBoxButtons.OkCancel(),
IsSoundEnabled = false
});
}
}
private void OnClosing(object sender, CancelEventArgs e)
{
_applicationView.ModelViewer.Clear();
_applicationView.ModelViewer.AppendMode = false;
_applicationView.CUE4Parse.ModelIsSwappingMaterial = false;
MyAntiCrashGroup.ItemsSource = null; // <3
}
private void OnWindowKeyDown(object sender, KeyEventArgs e)
{
if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key))
_applicationView.ModelViewer.PreviousLod();
else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key))
_applicationView.ModelViewer.NextLod();
else if (e.Key == Key.W)
_applicationView.ModelViewer.ShowWireframe = !_applicationView.ModelViewer.ShowWireframe;
switch (e.Key)
{
case Key.W:
_applicationView.ModelViewer.WirefreameToggle();
break;
case Key.H:
_applicationView.ModelViewer.RenderingToggle();
break;
// case Key.D:
// _applicationView.ModelViewer.DiffuseOnlyToggle();
// break;
case Key.Decimal:
_applicationView.ModelViewer.FocusOnSelectedMesh();
break;
}
}
private void OnFocusClick(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.FocusOnSelectedMesh();
private void OnCopyClick(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.CopySelectedMaterialName();
private void Save(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.SaveLoadedModels();
private void OnChangeMaterialClick(object sender, RoutedEventArgs e)
{
_applicationView.CUE4Parse.ModelIsSwappingMaterial = true;
if (!_messageShown)
{
MessageBox.Show(new MessageBoxModel
{
Text = "Simply extract a material once FModel will be brought to the foreground. This message will be shown once per Model Viewer's lifetime, close it to begin.",
Caption = "How To Change Material?",
Icon = MessageBoxImage.Information,
IsSoundEnabled = false
});
_messageShown = true;
}
MainWindow.YesWeCats.Activate();
}
}
}

View File

@ -113,6 +113,6 @@
<avalonEdit:TextEditor x:Name="MyAvalonEditor" Grid.Row="0" Grid.RowSpan="2" SyntaxHighlighting="{Binding Highlighter}" Document="{Binding Document}"
FontFamily="Consolas" FontSize="{Binding FontSize}" IsReadOnly="True" ShowLineNumbers="True" Foreground="#DAE5F2"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}" PreviewMouseWheel="OnPreviewMouseWheel"
TextChanged="OnTextChanged" MouseHover="OnMouseHover" MouseHoverStopped="OnMouseHoverStopped" />
TextChanged="OnTextChanged" MouseHover="OnMouseHover" MouseHoverStopped="OnMouseHoverStopped" PreviewMouseUp="OnMouseRelease" />
</Grid>
</UserControl>

View File

@ -1,9 +1,13 @@
using System;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using CUE4Parse.Utils;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.ViewModels;
using ICSharpCode.AvalonEdit;
using SkiaSharp;
@ -20,6 +24,18 @@ namespace FModel.Views.Resources.Controls
private readonly Regex _hexColorRegex = new("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private readonly System.Windows.Controls.ToolTip _toolTip = new();
private readonly Dictionary<string, NavigationList<int>> _savedCarets = new();
private NavigationList<int> _caretsOffsets
{
get
{
if (MyAvalonEditor.Document != null)
return _savedCarets.GetOrAdd(MyAvalonEditor.Document.FileName, () => new NavigationList<int>());
else
return new NavigationList<int>();
}
}
private bool _ignoreCaret = true;
public AvalonEditor()
{
@ -30,6 +46,8 @@ namespace FModel.Views.Resources.Controls
YesWeSearch = WpfSuckMyDick;
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator());
MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator());
ApplicationService.ApplicationView.CUE4Parse.TabControl.OnTabRemove += OnTabClose;
}
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
@ -49,6 +67,19 @@ namespace FModel.Views.Resources.Controls
FindNext();
dc.SearchUp = old;
break;
case Key.System: // Alt
if (Keyboard.IsKeyDown(Key.Left))
{
if (_caretsOffsets.Count == 0) return;
MyAvalonEditor.CaretOffset = _caretsOffsets.MovePrevious;
MyAvalonEditor.TextArea.Caret.BringCaretToView();
} else if ((Keyboard.IsKeyDown(Key.Right)))
{
if (_caretsOffsets.Count == 0) return;
MyAvalonEditor.CaretOffset = _caretsOffsets.MoveNext;
MyAvalonEditor.TextArea.Caret.BringCaretToView();
}
break;
}
}
@ -88,8 +119,11 @@ namespace FModel.Views.Resources.Controls
if (sender is not TextEditor avalonEditor || DataContext is not TabItem tabItem ||
avalonEditor.Document == null || string.IsNullOrEmpty(avalonEditor.Document.Text))
return;
avalonEditor.Document.FileName = tabItem.Directory + '/' + StringExtensions.SubstringBeforeLast(tabItem.Header, '.');
if (!_savedCarets.ContainsKey(avalonEditor.Document.FileName))
_ignoreCaret = true;
avalonEditor.Document.FileName = tabItem.Directory + '/' + tabItem.Header.SubstringBeforeLast('.');
if (!tabItem.ShouldScroll) return;
var lineNumber = avalonEditor.Document.Text.GetLineNumber(tabItem.ScrollTrigger);
@ -186,5 +220,31 @@ namespace FModel.Views.Resources.Controls
{
((TabItem) DataContext).HasSearchOpen = false;
}
private void OnTabClose(object sender, EventArgs eventArgs)
{
if (sender is not TabControlViewModel tab|| eventArgs is not TabControlViewModel.TabEventArgs e)
return;
var fileName = e.TabToRemove.Document.FileName;
if (_savedCarets.ContainsKey(fileName))
_savedCarets.Remove(fileName);
}
private void SaveCaretLoc(int offset)
{
if (_ignoreCaret) { _ignoreCaret = false; return;} // first always point to the end of the file for some reason
if (_caretsOffsets.Count >= 10)
_caretsOffsets.RemoveAt(0);
if (!_caretsOffsets.Contains(offset))
{
_caretsOffsets.Add(offset);
_caretsOffsets.CurrentIndex = _caretsOffsets.Count-1;
}
}
private void OnMouseRelease(object sender, MouseButtonEventArgs e)
{
SaveCaretLoc(MyAvalonEditor.CaretOffset);
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Globalization;
using System.Windows.Data;
using SharpDX.Direct3D11;
namespace FModel.Views.Resources.Converters
{
public class BoolToFillModeConverter : IValueConverter
{
public static readonly BoolToFillModeConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
FillMode.Solid => true,
_ => false
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
true => FillMode.Solid,
_ => FillMode.Wireframe
};
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters
{
public class BoolToToggleConverter : IValueConverter
{
public static readonly BoolToToggleConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
true => "Enabled",
_ => "Disabled"
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -25,6 +25,8 @@ namespace FModel.Views.Resources.Converters
"MinecraftDungeons" => "Minecraft Dungeons",
"shoebill" => "Star Wars: Jedi Fallen Order",
"a99769d95d8f400baad1f67ab5dfe508" => "Core",
578080 => "PLAYERUNKNOWN'S BATTLEGROUNDS",
677620 => "Splitgate",
_ => value,
};
}

View File

@ -4,6 +4,7 @@
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:soundOut="clr-namespace:CSCore.SoundOut;assembly=CSCore"
xmlns:sharpDx="clr-namespace:SharpDX.Direct3D11;assembly=SharpDX.Direct3D11"
xmlns:audioControls="clr-namespace:FModel.Views.Resources.Controls.Aup"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
@ -36,7 +37,7 @@
<Geometry x:Key="BugIcon">M14,12H10V10H14M14,16H10V14H14M20,8H17.19C16.74,7.22 16.12,6.55 15.37,6.04L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.04,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6.04C7.88,6.55 7.26,7.22 6.81,8H4V10H6.09C6.04,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.04,15.67 6.09,16H4V18H6.81C7.85,19.79 9.78,21 12,21C14.22,21 16.15,19.79 17.19,18H20V16H17.91C17.96,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.96,10.33 17.91,10H20V8Z</Geometry>
<Geometry x:Key="GiftIcon">M22 10.92L19.26 9.33C21.9 7.08 19.25 2.88 16.08 4.31L15.21 4.68L15.1 3.72C15 2.64 14.44 1.87 13.7 1.42C12.06 .467 9.56 1.12 9.16 3.5L6.41 1.92C5.45 1.36 4.23 1.69 3.68 2.65L2.68 4.38C2.4 4.86 2.57 5.47 3.05 5.75L10.84 10.25L12.34 7.65L14.07 8.65L12.57 11.25L20.36 15.75C20.84 16 21.46 15.86 21.73 15.38L22.73 13.65C23.28 12.69 22.96 11.47 22 10.92M12.37 5C11.5 5.25 10.8 4.32 11.24 3.55C11.5 3.07 12.13 2.91 12.61 3.18C13.38 3.63 13.23 4.79 12.37 5M17.56 8C16.7 8.25 16 7.32 16.44 6.55C16.71 6.07 17.33 5.91 17.8 6.18C18.57 6.63 18.42 7.79 17.56 8M20.87 16.88C21.28 16.88 21.67 16.74 22 16.5V20C22 21.11 21.11 22 20 22H4C2.9 22 2 21.11 2 20V11H10.15L11 11.5V20H13V12.65L19.87 16.61C20.17 16.79 20.5 16.88 20.87 16.88Z</Geometry>
<Geometry x:Key="NoteIcon">M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM8 19h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1zm0-6h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1zM7 6c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1z</Geometry>
<Geometry x:Key="InfoIcon">M11.5,2C6.81,2,3,5.81,3,10.5S6.81,19,11.5,19H12v3c4.86-2.34,8-7,8-11.5C20,5.81,16.19,2,11.5,2z M11.48,16 c-0.59,0-1.05-0.47-1.05-1.05c0-0.59,0.47-1.04,1.05-1.04c0.59,0,1.04,0.45,1.04,1.04C12.52,15.53,12.08,16,11.48,16z M13.99,9.83 c-0.63,0.93-1.23,1.21-1.56,1.81c-0.08,0.14-0.13,0.26-0.16,0.49c-0.05,0.39-0.36,0.68-0.75,0.68h-0.03 c-0.44,0-0.79-0.38-0.75-0.82c0.03-0.28,0.09-0.57,0.25-0.84c0.41-0.73,1.18-1.16,1.63-1.8c0.48-0.68,0.21-1.94-1.14-1.94 c-0.61,0-1.01,0.32-1.26,0.7c-0.19,0.29-0.57,0.39-0.89,0.25l0,0c-0.42-0.18-0.6-0.7-0.34-1.07C9.5,6.55,10.35,6,11.47,6 c1.23,0,2.08,0.56,2.51,1.26C14.34,7.87,14.56,8.99,13.99,9.83z</Geometry>
<Geometry x:Key="InfoIcon">M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 15c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1zm1-8h-2V7h2v2z</Geometry>
<Geometry x:Key="TrashIcon">M6,19c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V7H6V19z M9.17,12.59c-0.39-0.39-0.39-1.02,0-1.41c0.39-0.39,1.02-0.39,1.41,0 L12,12.59l1.41-1.41c0.39-0.39,1.02-0.39,1.41,0s0.39,1.02,0,1.41L13.41,14l1.41,1.41c0.39,0.39,0.39,1.02,0,1.41 s-1.02,0.39-1.41,0L12,15.41l-1.41,1.41c-0.39,0.39-1.02,0.39-1.41,0c-0.39-0.39-0.39-1.02,0-1.41L10.59,14L9.17,12.59z M18,4h-2.5 l-0.71-0.71C14.61,3.11,14.35,3,14.09,3H9.91c-0.26,0-0.52,0.11-0.7,0.29L8.5,4H6C5.45,4,5,4.45,5,5s0.45,1,1,1h12 c0.55,0,1-0.45,1-1S18.55,4,18,4z</Geometry>
<Geometry x:Key="HomeIcon">M18,4v16H6V4H18 M18,2H6C4.9,2,4,2.9,4,4v16c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V4C20,2.9,19.1,2,18,2L18,2z M7,19h10v-6H7 V19z M10,10h4v1h3V5H7v6h3V10z</Geometry>
<Geometry x:Key="RegexIcon">M16,16.92C15.67,16.97 15.34,17 15,17C14.66,17 14.33,16.97 14,16.92V13.41L11.5,15.89C11,15.5 10.5,15 10.11,14.5L12.59,12H9.08C9.03,11.67 9,11.34 9,11C9,10.66 9.03,10.33 9.08,10H12.59L10.11,7.5C10.3,7.25 10.5,7 10.76,6.76V6.76C11,6.5 11.25,6.3 11.5,6.11L14,8.59V5.08C14.33,5.03 14.66,5 15,5C15.34,5 15.67,5.03 16,5.08V8.59L18.5,6.11C19,6.5 19.5,7 19.89,7.5L17.41,10H20.92C20.97,10.33 21,10.66 21,11C21,11.34 20.97,11.67 20.92,12H17.41L19.89,14.5C19.7,14.75 19.5,15 19.24,15.24V15.24C19,15.5 18.75,15.7 18.5,15.89L16,13.41V16.92H16V16.92M5,19A2,2 0 0,1 7,17A2,2 0 0,1 9,19A2,2 0 0,1 7,21A2,2 0 0,1 5,19H5Z</Geometry>
@ -65,6 +66,11 @@
<Geometry x:Key="LocateMeIcon">M11.71,17.99C8.53,17.84,6,15.22,6,12c0-3.31,2.69-6,6-6c3.22,0,5.84,2.53,5.99,5.71l-2.1-0.63C15.48,9.31,13.89,8,12,8 c-2.21,0-4,1.79-4,4c0,1.89,1.31,3.48,3.08,3.89L11.71,17.99z M22,12c0,0.3-0.01,0.6-0.04,0.9l-1.97-0.59C20,12.21,20,12.1,20,12 c0-4.42-3.58-8-8-8s-8,3.58-8,8s3.58,8,8,8c0.1,0,0.21,0,0.31-0.01l0.59,1.97C12.6,21.99,12.3,22,12,22C6.48,22,2,17.52,2,12 C2,6.48,6.48,2,12,2S22,6.48,22,12z M18.23,16.26l2.27-0.76c0.46-0.15,0.45-0.81-0.01-0.95l-7.6-2.28 c-0.38-0.11-0.74,0.24-0.62,0.62l2.28,7.6c0.14,0.47,0.8,0.48,0.95,0.01l0.76-2.27l3.91,3.91c0.2,0.2,0.51,0.2,0.71,0l1.27-1.27 c0.2-0.2,0.2-0.51,0-0.71L18.23,16.26z</Geometry>
<Geometry x:Key="MeshIcon">M12 2c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm8 7h-5v12c0 .55-.45 1-1 1s-1-.45-1-1v-5h-2v5c0 .55-.45 1-1 1s-1-.45-1-1V9H4c-.55 0-1-.45-1-1s.45-1 1-1h16c.55 0 1 .45 1 1s-.45 1-1 1z</Geometry>
<Geometry x:Key="MaterialIcon">M11 9h2v2h-2V9zm-2 2h2v2H9v-2zm4 0h2v2h-2v-2zm2-2h2v2h-2V9zM7 9h2v2H7V9zm12-6H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 18H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2zm2-7h-2v2h2v2h-2v-2h-2v2h-2v-2h-2v2H9v-2H7v2H5v-2h2v-2H5V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v5z</Geometry>
<Geometry x:Key="VisibleIcon">M12 4C7 4 2.73 7.11 1 11.5 2.73 15.89 7 19 12 19s9.27-3.11 11-7.5C21.27 7.11 17 4 12 4zm0 12.5c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z</Geometry>
<Geometry x:Key="NotVisibleIcon">M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24zM2.71 3.16c-.39.39-.39 1.02 0 1.41l1.97 1.97C3.06 7.83 1.77 9.53 1 11.5 2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l2.72 2.72c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L4.13 3.16c-.39-.39-1.03-.39-1.42 0zM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5zm2.97-5.33c-.15-1.4-1.25-2.49-2.64-2.64l2.64 2.64z</Geometry>
<Geometry x:Key="WireframeIcon">M3 5h2V3c-1.1 0-2 .9-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-.9-2-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM8 17h8c.55 0 1-.45 1-1V8c0-.55-.45-1-1-1H8c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1zm1-8h6v6H9V9z</Geometry>
<Geometry x:Key="NotWireframeIcon">M3,13h2v-2H3V13z M7,21h2v-2H7V21z M13,3h-2v2h2V3z M19,3v2h2C21,3.9,20.1,3,19,3z M5,21v-2H3C3,20.1,3.9,21,5,21z M3,17h2 v-2H3V17z M11,21h2v-2h-2V21z M19,13h2v-2h-2V13z M19,9h2V7h-2V9z M15,5h2V3h-2V5z M7.83,5L7,4.17V3h2v2H7.83z M19.83,17L19,16.17 V15h2v2H19.83z M9,15v-3.17L12.17,15H9z M2.1,3.51c-0.39,0.39-0.39,1.02,0,1.41L4.17,7H3v2h2V7.83l2,2V16c0,0.55,0.45,1,1,1h6.17 l2,2H15v2h2v-1.17l2.07,2.07c0.39,0.39,1.02,0.39,1.41,0c0.39-0.39,0.39-1.02,0-1.41L3.51,3.51C3.12,3.12,2.49,3.12,2.1,3.51z M17,8c0-0.55-0.45-1-1-1H9.83l2,2H15v3.17l2,2V8z</Geometry>
<Geometry x:Key="SwapIcon">M16 17.01V11c0-.55-.45-1-1-1s-1 .45-1 1v6.01h-1.79c-.45 0-.67.54-.35.85l2.79 2.78c.2.19.51.19.71 0l2.79-2.78c.32-.31.09-.85-.35-.85H16zM8.65 3.35L5.86 6.14c-.32.31-.1.85.35.85H8V13c0 .55.45 1 1 1s1-.45 1-1V6.99h1.79c.45 0 .67-.54.35-.85L9.35 3.35c-.19-.19-.51-.19-.7 0z</Geometry>
<Style x:Key="TabItemFillSpace" TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="Width">
@ -612,6 +618,86 @@
</Style.Triggers>
</Style>
<Style x:Key="MaterialsListBox" TargetType="ListBox" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="ItemsSource" Value="{Binding ModelViewer.SelectedModel.Group3d, IsAsync=True}" />
<Setter Property="SelectedItem" Value="{Binding ModelViewer.SelectedModel.SelectedGeometry}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode" Value="NeverExpand"/>
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement" Value="Docked"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="25" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="/FModel;component/Resources/materialicon.png" Width="16" Height="16" Margin="5 0" HorizontalAlignment="Center" />
<TextBlock Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding Name}" TextTrimming="CharacterEllipsis" />
<ToggleButton Grid.Column="3" IsChecked="{Binding IsRendering}" Padding="3" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarToggleButton}}">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path x:Name="SvgIcon1" Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource VisibleIcon}" />
</Canvas>
</Viewbox>
</ToggleButton>
<ToggleButton Grid.Column="4" IsChecked="{Binding FillMode, Converter={x:Static converters:BoolToFillModeConverter.Instance}}" Padding="3" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarToggleButton}}">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path x:Name="SvgIcon2" Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource WireframeIcon}" />
</Canvas>
</Viewbox>
</ToggleButton>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsRendering}" Value="True">
<Setter TargetName="SvgIcon1" Property="Data" Value="{StaticResource VisibleIcon}" />
<Setter TargetName="SvgIcon1" Property="Fill" Value="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</DataTrigger>
<DataTrigger Binding="{Binding IsRendering}" Value="False">
<Setter TargetName="SvgIcon1" Property="Data" Value="{StaticResource NotVisibleIcon}" />
<Setter TargetName="SvgIcon1" Property="Fill" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
</DataTrigger>
<DataTrigger Binding="{Binding FillMode}" Value="{x:Static sharpDx:FillMode.Solid}">
<Setter TargetName="SvgIcon2" Property="Data" Value="{StaticResource WireframeIcon}" />
<Setter TargetName="SvgIcon2" Property="Fill" Value="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</DataTrigger>
<DataTrigger Binding="{Binding FillMode}" Value="{x:Static sharpDx:FillMode.Wireframe}">
<Setter TargetName="SvgIcon2" Property="Data" Value="{StaticResource NotWireframeIcon}" />
<Setter TargetName="SvgIcon2" Property="Fill" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="0" />
</Style>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ModelViewer.SelectedModel.Group3d.Count, FallbackValue=0}" Value="0">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<TextBlock Text="No material found in the mesh" FontWeight="SemiBold" TextAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="GameFilesTabControl" TargetType="TabControl" BasedOn="{StaticResource {x:Type TabControl}}">
<Setter Property="ItemsSource" Value="{Binding CUE4Parse.TabControl.TabsItems, IsAsync=True}" />
<Setter Property="SelectedItem" Value="{Binding CUE4Parse.TabControl.SelectedTab}" />

View File

@ -127,14 +127,9 @@
</Grid>
<TextBlock Grid.Row="10" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Auto Export &#38; Save assets inside their game directory" />
<ComboBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.DirectoryStructures}" SelectedItem="{Binding SettingsView.SelectedDirectoryStructure, 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>
<CheckBox Grid.Row="10" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 10"/>
<TextBlock Grid.Row="11" Grid.Column="0" Text="Compressed Audio" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="What to do when encountering a compressed audio file" />
<ComboBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.CompressedAudios}" SelectedItem="{Binding SettingsView.SelectedCompressedAudio, Mode=TwoWay}"
@ -238,14 +233,9 @@
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Cosmetic Shop Icon" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding SettingsView.CosmeticDisplayAssets}" SelectedItem="{Binding SettingsView.SelectedCosmeticDisplayAsset, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Row="1" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding CosmeticDisplayAsset, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 10"/>
</Grid>
</Grid>
</DataTemplate>
@ -290,8 +280,15 @@
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}"
<TextBlock Grid.Row="2" Grid.Column="0" Text="Save Skeletons as Empty Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="2" Grid.Column="2" 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 10"/>
<Separator Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="4" Grid.Column="2" 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>
@ -323,7 +320,6 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -364,32 +360,29 @@
<TextBlock Grid.Row="10" Grid.Column="0" Text="Auto Save Textures *" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="10" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AutoSaveTextures, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="11" Grid.Column="0" Text="Auto Save Materials *" VerticalAlignment="Center" Margin="0 0 0 5" />
<TextBlock Grid.Row="11" Grid.Column="0" Text="Auto Save Animations *" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="11" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AutoSaveMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="12" Grid.Column="0" Text="Auto Save Meshes *" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="12" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AutoSaveMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="13" Grid.Column="0" Text="Auto Save Animations *" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="13" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AutoSaveAnimations, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="14" Grid.Column="0" Text="Auto Open Sounds *" VerticalAlignment="Center" />
<controls:HotkeyTextBox Grid.Row="14" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}"
<TextBlock Grid.Row="12" Grid.Column="0" Text="Auto Open Sounds *" VerticalAlignment="Center" />
<controls:HotkeyTextBox Grid.Row="12" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}"
HotKey="{Binding AutoOpenSounds, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="13" Grid.Column="0" Text="Auto Open Meshes &amp; Materials *" VerticalAlignment="Center" />
<controls:HotkeyTextBox Grid.Row="13" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}"
HotKey="{Binding AutoOpenMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<Separator Grid.Row="15" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" />
<Separator Grid.Row="14" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="16" Grid.Column="0" Text="Add Audio File" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="16" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
<TextBlock Grid.Row="15" Grid.Column="0" Text="Add Audio File" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="15" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AddAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="17" Grid.Column="0" Text="Play / Pause Current Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="17" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
<TextBlock Grid.Row="16" Grid.Column="0" Text="Play / Pause Current Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="16" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding PlayPauseAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="18" Grid.Column="0" Text="Previous Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="18" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
<TextBlock Grid.Row="17" Grid.Column="0" Text="Previous Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="17" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding PreviousAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="19" Grid.Column="0" Text="Next Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="19" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
<TextBlock Grid.Row="18" Grid.Column="0" Text="Next Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="18" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding NextAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
</Grid>
</DataTemplate>

View File

@ -1,4 +1,4 @@
using System.Windows;
using System.Windows;
using System.Windows.Controls;
using FModel.Services;
using FModel.Settings;

View File

@ -1,14 +1,12 @@
# FModel [![Discord](https://discordapp.com/api/guilds/637265123144237061/widget.png?style=shield)](https://discord.gg/fdkNYYQ)
&ensp;&ensp;&ensp;&ensp;Open-source software for exploring Unreal Engine games' files. From seeing the properties of an asset to listening to your favorite audio files, it has never been easier to navigate inside a game's assets. FModel supports tens of file types and asset types, to display the most information possible and it also has the ability to let you <kbd>CTRL+LMB</kbd> on an asset path to display its properties too.
Beginner-friendly and open-source software for data-mining games made with Unreal Engine.
<img src="https://user-images.githubusercontent.com/26126862/119065662-52534800-b9de-11eb-85fd-a47797daa062.png" align="center" alt="FModel">
## Installation
To use FModel, you need to have **[.NET 5](https://dotnet.microsoft.com/download/dotnet/thank-you/runtime-desktop-5.0.11-windows-x64-installer)** installed on your computer
* **[Download](https://github.com/iAmAsval/FModel/releases/latest/download/FModel.zip)** the latest release.
* **Extract FModel.exe** somewhere on your PC and launch it
Follow [our documentation](https://fmodel.app/docs#installation)
## Authors