Merge pull request #213 from iAmAsval/dev

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

164
.editorconfig Normal file
View File

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

View File

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

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

View File

@ -25,7 +25,7 @@ namespace FModel
{
[DllImport("kernel32.dll")]
private static extern bool AttachConsole(int dwProcessId);
protected override void OnStartup(StartupEventArgs e)
{
#if DEBUG
@ -98,7 +98,7 @@ namespace FModel
{
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
UserSettings.Default = new UserSettings();
ApplicationService.ApplicationView.Restart();
}
@ -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)
@ -131,4 +131,4 @@ namespace FModel
return string.Empty;
}
}
}
}

View File

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

View File

@ -33,14 +33,14 @@ namespace FModel.Creator.Bases.FN
{
// rarity
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
else GetRarity(Object.GetOrDefault<FName>("Rarity")); // default is uncommon
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
// preview
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
Preview = preview;
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
Preview = Utils.GetBitmap(itemDefinition);
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SidePanelIcon", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
Preview = Utils.GetBitmap(largePreview);
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
Preview = Utils.GetBitmap(s);
@ -78,7 +78,7 @@ namespace FModel.Creator.Bases.FN
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset == EEnabledDisabled.Enabled);
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
@ -165,44 +165,11 @@ namespace FModel.Creator.Bases.FN
}
}
private void GetRarity(FName r)
private void GetRarity(EFortRarity r)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
var rarity = EFortRarity.Uncommon;
switch (r.Text)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
rarity = EFortRarity.Common;
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
rarity = EFortRarity.Rare;
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
rarity = EFortRarity.Epic;
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
rarity = EFortRarity.Legendary;
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
rarity = EFortRarity.Mythic;
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
rarity = EFortRarity.Unattainable;
break;
}
if (export.GetByIndex<FStructFallback>((int) rarity) is { } data &&
if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
data.TryGetValue(out FLinearColor color1, "Color1") &&
data.TryGetValue(out FLinearColor color2, "Color2") &&
data.TryGetValue(out FLinearColor color3, "Color3"))
@ -308,4 +275,4 @@ namespace FModel.Creator.Bases.FN
}
}
}
}
}

View File

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

View File

@ -2,6 +2,8 @@
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
@ -72,9 +74,15 @@ namespace FModel.Creator.Bases.FN
Description += "\n" + completionText.Text;
if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "SidePanelIcon", "EntryListIcon", "ToastIcon"))
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") &&
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out UObject iconObject))
{
Preview = Utils.GetBitmap(tandemIcon);
Preview = iconObject switch
{
UTexture2D text => Utils.GetBitmap(text),
UMaterialInstanceConstant mat => Utils.GetBitmap(mat),
_ => Preview
};
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
@ -46,7 +46,7 @@ namespace FModel.Creator
var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}";
if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition
{
if (!TryLoadObject($"{path[..path.LastIndexOf('_')]}_{path.SubstringAfterLast('_').ToLower()}", out material)) // Try to get MI with lowercase obfuscation
if (!TryLoadObject($"{path[..path.LastIndexOf('_')]}_{path.SubstringAfterLast('_').ToLower()}", out material)) // Try to get MI with lowercase obfuscation
TryLoadObject(path[..path.LastIndexOf('_')], out material); // hopefully gets obfuscated item definition
}
@ -93,6 +93,7 @@ namespace FModel.Creator
case "TextureB":
case "OfferImage":
case "KeyArtTexture":
case "NPC-Portrait":
{
return GetBitmap(texture);
}
@ -122,11 +123,11 @@ namespace FModel.Creator
me.ScalePixels(pixmap, SKFilterQuality.Medium);
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;
@ -274,4 +275,4 @@ namespace FModel.Creator
return ret;
}
}
}
}

View File

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

View File

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

View File

@ -1,7 +1,7 @@

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

View File

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

View File

@ -85,4 +85,4 @@ namespace FModel
return -d < n && d > n;
}
}
}
}

View File

