mirror of
https://github.com/4sval/FModel.git
synced 2026-04-26 08:13:27 -05:00
commit
bc2285adf0
164
.editorconfig
Normal file
164
.editorconfig
Normal 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
|
||||
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
34
FModel/Framework/NavigationList.cs
Normal file
34
FModel/Framework/NavigationList.cs
Normal 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]; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 & 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 & 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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
FModel/Resources/approaching_storm_cubemap.dds
Normal file
BIN
FModel/Resources/approaching_storm_cubemap.dds
Normal file
Binary file not shown.
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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})";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
FModel/Views/Resources/Converters/BoolToFillModeConverter.cs
Normal file
30
FModel/Views/Resources/Converters/BoolToFillModeConverter.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
25
FModel/Views/Resources/Converters/BoolToToggleConverter.cs
Normal file
25
FModel/Views/Resources/Converters/BoolToToggleConverter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}" />
|
||||
|
|
|
|||
|
|
@ -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 & 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 & 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>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Windows;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
# FModel [](https://discord.gg/fdkNYYQ)
|
||||
|
||||
    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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user