@ -64,6 +64,15 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Archives Info" Command="{Binding MenuCommand}" CommandParameter="Directory_ArchivesInfo" IsEnabled="{Binding IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Assets">
<MenuItem Header="Search" IsEnabled="{Binding IsReady}" InputGestureText="Ctrl+Shift+F" Click="OnSearchViewClick">
@ -147,18 +156,15 @@
<MenuItem Header="Save Textures" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveTextures, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveTextures, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Save Materials" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveMaterials, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveMaterials, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Save Meshes" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveMeshes, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveMeshes, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Save Animations" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveAnimations, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveAnimations, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Open Sounds" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoOpenSounds, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoOpenSounds, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Open Meshes &amp; Materials" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoOpenMeshes, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoOpenMeshes, Source={x:Static local:Settings.UserSettings.Default}}" />
</MenuItem>
</MenuItem>
<MenuItem Header="Views">
@ -333,14 +339,14 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Image Source="/FModel;component/Resources/label.png"
Width="16" Height="16" HorizontalAlignment="Center" Margin="0 0 3.5 0" />
@ -448,7 +454,7 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.AssetsList.Assets.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Assets Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FoldersView.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
@ -472,7 +478,7 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" ZIndex="1" HorizontalAlignment="Left" Margin="5 2 0 0">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -500,11 +506,11 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<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}">
@ -634,7 +640,7 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.Offset, ElementName=AssetsListName, FallbackValue=0, StringFormat='{}0x{0:X}'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Offset" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.Size, ElementName=AssetsListName, FallbackValue=0, Converter={x:Static converters:SizeToStringConverter.Instance}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
@ -664,7 +670,7 @@
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl Grid.Row="0" x:Name="TabControlName" Style="{StaticResource GameFilesTabControl}" />
<Expander Grid.Row="1" Margin="0 5 0 5" ExpandDirection="Down">
<Grid>
@ -673,7 +679,7 @@
<ColumnDefinition Width="2" />
<ColumnDefinition Width="24" />
</Grid.ColumnDefinitions>
<controls:CustomRichTextBox Grid.Column="0" Grid.ColumnSpan="3" x:Name="LogRtbName" Style="{StaticResource CustomRichTextBox}" />
<StackPanel Grid.Column="2" Orientation="Vertical" VerticalAlignment="Bottom" Margin="-5 -5 5 5">
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Open Output Folder" Padding="0,4,0,4"
@ -834,32 +840,6 @@
</StatusBarItem.Style>
<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>
@ -886,6 +866,19 @@
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="SND" />
</StatusBarItem>
<StatusBarItem Width="35" HorizontalContentAlignment="Stretch" ToolTip="Auto Open Meshes &amp; Materials Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoOpenMeshes, Source={x:Static local:Settings.UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="MSH" />
</StatusBarItem>
</StackPanel>
</StatusBarItem>
</StatusBar>

View File

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

Binary file not shown.

View File

@ -36,7 +36,7 @@ namespace FModel.Settings
{
if (File.Exists(FilePath)) File.Delete(FilePath);
}
private bool _showChangelog = true;
public bool ShowChangelog
{
@ -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,14 +275,17 @@ 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
{
get => _overridedUEVersion;
set => SetProperty(ref _overridedUEVersion, value);
}
private IDictionary<FGame, List<FCustomVersion>> _overridedCustomVersions = new Dictionary<FGame, List<FCustomVersion>>
{
{FGame.Unknown, null},
@ -300,14 +302,17 @@ 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
{
get => _overridedCustomVersions;
set => SetProperty(ref _overridedCustomVersions, value);
}
private IDictionary<FGame, Dictionary<string, bool>> _overridedOptions = new Dictionary<FGame, Dictionary<string, bool>>
{
{FGame.Unknown, null},
@ -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
{
@ -534,14 +538,21 @@ namespace FModel.Settings
get => _meshExportFormat;
set => SetProperty(ref _meshExportFormat, value);
}
private ELodFormat _lodExportFormat = ELodFormat.FirstLod;
public ELodFormat LodExportFormat
{
get => _lodExportFormat;
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
{
@ -549,4 +560,4 @@ namespace FModel.Settings
set => SetProperty(ref _textureExportFormat, value);
}
}
}
}

View File

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

View File

@ -27,7 +27,7 @@ namespace FModel.ViewModels.ApiEndpoints
private Game _game;
private readonly IDictionary<string, CommunityDesign> _communityDesigns = new Dictionary<string, CommunityDesign>();
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public FModelApi(IRestClient client) : base(client)
{
}
@ -70,7 +70,7 @@ namespace FModel.ViewModels.ApiEndpoints
{
return _backups ??= GetBackupsAsync(token, gameName).GetAwaiter().GetResult();
}
public async Task<Game> GetGamesAsync(CancellationToken token, string gameName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/games/{gameName}", Method.GET);
@ -78,7 +78,7 @@ namespace FModel.ViewModels.ApiEndpoints
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public Game GetGames(CancellationToken token, string gameName)
{
return _game ??= GetGamesAsync(token, gameName).GetAwaiter().GetResult();
@ -101,14 +101,14 @@ namespace FModel.ViewModels.ApiEndpoints
_communityDesigns[designName] = communityDesign;
return communityDesign;
}
public void CheckForUpdates(EUpdateMode updateMode)
{
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}");
}
private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
{
_infos = JsonConvert.DeserializeObject<Info>(args.RemoteData);
@ -122,7 +122,7 @@ namespace FModel.ViewModels.ApiEndpoints
};
}
}
private void CheckForUpdateEvent(UpdateInfoEventArgs args)
{
if (args is {CurrentVersion: { }})
@ -144,7 +144,7 @@ namespace FModel.ViewModels.ApiEndpoints
Buttons = MessageBoxButtons.YesNo(),
IsSoundEnabled = false
};
MessageBox.Show(messageBox);
if (messageBox.Result != MessageBoxResult.Yes) return;
@ -175,11 +175,11 @@ namespace FModel.ViewModels.ApiEndpoints
var request = new RestRequest(args.ChangelogURL, Method.GET);
var response = _client.Execute(request);
if (string.IsNullOrEmpty(response.Content)) return;
_applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}");
_applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog");
_applicationView.CUE4Parse.TabControl.SelectedTab.SetDocumentText(response.Content, false);
UserSettings.Default.ShowChangelog = false;
}
}
}
}

View File

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

View File

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

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
@ -45,14 +45,14 @@ namespace FModel.ViewModels.Commands
FLogger.AppendText("An encrypted file has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true);
return;
}
if (_applicationView.CUE4Parse.Game == FGame.FortniteGame &&
_applicationView.CUE4Parse.Provider.MappingsContainer == null)
{
FLogger.AppendError();
FLogger.AppendText("Mappings could not get pulled, extracting assets might not work properly. If so, press F12 or please restart.", Constants.WHITE, true);
}
#if DEBUG
var loadingTime = Stopwatch.StartNew();
#endif
@ -225,4 +225,4 @@ namespace FModel.ViewModels.Commands
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
}
}
}

View File

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

View File

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

View File

@ -31,49 +31,49 @@ namespace FModel.ViewModels
Parkour,
TimeTrials
}
public class MapViewerViewModel : ViewModel
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
#region BINDINGS
private bool _brPois;
public bool BrPois
{
get => _brPois;
set => SetProperty(ref _brPois, value, "ApolloGameplay_MapPois");
}
private bool _brLandmarks;
public bool BrLandmarks
{
get => _brLandmarks;
set => SetProperty(ref _brLandmarks, value, "ApolloGameplay_MapLandmarks");
}
private bool _brTagsLocation;
public bool BrTagsLocation
{
get => _brTagsLocation;
set => SetProperty(ref _brTagsLocation, value, "ApolloGameplay_TagsLocation");
}
private bool _brPatrolsPath;
public bool BrPatrolsPath
{
get => _brPatrolsPath;
set => SetProperty(ref _brPatrolsPath, value, "ApolloGameplay_PatrolsPath");
}
private bool _brUpgradeBenches;
public bool BrUpgradeBenches
{
get => _brUpgradeBenches;
set => SetProperty(ref _brUpgradeBenches, value, "ApolloGameplay_UpgradeBenches");
}
private bool _brPhonebooths;
public bool BrPhonebooths
{
@ -87,77 +87,70 @@ namespace FModel.ViewModels
get => _brVendingMachines;
set => SetProperty(ref _brVendingMachines, value, "ApolloGameplay_VendingMachines");
}
private bool _brFireflies;
public bool BrFireflies
{
get => _brFireflies;
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
{
get => _brCubeMovements;
set => SetProperty(ref _brCubeMovements, value, "ApolloGameplay_CubeMovements");
}
private bool _prLandmarks;
public bool PrLandmarks
{
get => _prLandmarks;
set => SetProperty(ref _prLandmarks, value, "PapayaGameplay_MapLandmarks");
}
private bool _prCannonball;
public bool PrCannonball
{
get => _prCannonball;
set => SetProperty(ref _prCannonball, value, "PapayaGameplay_CannonballGame");
}
private bool _prSkydive;
public bool PrSkydive
{
get => _prSkydive;
set => SetProperty(ref _prSkydive, value, "PapayaGameplay_SkydiveGame");
}
private bool _prShootingTargets;
public bool PrShootingTargets
{
get => _prShootingTargets;
set => SetProperty(ref _prShootingTargets, value, "PapayaGameplay_ShootingTargets");
}
private bool _prParkour;
public bool PrParkour
{
get => _prParkour;
set => SetProperty(ref _prParkour, value, "PapayaGameplay_ParkourGame");
}
private bool _prTimeTrials;
public bool PrTimeTrials
{
get => _prTimeTrials;
set => SetProperty(ref _prTimeTrials, value, "PapayaGameplay_TimeTrials");
}
private bool _prVendingMachines;
public bool PrVendingMachines
{
get => _prVendingMachines;
set => SetProperty(ref _prVendingMachines, value, "PapayaGameplay_VendingMachines");
}
private bool _prMusicBlocks;
public bool PrMusicBlocks
{
@ -177,7 +170,7 @@ namespace FModel.ViewModels
get => _mapImage;
set => SetProperty(ref _mapImage, value);
}
private BitmapImage _brLayerImage;
private BitmapImage _prLayerImage;
private BitmapImage _layerImage;
@ -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;
@ -253,7 +242,7 @@ namespace FModel.ViewModels
if (bool.TryParse(value.ToString(), out var b)) GenericToggle(propertyName, b);
return ret;
}
private async void GenericToggle(string key, bool enabled)
{
if (_bitmaps[MapIndex].TryGetValue(key, out var layer) && layer.Layer != null)
@ -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;
@ -360,11 +346,11 @@ namespace FModel.ViewModels
_layerImage = _prLayerImage;
break;
}
RaisePropertyChanged(nameof(MapImage));
RaisePropertyChanged(nameof(LayerImage));
}
private readonly SKPaint _textPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
@ -389,12 +375,12 @@ namespace FModel.ViewModels
var ny = (1 - (vector.X + mapRadius) / (mapRadius * 2)) * _widthHeight;
return new FVector2D(nx, ny);
}
private async Task LoadBrMiniMap()
{
if (_bitmaps[0].TryGetValue(_FIRST_BITMAP, out var brMap) && brMap.Layer != null)
return; // if map already loaded
await _threadWorkerView.Begin(_ =>
{
if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerBR.Default__UIMapManagerBR_C", out UObject mapManager) ||
@ -412,7 +398,7 @@ namespace FModel.ViewModels
{
if (_bitmaps[1].TryGetValue(_FIRST_BITMAP, out var prMap) && prMap.Layer != null)
return; // if map already loaded
await _threadWorkerView.Begin(_ =>
{
if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerPapaya.Default__UIMapManagerPapaya_C", out UObject mapManager) ||
@ -423,7 +409,7 @@ namespace FModel.ViewModels
_prMiniMapImage = GetImageSource(_bitmaps[1][_FIRST_BITMAP].Layer);
});
}
private async Task LoadQuestIndicatorData()
{
await _threadWorkerView.Begin(_ =>
@ -434,7 +420,7 @@ namespace FModel.ViewModels
using var pois = new SKCanvas(poisBitmap);
using var brLandmarks = new SKCanvas(brLandmarksBitmap);
using var prLandmarks = new SKCanvas(prLandmarksBitmap);
if (Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData", out UObject indicatorData) &&
indicatorData.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
{
@ -444,7 +430,7 @@ namespace FModel.ViewModels
!poiData.TryGetValue(out FText text, "Text") || string.IsNullOrEmpty(text.Text) ||
!poiData.TryGetValue(out FVector worldLocation, "WorldLocation") ||
!poiData.TryGetValue(out FName discoverBackend, "DiscoverObjectiveBackendName")) continue;
var shaper = new CustomSKShaper(_textPaint.Typeface);
var shapedText = shaper.Shape(text.Text, _textPaint);
@ -479,7 +465,7 @@ namespace FModel.ViewModels
_bitmaps[1]["PapayaGameplay_MapLandmarks"] = new MapLayer {Layer = prLandmarksBitmap, IsEnabled = false};
});
}
private async Task LoadPatrolsPath()
{
await _threadWorkerView.Begin(_ =>
@ -514,7 +500,7 @@ namespace FModel.ViewModels
vector = GetMapPosition(relativeLocation, _brRadius);
path.LineTo(vector.X, vector.Y);
}
c.DrawPath(path, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
@ -544,7 +530,7 @@ namespace FModel.ViewModels
var displayName = Utils.GetLocalizedResource("", "D998BEF44F051E0885C6C58565934BEA", "Cannonball");
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
@ -573,7 +559,7 @@ namespace FModel.ViewModels
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _textPaint);
@ -582,7 +568,7 @@ namespace FModel.ViewModels
_bitmaps[1]["PapayaGameplay_SkydiveGame"] = new MapLayer {Layer = skydiveBitmap, IsEnabled = false};
});
}
private async Task LoadShootingTargets()
{
await _threadWorkerView.Begin(_ =>
@ -604,7 +590,7 @@ namespace FModel.ViewModels
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
if (bDone) continue;
bDone = true;
c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _textPaint);
@ -637,7 +623,7 @@ namespace FModel.ViewModels
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
var path = new SKPath();
var exports = Utils.LoadExports($"/PapayaGameplay/LevelOverlays/{file}");
foreach (var export in exports)
@ -647,7 +633,7 @@ namespace FModel.ViewModels
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject root) ||
!root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _prRadius);
if (path.IsEmpty || export.TryGetValue(out bool startsTrial, "StartsTrial") && startsTrial)
{
@ -703,7 +689,7 @@ namespace FModel.ViewModels
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
if (!set.Add(name)) continue;
c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint);
}
@ -711,7 +697,7 @@ namespace FModel.ViewModels
_bitmaps[1]["PapayaGameplay_VendingMachines"] = new MapLayer {Layer = timeTrialsBitmap, IsEnabled = false};
});
}
private async Task LoadMusicBlocks()
{
await _threadWorkerView.Begin(_ =>
@ -733,7 +719,7 @@ namespace FModel.ViewModels
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
if (bDone) continue;
bDone = true;
c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _textPaint);
@ -742,7 +728,7 @@ namespace FModel.ViewModels
_bitmaps[1]["PapayaGameplay_MusicBlocks"] = new MapLayer {Layer = shootingTargetsBitmap, IsEnabled = false};
});
}
private async Task LoadUpgradeBenches()
{
await _threadWorkerView.Begin(_ =>
@ -770,7 +756,7 @@ namespace FModel.ViewModels
_bitmaps[0]["ApolloGameplay_UpgradeBenches"] = new MapLayer {Layer = upgradeBenchesBitmap, IsEnabled = false};
});
}
private async Task LoadPhonebooths()
{
await _threadWorkerView.Begin(_ =>
@ -798,7 +784,7 @@ namespace FModel.ViewModels
_bitmaps[0]["ApolloGameplay_Phonebooths"] = new MapLayer {Layer = phoneboothsBitmap, IsEnabled = false};
});
}
private async Task LoadBrVendingMachines()
{
await _threadWorkerView.Begin(_ =>
@ -827,7 +813,7 @@ namespace FModel.ViewModels
_bitmaps[0]["ApolloGameplay_VendingMachines"] = new MapLayer {Layer = vendingMachinesBitmap, IsEnabled = false};
});
}
private async Task LoadFireflies()
{
await _threadWorkerView.Begin(_ =>
@ -863,7 +849,7 @@ namespace FModel.ViewModels
_fillPaint.StrokeWidth = 5;
if (!Utils.TryLoadObject("FortniteGame/Content/Quests/QuestTagToLocationDataRows.QuestTagToLocationDataRows", out UDataTable locationData))
return;
var tagsLocationBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(tagsLocationBitmap);
@ -878,7 +864,7 @@ namespace FModel.ViewModels
var displayName = parts[^2];
if (!int.TryParse(parts[^1], out var _))
displayName += " " + parts[^1];
var vector = GetMapPosition(worldLocation, _brRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
@ -888,38 +874,7 @@ namespace FModel.ViewModels
_bitmaps[0]["ApolloGameplay_TagsLocation"] = new MapLayer {Layer = tagsLocationBitmap, IsEnabled = false};
});
}
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
@ -944,7 +899,7 @@ namespace FModel.ViewModels
var objectName = cubeMovementStaticPath.SubPathString.SubstringAfterLast(".");
if (!Utils.TryLoadObject($"{objectPath}.{objectName}", out UObject staticPath))
return;
DrawCubeMovements(c, staticPath, true);
}

View File

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

View File

@ -53,14 +53,14 @@ namespace FModel.ViewModels
get => _selectedUeVersion;
set => SetProperty(ref _selectedUeVersion, value);
}
private List<FCustomVersion> _selectedCustomVersions;
public List<FCustomVersion> SelectedCustomVersions
{
get => _selectedCustomVersions;
set => SetProperty(ref _selectedCustomVersions, value);
}
private Dictionary<string, bool> _selectedOptions;
public Dictionary<string, bool> SelectedOptions
{
@ -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,27 +103,20 @@ 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
{
get => _selectedMeshExportFormat;
set => SetProperty(ref _selectedMeshExportFormat, value);
}
private ELodFormat _selectedLodExportFormat;
public ELodFormat SelectedLodExportFormat
{
get => _selectedLodExportFormat;
set => SetProperty(ref _selectedLodExportFormat, value);
}
private ETextureFormat _selectedTextureExportFormat;
public ETextureFormat SelectedTextureExportFormat
{
@ -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()));
@ -238,7 +214,7 @@ namespace FModel.ViewModels
if (string.IsNullOrEmpty(gameName)) return;
_gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName);
});
if (_gamePreset?.Versions == null) return;
foreach (var version in _gamePreset.Versions.Keys)
{
@ -251,7 +227,7 @@ namespace FModel.ViewModels
if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return;
SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST);
SelectedUeVersion = (UE4Version)version.UeVer;
SelectedCustomVersions = new List<FCustomVersion>();
foreach (var (guid, v) in version.CustomVersions)
{
@ -264,7 +240,7 @@ namespace FModel.ViewModels
SelectedOptions[k] = v;
}
}
public void ResetPreset()
{
SelectedUeGame = _ueGameSnapshot;
@ -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,9 +295,8 @@ namespace FModel.ViewModels
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues(SelectedDiscordRpc.GetType()).Cast<EDiscordRpc>();
private IEnumerable<ECompressedAudio> EnumerateCompressedAudios() => Enum.GetValues(SelectedCompressedAudio.GetType()).Cast<ECompressedAudio>();
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues(SelectedCosmeticStyle.GetType()).Cast<EIconStyle>();
private IEnumerable<EEnabledDisabled> EnumerateEnabledDisabled() => Enum.GetValues(SelectedCosmeticDisplayAsset.GetType()).Cast<EEnabledDisabled>();
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues(SelectedMeshExportFormat.GetType()).Cast<EMeshFormat>();
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues(SelectedLodExportFormat.GetType()).Cast<ELodFormat>();
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues(SelectedTextureExportFormat.GetType()).Cast<ETextureFormat>();
}
}
}

View File

@ -1,4 +1,5 @@
using FModel.Extensions;
using System;
using FModel.Extensions;
using FModel.Framework;
using FModel.Settings;
using FModel.ViewModels.Commands;
@ -119,7 +120,7 @@ namespace FModel.ViewModels
SetProperty(ref _highlighter, value);
}
}
public byte[] ImageBuffer { get; private set; }
private BitmapImage _image;
@ -133,7 +134,7 @@ namespace FModel.ViewModels
RaisePropertyChanged("HasImage");
}
}
private bool _noAlpha;
public bool NoAlpha
{
@ -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)
{
@ -222,7 +223,7 @@ namespace FModel.ViewModels
public void SetImage(SKImage img)
{
_img = img;
using var data = _img.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100);
using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false);
var image = new BitmapImage();
@ -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);
@ -370,4 +388,4 @@ namespace FModel.ViewModels
yield return new TabItem("New Tab", string.Empty);
}
}
}
}

View File

@ -1,9 +1,9 @@
using FModel.Framework;
using FModel.Services;
using System;
using System.Threading;
using System.Threading.Tasks;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Views.Resources.Controls;
using Serilog;
@ -97,7 +97,7 @@ namespace FModel.ViewModels
CurrentCancellationTokenSource = null; // kill token
Log.Error("{Exception}", e);
FLogger.AppendError();
FLogger.AppendText(e.Message, Constants.WHITE, true);
FLogger.AppendText(" " + e.StackTrace.SubstringBefore('\n').Trim(), Constants.WHITE, true);
@ -116,4 +116,4 @@ namespace FModel.ViewModels
StatusChangeAttempted = false;
}
}
}
}

View File

@ -19,7 +19,7 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<controls:OnTagDataTemplateSelector x:Key="TagTemplateSelector" />
<DataTemplate x:Key="BrTemplate">
<StackPanel VerticalAlignment="Center" Margin="25 0">
@ -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>
@ -73,14 +70,14 @@
<ColumnDefinition Width="Auto" MinWidth="300" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TreeView Grid.Row="0" Grid.RowSpan="3" x:Name="MapTree" SelectedItemChanged="OnSelectedItemChanged"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}">
<TreeViewItem Tag="BrTemplate" IsSelected="True">
@ -108,14 +105,14 @@
</TreeViewItem.Header>
</TreeViewItem>
</TreeView>
<Grid Grid.Row="1" HorizontalAlignment="Stretch">
<ContentControl ContentTemplateSelector="{StaticResource TagTemplateSelector}" Content="{Binding SelectedItem.Tag, ElementName=MapTree}" />
</Grid>
<Button Grid.Row="2" Content="Save Image" Margin="5" IsEnabled="{Binding IsReady}" VerticalAlignment="Bottom" Click="OnClick" />
</Grid>
<Grid Grid.Column="1" HorizontalAlignment="Stretch">
<controls:MagnifierManager.Magnifier>
<controls:Magnifier Radius="200" ZoomFactor=".4" BorderBrush="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}" BorderThickness="1" />

View File

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

View File

@ -1,31 +1,101 @@
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()
{
DataContext = _applicationView;
InitializeComponent();
}
public void Load(UObject export) => _applicationView.ModelViewer.LoadExport(export);
public async void Load(UObject export) => await _applicationView.ModelViewer.LoadExport(export);
public async void Swap(UMaterialInstance materialInstance)
{
var sucess = await _applicationView.ModelViewer.TryChangeSelectedMaterial(materialInstance);
if (sucess)
{
_applicationView.CUE4Parse.ModelIsSwappingMaterial = false;
}
else
{
MessageBox.Show(new MessageBoxModel
{
Text = "An attempt to load a material failed.",
Caption = "Error",
Icon = MessageBoxImage.Error,
Buttons = MessageBoxButtons.OkCancel(),
IsSoundEnabled = false
});
}
}
private void OnClosing(object sender, CancelEventArgs e)
{
_applicationView.ModelViewer.Clear();
_applicationView.ModelViewer.AppendMode = false;
_applicationView.CUE4Parse.ModelIsSwappingMaterial = false;
MyAntiCrashGroup.ItemsSource = null; // <3
}
private void OnWindowKeyDown(object sender, KeyEventArgs e)
{
if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key))
_applicationView.ModelViewer.PreviousLod();
else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key))
_applicationView.ModelViewer.NextLod();
else if (e.Key == Key.W)
_applicationView.ModelViewer.ShowWireframe = !_applicationView.ModelViewer.ShowWireframe;
switch (e.Key)
{
case Key.W:
_applicationView.ModelViewer.WirefreameToggle();
break;
case Key.H:
_applicationView.ModelViewer.RenderingToggle();
break;
// case Key.D:
// _applicationView.ModelViewer.DiffuseOnlyToggle();
// break;
case Key.Decimal:
_applicationView.ModelViewer.FocusOnSelectedMesh();
break;
}
}
private void OnFocusClick(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.FocusOnSelectedMesh();
private void OnCopyClick(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.CopySelectedMaterialName();
private void Save(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.SaveLoadedModels();
private void OnChangeMaterialClick(object sender, RoutedEventArgs e)
{
_applicationView.CUE4Parse.ModelIsSwappingMaterial = true;
if (!_messageShown)
{
MessageBox.Show(new MessageBoxModel
{
Text = "Simply extract a material once FModel will be brought to the foreground. This message will be shown once per Model Viewer's lifetime, close it to begin.",
Caption = "How To Change Material?",
Icon = MessageBoxImage.Information,
IsSoundEnabled = false
});
_messageShown = true;
}
MainWindow.YesWeCats.Activate();
}
}
}
}

View File

@ -17,7 +17,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="3 0" HorizontalAlignment="Right" Width="500">
<Grid.Style>
<Style TargetType="{x:Type Grid}">
@ -34,7 +34,7 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" ZIndex="1" HorizontalAlignment="Left" Margin="5 2 0 0">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -113,6 +113,6 @@
<avalonEdit:TextEditor x:Name="MyAvalonEditor" Grid.Row="0" Grid.RowSpan="2" SyntaxHighlighting="{Binding Highlighter}" Document="{Binding Document}"
FontFamily="Consolas" FontSize="{Binding FontSize}" IsReadOnly="True" ShowLineNumbers="True" Foreground="#DAE5F2"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}" PreviewMouseWheel="OnPreviewMouseWheel"
TextChanged="OnTextChanged" MouseHover="OnMouseHover" MouseHoverStopped="OnMouseHoverStopped" />
TextChanged="OnTextChanged" MouseHover="OnMouseHover" MouseHoverStopped="OnMouseHoverStopped" PreviewMouseUp="OnMouseRelease" />
</Grid>
</UserControl>

View File

@ -1,9 +1,13 @@
using System;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using CUE4Parse.Utils;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.ViewModels;
using ICSharpCode.AvalonEdit;
using SkiaSharp;
@ -20,16 +24,30 @@ 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()
{
CommandBindings.Add(new CommandBinding(NavigationCommands.Search, (_, e) => FindNext(e.Parameter != null)));
InitializeComponent();
YesWeEditor = MyAvalonEditor;
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 + '/' + tabItem.Header.SubstringBeforeLast('.');
avalonEditor.Document.FileName = tabItem.Directory + '/' + StringExtensions.SubstringBeforeLast(tabItem.Header, '.');
if (!_savedCarets.ContainsKey(avalonEditor.Document.FileName))
_ignoreCaret = true;
if (!tabItem.ShouldScroll) return;
var lineNumber = avalonEditor.Document.Text.GetLineNumber(tabItem.ScrollTrigger);
@ -102,7 +136,7 @@ namespace FModel.Views.Resources.Controls
{
if (DataContext is not TabItem tabItem || Keyboard.Modifiers != ModifierKeys.Control)
return;
var fontSize = tabItem.FontSize + e.Delta / 50.0;
tabItem.FontSize = fontSize switch
{
@ -186,5 +220,31 @@ namespace FModel.Views.Resources.Controls
{
((TabItem) DataContext).HasSearchOpen = false;
}
private void OnTabClose(object sender, EventArgs eventArgs)
{
if (sender is not TabControlViewModel tab|| eventArgs is not TabControlViewModel.TabEventArgs e)
return;
var fileName = e.TabToRemove.Document.FileName;
if (_savedCarets.ContainsKey(fileName))
_savedCarets.Remove(fileName);
}
private void SaveCaretLoc(int offset)
{
if (_ignoreCaret) { _ignoreCaret = false; return;} // first always point to the end of the file for some reason
if (_caretsOffsets.Count >= 10)
_caretsOffsets.RemoveAt(0);
if (!_caretsOffsets.Contains(offset))
{
_caretsOffsets.Add(offset);
_caretsOffsets.CurrentIndex = _caretsOffsets.Count-1;
}
}
private void OnMouseRelease(object sender, MouseButtonEventArgs e)
{
SaveCaretLoc(MyAvalonEditor.CaretOffset);
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:soundOut="clr-namespace:CSCore.SoundOut;assembly=CSCore"
xmlns:sharpDx="clr-namespace:SharpDX.Direct3D11;assembly=SharpDX.Direct3D11"
xmlns:audioControls="clr-namespace:FModel.Views.Resources.Controls.Aup"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
@ -14,7 +15,7 @@
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI">
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<Geometry x:Key="StatusBarIcon">M22,9v6c0,1.1-0.9,2-2,2h-1l0-2h1V9H4v6h6v2H4c-1.1,0-2-0.9-2-2V9c0-1.1,0.9-2,2-2h16C21.1,7,22,7.9,22,9z M14.04,17.99 c0.18,0.39,0.73,0.39,0.91,0l0.63-1.4l1.4-0.63c0.39-0.18,0.39-0.73,0-0.91l-1.4-0.63l-0.63-1.4c-0.18-0.39-0.73-0.39-0.91,0 l-0.63,1.4l-1.4,0.63c-0.39,0.18-0.39,0.73,0,0.91l1.4,0.63L14.04,17.99z M16.74,13.43c0.1,0.22,0.42,0.22,0.52,0l0.36-0.8 l0.8-0.36c0.22-0.1,0.22-0.42,0-0.52l-0.8-0.36l-0.36-0.8c-0.1-0.22-0.42-0.22-0.52,0l-0.36,0.8l-0.8,0.36 c-0.22,0.1-0.22,0.42,0,0.52l0.8,0.36L16.74,13.43z</Geometry>
<Geometry x:Key="SearchIcon">M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z</Geometry>
<Geometry x:Key="DirectoryIcon">M19.71,15.71l-3.59,3.59c-0.63,0.63-1.71,0.18-1.71-0.71V16h-7c-1.1,0-2-0.9-2-2V5c0-0.55,0.45-1,1-1h0c0.55,0,1,0.45,1,1 v9h7v-2.59c0-0.89,1.08-1.34,1.71-0.71l3.59,3.59C20.1,14.68,20.1,15.32,19.71,15.71z</Geometry>
@ -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,7 +66,12 @@
<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">
<Setter.Value>
@ -110,7 +116,7 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CustomVerticalSeparator" TargetType="{x:Type Separator}" BasedOn="{StaticResource CustomSeparator}">
<Setter Property="Margin" Value="8 0"/>
<Setter Property="LayoutTransform">
@ -150,7 +156,7 @@
<ColumnDefinition Width="25" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image x:Name="ListImage" Source="/FModel;component/Resources/archive.png"
Width="16" Height="16" HorizontalAlignment="Center" Margin="0 0 3 0" />
<TextBlock Grid.Column="1" HorizontalAlignment="Left" Text="{Binding Name}" TextTrimming="CharacterEllipsis" />
@ -197,7 +203,7 @@
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="AudioFilesListBox" TargetType="ListBox" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="ItemsSource" Value="{Binding AudioPlayer.AudioFilesView, IsAsync=True}" />
<Setter Property="SelectedItem" Value="{Binding AudioPlayer.SelectedAudioFile}" />
@ -221,7 +227,7 @@
<ColumnDefinition Width="25" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" HorizontalAlignment="Left" Text="{Binding FileName}" TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Length, Converter={x:Static converters:SizeToStringConverter.Instance}}" />
</Grid>
@ -496,7 +502,7 @@
Grid.ColumnSpan="2"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="False">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
</Trigger>
@ -566,7 +572,7 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="ListImage" Source="/FModel;component/Resources/unknown_asset.png"
Width="16" Height="16" HorizontalAlignment="Center" Margin="0 0 3 0" />
<TextBlock Grid.Column="1" HorizontalAlignment="Left" Text="{Binding FullPath, Converter={x:Static converters:FullPathToFileConverter.Instance}}" />
@ -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}" />
@ -623,7 +709,7 @@
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding SelectedItem.TabCommand, ElementName=TabControlName}" CommandParameter="Open_Properties"/>
<MouseBinding MouseAction="MiddleClick" Command="{Binding SelectedItem.TabCommand, ElementName=TabControlName}" CommandParameter="{Binding}" />
</DockPanel.InputBindings>
<TextBlock DockPanel.Dock="Left" Text="{Binding Header}" TextTrimming="CharacterEllipsis" ToolTip="{Binding Header}">
<TextBlock.Width>
<MultiBinding Converter="{x:Static converters:TabSizeConverter.Instance}" ConverterParameter="6">
@ -649,7 +735,7 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<controls:AvalonEditor x:Name="DynamicArea" Grid.Column="0" Grid.ColumnSpan="3" DataContext="{Binding SelectedItem, ElementName=TabControlName}" />
<Image Grid.Column="3" Grid.ColumnSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" Stretch="Uniform"
Visibility="{Binding SelectedItem.HasImage, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}"
@ -907,16 +993,16 @@
ContentTemplate="{TemplateBinding HeaderTemplate}"
TextElement.Foreground="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type adonisControls:RippleHost}}}"
RecognizesAccessKey="True"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Margin="{Binding Margin, ElementName=ItemHeader}"/>
<TextBlock x:Name="RippleLayerInputGesturePresenter"
Grid.Column="2"
Text="{TemplateBinding InputGestureText}"
Text="{TemplateBinding InputGestureText}"
TextElement.Foreground="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type adonisControls:RippleHost}}}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Margin="{Binding Margin, ElementName=InputGesturePresenter}"/>
<Viewbox x:Name="RippleLayerArrowPresenter"
@ -959,16 +1045,16 @@
ContentTemplate="{TemplateBinding HeaderTemplate}"
TextElement.Foreground="{TemplateBinding Foreground}"
RecognizesAccessKey="True"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Margin="4, 0"/>
<TextBlock x:Name="InputGesturePresenter"
Grid.Column="3"
Text="{TemplateBinding InputGestureText}"
Text="{TemplateBinding InputGestureText}"
Foreground="{TemplateBinding Foreground}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Margin="4, 0"/>
<Viewbox x:Name="ArrowPresenter"
@ -1085,7 +1171,7 @@
<Setter Property="BorderBrush" TargetName="SpotlightLayer" Value="{Binding Path=(adonisExtensions:CursorSpotlightExtension.BorderBrush), RelativeSource={RelativeSource FindAncestor, AncestorType=MenuItem}}"/>
<Setter Property="BorderThickness" TargetName="SpotlightLayer" Value="{Binding BorderThickness, RelativeSource={RelativeSource FindAncestor, AncestorType=MenuItem}}"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Role" Value="TopLevelItem"/>
@ -1158,7 +1244,7 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CustomExpanderDownHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
@ -1174,7 +1260,7 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Type Expander}" TargetType="Expander" BasedOn="{StaticResource {x:Type Expander}}">
<Setter Property="IsExpanded" Value="{Binding IsLoggerExpanded, Source={x:Static local:Settings.UserSettings.Default}}"></Setter>
<Setter Property="Template">
@ -1240,7 +1326,7 @@
</Setter.Value>
</Setter>
</MultiTrigger>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="Margin" TargetName="HeaderSite" Value="0 0 0 5" />
<Trigger.EnterActions>
@ -1313,7 +1399,7 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CustomRichTextBox" TargetType="RichTextBox">
<Setter Property="Height" Value="175" />
<Setter Property="IsReadOnly" Value="True" />
@ -1324,7 +1410,7 @@
<Setter.Value>
<ControlTemplate>
<Border x:Name="Bd"
BorderBrush="{TemplateBinding BorderBrush}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True"
Background="{TemplateBinding Background}">
@ -1333,7 +1419,7 @@
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0" />
@ -1346,11 +1432,11 @@
</Style>
</Style.Resources>
</Style>
<ControlTemplate x:Key="CircleTemplate" TargetType="{x:Type controls:Magnifier}">
<Grid>
<Ellipse Fill="{TemplateBinding Background}" />
<Ellipse Stroke="{Binding Path=BorderBrush, RelativeSource={RelativeSource TemplatedParent}}"
<Ellipse Stroke="{Binding Path=BorderBrush, RelativeSource={RelativeSource TemplatedParent}}"
StrokeThickness="{Binding Path=BorderThickness, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static converters:BorderThicknessToStrokeThicknessConverter.Instance}}">
<Ellipse.Fill>
<VisualBrush x:Name="PART_VisualBrush" ViewboxUnits="Absolute" Visual="{Binding Path=Target, RelativeSource={RelativeSource TemplatedParent}}" />
@ -1372,7 +1458,7 @@
<Style TargetType="{x:Type controls:Magnifier}">
<Setter Property="IsHitTestVisible" Value="False" />
<Style.Triggers>
<Trigger Property="FrameType" Value="Circle">
<Setter Property="Template" Value="{StaticResource CircleTemplate}" />
@ -1385,7 +1471,7 @@
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="{x:Type ComboBoxItem}" TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}">
<Setter Property="Focusable" Value="False" />
</Style>
@ -1397,13 +1483,13 @@
<Style x:Key="{x:Type Button}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Focusable" Value="False" />
</Style>
<Style x:Key="{x:Static GridView.GridViewScrollViewerStyleKey}" TargetType="ScrollViewer" BasedOn="{StaticResource {x:Type ScrollViewer}}">
<Setter Property="HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode" Value="AlwaysExpand"/>
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement" Value="Docked"/>
</Style>
<Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}" BasedOn="{StaticResource {x:Type ScrollBar}}">
<Style.Triggers>
<Trigger Property="Orientation" Value="Horizontal">
@ -1434,7 +1520,7 @@
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="PlayPauseToolbarButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Content">
<Setter.Value>
@ -1452,7 +1538,7 @@
<Setter Property="ToolTip" Value="Play" />
<Style.Triggers>
<DataTrigger Binding="{Binding AudioPlayer.PlayedFile.PlaybackState, Mode=OneWay}" Value="{x:Static soundOut:PlaybackState.Playing}">
<Setter Property="ToolTip" Value="Pause" />
<Setter Property="Content">
@ -1529,7 +1615,7 @@
</Style.Triggers>
</Style>
<Style TargetType="{x:Type audioControls:Timeline}">
<Setter Property="ProgressBrush">
<Setter.Value>
@ -1561,12 +1647,12 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Rectangle Name="PART_MousePosition" Grid.Row="0" Grid.RowSpan="2" Grid.Column="0"
Fill="{TemplateBinding MousePositionBrush}" Width="2" Visibility="Collapsed" />
<Grid Name="PART_Length" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" ClipToBounds="True" SnapsToDevicePixels="False" />
<Grid Name="PART_ControlContainer" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<Border Name="PART_ProgressLine" BorderBrush="{TemplateBinding ProgressLineBrush}"
<Border Name="PART_ProgressLine" BorderBrush="{TemplateBinding ProgressLineBrush}"
Background="{TemplateBinding ProgressBrush}" Width="{TemplateBinding ActualWidth}"
VerticalAlignment="Stretch" HorizontalAlignment="Left" BorderThickness="0 0 1 0" />
</Grid>
@ -1576,7 +1662,7 @@
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type audioControls:SpectrumAnalyzer}">
<Setter Property="FrequencyBarCount" Value="75" />
<Setter Property="FrequencyBarBorderBrush" Value="#DAE5F2" />
@ -1603,7 +1689,7 @@
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type audioControls:Timeclock}">
<Setter Property="FontSize" Value="16" />
<Setter Property="VerticalAlignment" Value="Top" />
@ -1620,9 +1706,9 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Name="PART_Label"
Text="{TemplateBinding Label}"
<TextBlock Name="PART_Label"
Text="{TemplateBinding Label}"
FontFamily="{TemplateBinding LabelFont}"
Padding="0"
Foreground="{TemplateBinding LabelForeground}">
@ -1630,8 +1716,8 @@
<ScaleTransform ScaleX=".75" ScaleY=".75" />
</TextBlock.LayoutTransform>
</TextBlock>
<TextBlock Name="PART_Time"
Text="00:00:00.00"
<TextBlock Name="PART_Time"
Text="00:00:00.00"
Grid.Row="1"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
FontFamily="{TemplateBinding TimeFont}"
@ -1641,12 +1727,12 @@
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type folding:FoldingMargin}">
<Setter Property="FoldingMarkerBrush" Value="#262630" />
<Setter Property="FoldingMarkerBackgroundBrush" Value="#262630" />
<Setter Property="SelectedFoldingMarkerBrush" Value="#808080" />
<Setter Property="SelectedFoldingMarkerBackgroundBrush" Value="#2A2B34" />
</Style>
</ResourceDictionary>
</ResourceDictionary>

View File

@ -20,7 +20,7 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<controls:OnTagDataTemplateSelector x:Key="TagTemplateSelector" />
<DataTemplate x:Key="GeneralTemplate">
<Grid adonisExtensions:LayerExtension.Layer="2">
@ -46,7 +46,7 @@
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Output Directory *" VerticalAlignment="Center" Margin="0 0 0 5" />
<TextBox Grid.Row="0" Grid.Column="2" Text="{Binding OutputDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
<Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseOutput" Margin="0 0 0 5" />
@ -54,7 +54,7 @@
<TextBlock Grid.Row="1" Grid.Column="0" Text="Game Directory *" VerticalAlignment="Center" Margin="0 0 0 5" />
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding GameDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
<Button Grid.Row="1" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" Margin="0 0 0 5" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Update Mode" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Receive updates each time a new release is pushed to GitHub&#10;Receive updates each time a new commit is pushed to GitHub" />
<ComboBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.UpdateModes}" SelectedItem="{Binding SettingsView.SelectedUpdateMode, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
@ -64,7 +64,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Assets Language" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Language used and shown when parsing assets" />
<ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.AssetLanguages}" SelectedItem="{Binding SettingsView.SelectedAssetLanguage, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
@ -74,7 +74,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="4" Grid.Column="0" Text="Discord Rich Presence" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.DiscordRpcs}" SelectedItem="{Binding SettingsView.SelectedDiscordRpc, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
@ -84,14 +84,14 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Separator Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" Tag="ADVANCED"></Separator>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Presets" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Use a fine tuned preset for the game you're trying to load and its version" />
<ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.Presets}" SelectedItem="{Binding SettingsView.SelectedPreset, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" SelectionChanged="OnSelectionChanged" Margin="0 0 0 5">
</ComboBox>
<TextBlock Grid.Row="7" Grid.Column="0" Text="UE4 Versions *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the UE4 version to use when parsing assets" />
<ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.UeGames}" SelectedItem="{Binding SettingsView.SelectedUeGame, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" IsEnabled="{Binding SettingsView.EnableElements}"
@ -102,7 +102,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="8" Grid.Column="0" Text="UE4 Object Versions *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the UE4 object version to use when parsing assets" />
<ComboBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.UeVersions}" SelectedItem="{Binding SettingsView.SelectedUeVersion, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" IsEnabled="{Binding SettingsView.EnableElements}"
@ -113,7 +113,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="9" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" />
<Grid Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5">
<Grid.ColumnDefinitions>
@ -121,21 +121,16 @@
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="Custom Versions" Click="OpenCustomVersions" />
<Button Grid.Column="2" Content="Options" Click="OpenOptions" />
</Grid>
<TextBlock Grid.Row="10" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Auto Export &#38; Save assets inside their game directory" />
<ComboBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.DirectoryStructures}" SelectedItem="{Binding SettingsView.SelectedDirectoryStructure, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Row="10" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 10"/>
<TextBlock Grid.Row="11" Grid.Column="0" Text="Compressed Audio" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="What to do when encountering a compressed audio file" />
<ComboBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.CompressedAudios}" SelectedItem="{Binding SettingsView.SelectedCompressedAudio, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
@ -145,7 +140,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="12" Grid.Column="0" Text="AES Reload at Launch" VerticalAlignment="Center" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}">
<TextBlock.Style>
@ -226,7 +221,7 @@
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Cosmetic Style" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="0" Grid.Column="2" ItemsSource="{Binding SettingsView.CosmeticStyles}" SelectedItem="{Binding SettingsView.SelectedCosmeticStyle, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
@ -236,16 +231,11 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</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>
@ -269,7 +259,7 @@
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Mesh Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="0" Grid.Column="2" ItemsSource="{Binding SettingsView.MeshExportFormats}" SelectedItem="{Binding SettingsView.SelectedMeshExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
@ -279,7 +269,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Level Of Detail Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding SettingsView.LodExportFormats}" SelectedItem="{Binding SettingsView.SelectedLodExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
@ -289,9 +279,16 @@
</DataTemplate>
</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,23 +320,22 @@
<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="Left Switch on Directory Tab" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="0" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding DirLeftTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Right Switch on Directory Tab" VerticalAlignment="Center" />
<controls:HotkeyTextBox Grid.Row="1" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}"
HotKey="{Binding DirRightTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<Separator Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Left Switch on Asset Tab" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="3" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AssetLeftTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
@ -352,9 +348,9 @@
<TextBlock Grid.Row="6" Grid.Column="0" Text="Remove Selected Asset Tab" VerticalAlignment="Center" />
<controls:HotkeyTextBox Grid.Row="6" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}"
HotKey="{Binding AssetRemoveTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<Separator Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="8" Grid.Column="0" Text="Auto Export Data *" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="8" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AutoExportData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
@ -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}" />
<Separator Grid.Row="15" 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="13" Grid.Column="0" Text="Auto Open Meshes &amp; Materials *" VerticalAlignment="Center" />
<controls:HotkeyTextBox Grid.Row="13" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}"
HotKey="{Binding AutoOpenMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<Separator Grid.Row="14" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" />
<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>
@ -404,7 +397,7 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TreeView Grid.Row="0" Grid.Column="0" x:Name="SettingsTree" SelectedItemChanged="OnSelectedItemChanged"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}">
<TreeViewItem Tag="GeneralTemplate">
@ -471,11 +464,11 @@
</TreeViewItem.Header>
</TreeViewItem>
</TreeView>
<Grid Grid.Row="0" Grid.Column="1" Margin="{adonisUi:Space 1, 0.5}" HorizontalAlignment="Stretch">
<ContentControl ContentTemplateSelector="{StaticResource TagTemplateSelector}" Content="{Binding SelectedItem.Tag, ElementName=SettingsTree}" />
</Grid>
<Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" adonisExtensions:LayerExtension.IncreaseLayer="True"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer1BackgroundBrush}}">
<Grid Margin="30, 12, 6, 12">

View File

@ -1,4 +1,4 @@
using System.Windows;
using System.Windows;
using System.Windows.Controls;
using FModel.Services;
using FModel.Settings;
@ -118,8 +118,8 @@ namespace FModel.Views
var result = dictionary.ShowDialog();
if (!result.HasValue || !result.Value)
return;
_applicationView.SettingsView.SelectedOptions = dictionary.Options;
}
}
}
}

View File

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