Versioning Configuration

This commit is contained in:
iAmAsval 2021-08-09 21:10:19 +02:00
parent f462b183ee
commit 724c95aae1
11 changed files with 300 additions and 28 deletions

@ -1 +1 @@
Subproject commit 059f4a06ed9eb84560f7cb2381b989ecc2c1f671
Subproject commit 1f4b957ee9d6d4b3d73eab6e98a84041b1123bc6

View File

@ -21,5 +21,7 @@ namespace FModel
public const string _FN_LIVE_TRIGGER = "fortnite-live.manifest";
public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest";
public const string _NO_PRESET_TRIGGER = "Hand Made";
}
}

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Input;
using CUE4Parse.UE4.Objects.Core.Serialization;
using CUE4Parse.UE4.Versions;
using CUE4Parse_Conversion.Meshes;
using CUE4Parse_Conversion.Textures;
@ -206,21 +207,21 @@ namespace FModel.Settings
private IDictionary<FGame, string> _presets = new Dictionary<FGame, string>
{
{FGame.Unknown, "None"},
{FGame.FortniteGame, "None"},
{FGame.ShooterGame, "None"},
{FGame.DeadByDaylight, "None"},
{FGame.OakGame, "None"},
{FGame.Dungeons, "None"},
{FGame.WorldExplorers, "None"},
{FGame.g3, "None"},
{FGame.StateOfDecay2, "None"},
{FGame.Prospect, "None"},
{FGame.Indiana, "None"},
{FGame.RogueCompany, "None"},
{FGame.SwGame, "None"},
{FGame.Platform, "None"},
{FGame.BendGame, "None"}
{FGame.Unknown, Constants._NO_PRESET_TRIGGER},
{FGame.FortniteGame, Constants._NO_PRESET_TRIGGER},
{FGame.ShooterGame, Constants._NO_PRESET_TRIGGER},
{FGame.DeadByDaylight, Constants._NO_PRESET_TRIGGER},
{FGame.OakGame, Constants._NO_PRESET_TRIGGER},
{FGame.Dungeons, Constants._NO_PRESET_TRIGGER},
{FGame.WorldExplorers, Constants._NO_PRESET_TRIGGER},
{FGame.g3, Constants._NO_PRESET_TRIGGER},
{FGame.StateOfDecay2, Constants._NO_PRESET_TRIGGER},
{FGame.Prospect, Constants._NO_PRESET_TRIGGER},
{FGame.Indiana, Constants._NO_PRESET_TRIGGER},
{FGame.RogueCompany, Constants._NO_PRESET_TRIGGER},
{FGame.SwGame, Constants._NO_PRESET_TRIGGER},
{FGame.Platform, Constants._NO_PRESET_TRIGGER},
{FGame.BendGame, Constants._NO_PRESET_TRIGGER}
};
public IDictionary<FGame, string> Presets
{
@ -275,6 +276,54 @@ namespace FModel.Settings
get => _overridedUEVersion;
set => SetProperty(ref _overridedUEVersion, value);
}
private IDictionary<FGame, List<FCustomVersion>> _overridedCustomVersions = new Dictionary<FGame, List<FCustomVersion>>
{
{FGame.Unknown, null},
{FGame.FortniteGame, null},
{FGame.ShooterGame, null},
{FGame.DeadByDaylight, null},
{FGame.OakGame, null},
{FGame.Dungeons, null},
{FGame.WorldExplorers, null},
{FGame.g3, null},
{FGame.StateOfDecay2, null},
{FGame.Prospect, null},
{FGame.Indiana, null},
{FGame.RogueCompany, null},
{FGame.SwGame, null},
{FGame.Platform, null},
{FGame.BendGame, null}
};
public IDictionary<FGame, List<FCustomVersion>> OverridedCustomVersions
{
get => _overridedCustomVersions;
set => SetProperty(ref _overridedCustomVersions, value);
}
private IDictionary<FGame, Dictionary<string, bool>> _overridedOptions = new Dictionary<FGame, Dictionary<string, bool>>
{
{FGame.Unknown, null},
{FGame.FortniteGame, null},
{FGame.ShooterGame, null},
{FGame.DeadByDaylight, null},
{FGame.OakGame, null},
{FGame.Dungeons, null},
{FGame.WorldExplorers, null},
{FGame.g3, null},
{FGame.StateOfDecay2, null},
{FGame.Prospect, null},
{FGame.Indiana, null},
{FGame.RogueCompany, null},
{FGame.SwGame, null},
{FGame.Platform, null},
{FGame.BendGame, null}
};
public IDictionary<FGame, Dictionary<string, bool>> OverridedOptions
{
get => _overridedOptions;
set => SetProperty(ref _overridedOptions, value);
}
private IDictionary<FGame, IList<CustomDirectory>> _customDirectories = new Dictionary<FGame, IList<CustomDirectory>>
{

View File

@ -71,19 +71,31 @@ namespace FModel.ViewModels
case Constants._FN_LIVE_TRIGGER:
{
Game = FGame.FortniteGame;
Provider = new StreamedFileProvider("FortniteLive", true, new VersionContainer(UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedUEVersion[Game]));
Provider = new StreamedFileProvider("FortniteLive", true,
new VersionContainer(
UserSettings.Default.OverridedGame[Game],
UserSettings.Default.OverridedUEVersion[Game],
UserSettings.Default.OverridedCustomVersions[Game],
UserSettings.Default.OverridedOptions[Game]));
break;
}
case Constants._VAL_LIVE_TRIGGER:
{
Game = FGame.ShooterGame;
Provider = new StreamedFileProvider("ValorantLive", true, new VersionContainer(UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedUEVersion[Game]));
Provider = new StreamedFileProvider("ValorantLive", true,
new VersionContainer(
UserSettings.Default.OverridedGame[Game],
UserSettings.Default.OverridedUEVersion[Game],
UserSettings.Default.OverridedCustomVersions[Game],
UserSettings.Default.OverridedOptions[Game]));
break;
}
default:
{
Game = gameDirectory.SubstringBeforeLast("\\Content").SubstringAfterLast("\\").ToEnum(FGame.Unknown);
var versions = new VersionContainer(UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedUEVersion[Game]);
var versions = new VersionContainer(
UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedUEVersion[Game],
UserSettings.Default.OverridedCustomVersions[Game], UserSettings.Default.OverridedOptions[Game]);
if (Game == FGame.StateOfDecay2)
Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List<DirectoryInfo>

View File

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.UE4.Objects.Core.Serialization;
using CUE4Parse.UE4.Versions;
using CUE4Parse_Conversion.Meshes;
using CUE4Parse_Conversion.Textures;
@ -31,7 +33,11 @@ namespace FModel.ViewModels
public string SelectedPreset
{
get => _selectedPreset;
set => SetProperty(ref _selectedPreset, value);
set
{
SetProperty(ref _selectedPreset, value);
RaisePropertyChanged("EnableElements");
}
}
private EGame _selectedUeGame;
@ -47,6 +53,20 @@ 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
{
get => _selectedOptions;
set => SetProperty(ref _selectedOptions, value);
}
private ELanguage _selectedAssetLanguage;
public ELanguage SelectedAssetLanguage
@ -133,6 +153,8 @@ namespace FModel.ViewModels
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER;
private readonly FGame _game;
private Game _gamePreset;
private string _outputSnapshot;
@ -141,6 +163,8 @@ namespace FModel.ViewModels
private string _presetSnapshot;
private EGame _ueGameSnapshot;
private UE4Version _ueVersionSnapshot;
private List<FCustomVersion> _customVersionsSnapshot;
private Dictionary<string, bool> _optionsSnapshot;
private ELanguage _assetLanguageSnapshot;
private EEnabledDisabled _directoryStructureSnapshot;
private ECompressedAudio _compressedAudioSnapshot;
@ -163,6 +187,8 @@ namespace FModel.ViewModels
_presetSnapshot = UserSettings.Default.Presets[_game];
_ueGameSnapshot = UserSettings.Default.OverridedGame[_game];
_ueVersionSnapshot = UserSettings.Default.OverridedUEVersion[_game];
_customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game];
_optionsSnapshot = UserSettings.Default.OverridedOptions[_game];
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
_directoryStructureSnapshot = UserSettings.Default.KeepDirectoryStructure;
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
@ -176,6 +202,8 @@ namespace FModel.ViewModels
SelectedPreset = _presetSnapshot;
SelectedUeGame = _ueGameSnapshot;
SelectedUeVersion = _ueVersionSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
SelectedAssetLanguage = _assetLanguageSnapshot;
SelectedDirectoryStructure = _directoryStructureSnapshot;
SelectedCompressedAudio = _compressedAudioSnapshot;
@ -223,12 +251,26 @@ 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)
{
SelectedCustomVersions.Add(new FCustomVersion {Key = new FGuid(guid), Version = v});
}
SelectedOptions = new Dictionary<string, bool>();
foreach (var (k, v) in version.Options)
{
SelectedOptions[k] = v;
}
}
public void ResetPreset()
{
SelectedUeGame = _ueGameSnapshot;
SelectedUeVersion = _ueVersionSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
}
public SettingsOut Save()
@ -236,6 +278,7 @@ namespace FModel.ViewModels
var ret = SettingsOut.Nothing;
if (_ueGameSnapshot != SelectedUeGame || _ueVersionSnapshot != SelectedUeVersion || // comboboxes
_customVersionsSnapshot != SelectedCustomVersions || _optionsSnapshot != SelectedOptions ||
_outputSnapshot != UserSettings.Default.OutputDirectory || // textbox
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
ret = SettingsOut.Restart;
@ -247,6 +290,8 @@ namespace FModel.ViewModels
UserSettings.Default.Presets[_game] = SelectedPreset;
UserSettings.Default.OverridedGame[_game] = SelectedUeGame;
UserSettings.Default.OverridedUEVersion[_game] = SelectedUeVersion;
UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions;
UserSettings.Default.OverridedOptions[_game] = SelectedOptions;
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
UserSettings.Default.KeepDirectoryStructure = SelectedDirectoryStructure;
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
@ -267,7 +312,7 @@ namespace FModel.ViewModels
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues(SelectedUpdateMode.GetType()).Cast<EUpdateMode>();
private IEnumerable<string> EnumeratePresets()
{
yield return "None";
yield return Constants._NO_PRESET_TRIGGER;
}
private IEnumerable<EGame> EnumerateUeGames() => Enum.GetValues(SelectedUeGame.GetType()).Cast<EGame>();
private IEnumerable<UE4Version> EnumerateUeVersions() => Enum.GetValues(SelectedUeVersion.GetType()).Cast<UE4Version>();

View File

@ -0,0 +1,49 @@
<adonisControls:AdonisWindow x:Class="FModel.Views.Resources.Controls.DictionaryEditor"
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:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" ResizeMode="NoResize" SizeToContent="Width"
MinWidth="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.30'}"
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.20'}">
<adonisControls:AdonisWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<avalonEdit:TextEditor x:Name="MyAvalonEditor" Grid.Row="0" Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
FontFamily="Consolas" FontSize="8pt" IsReadOnly="True" ShowLineNumbers="True" Foreground="#DAE5F2" />
<Border Grid.Row="1"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer1BackgroundBrush}}"
adonisExtensions:LayerExtension.IncreaseLayer="True">
<Grid Margin="30, 12, 6, 12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="HeBrokeIt" Grid.Column="0" Text="IF YOU DON'T KNOW WHAT THIS DOES, DON'T TOUCH IT, EVER!"
HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="11" Margin="0 0 10 0" FontWeight="DemiBold"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.Layer1InteractionForegroundBrush}}" />
<Button Grid.Column="1" MinWidth="78" Margin="0 0 12 0" IsDefault="True" IsCancel="False"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK" Click="OnClick" />
<Button Grid.Column="2" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="True"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Cancel" />
</Grid>
</Border>
</Grid>
</adonisControls:AdonisWindow>

View File

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.UE4.Objects.Core.Serialization;
using FModel.Extensions;
using ICSharpCode.AvalonEdit.Document;
using Newtonsoft.Json;
namespace FModel.Views.Resources.Controls
{
public partial class DictionaryEditor
{
private readonly bool _enableElements;
private readonly List<FCustomVersion> _defaultCustomVersions;
private readonly Dictionary<string, bool> _defaultOptions;
public List<FCustomVersion> CustomVersions { get; private set; }
public Dictionary<string, bool> Options { get; private set; }
public DictionaryEditor(string title, bool enableElements)
{
_enableElements = enableElements;
_defaultCustomVersions = new List<FCustomVersion> {new() { Key = new FGuid(), Version = 0 }};
_defaultOptions = new Dictionary<string, bool> {{ "key1", true }, { "key2", false }};
InitializeComponent();
Title = title;
MyAvalonEditor.IsReadOnly = !_enableElements;
MyAvalonEditor.SyntaxHighlighting = AvalonExtensions.HighlighterSelector("");
}
public DictionaryEditor(List<FCustomVersion> customVersions, string title, bool enableElements) : this(title, enableElements)
{
MyAvalonEditor.Document = new TextDocument
{
Text = JsonConvert.SerializeObject(customVersions ?? _defaultCustomVersions, Formatting.Indented)
};
}
public DictionaryEditor(Dictionary<string, bool> options, string title, bool enableElements) : this(title, enableElements)
{
MyAvalonEditor.Document = new TextDocument
{
Text = JsonConvert.SerializeObject(options ?? _defaultOptions, Formatting.Indented)
};
}
private void OnClick(object sender, RoutedEventArgs e)
{
if (!_enableElements)
{
DialogResult = false;
Close();
return;
}
try
{
switch (Title)
{
case "Versioning Configuration (Custom Versions)":
CustomVersions = JsonConvert.DeserializeObject<List<FCustomVersion>>(MyAvalonEditor.Document.Text);
DialogResult = !CustomVersions.SequenceEqual(_defaultCustomVersions);
Close();
break;
case "Versioning Configuration (Options)":
Options = JsonConvert.DeserializeObject<Dictionary<string, bool>>(MyAvalonEditor.Document.Text);
DialogResult = !Options.SequenceEqual(_defaultOptions);
Close();
break;
default:
throw new NotImplementedException();
}
}
catch
{
HeBrokeIt.Text = "GG YOU BROKE THE FORMAT, FIX THE JSON OR CANCEL THE CHANGES!";
HeBrokeIt.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(Constants.RED));
}
}
}
}

View File

@ -2,8 +2,8 @@
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:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" PreviewKeyDown="OnPreviewKeyDown"
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.40'}">

View File

@ -94,7 +94,8 @@
<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}}}" Margin="0 0 0 5">
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" IsEnabled="{Binding SettingsView.EnableElements}"
Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
@ -104,7 +105,8 @@
<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}}}" Margin="0 0 0 5">
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" IsEnabled="{Binding SettingsView.EnableElements}"
Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
@ -120,8 +122,8 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="Custom Versions" IsEnabled="False" />
<Button Grid.Column="2" Content="Options" IsEnabled="False" />
<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" />

View File

@ -3,6 +3,7 @@ using System.Windows.Controls;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
using FModel.Views.Resources.Controls;
using Ookii.Dialogs.Wpf;
namespace FModel.Views
@ -91,8 +92,34 @@ namespace FModel.Views
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is not ComboBox {SelectedItem: string s}) return;
if (s == "None") _applicationView.SettingsView.ResetPreset();
if (s == Constants._NO_PRESET_TRIGGER) _applicationView.SettingsView.ResetPreset();
else _applicationView.SettingsView.SwitchPreset(s);
}
private void OpenCustomVersions(object sender, RoutedEventArgs e)
{
var dictionary = new DictionaryEditor(
_applicationView.SettingsView.SelectedCustomVersions,
"Versioning Configuration (Custom Versions)",
_applicationView.SettingsView.EnableElements);
var result = dictionary.ShowDialog();
if (!result.HasValue || !result.Value)
return;
_applicationView.SettingsView.SelectedCustomVersions = dictionary.CustomVersions;
}
private void OpenOptions(object sender, RoutedEventArgs e)
{
var dictionary = new DictionaryEditor(
_applicationView.SettingsView.SelectedOptions,
"Versioning Configuration (Options)",
_applicationView.SettingsView.EnableElements);
var result = dictionary.ShowDialog();
if (!result.HasValue || !result.Value)
return;
_applicationView.SettingsView.SelectedOptions = dictionary.Options;
}
}
}

View File

@ -1,6 +1,6 @@
# 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 support 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.
&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.
<img src="https://user-images.githubusercontent.com/26126862/119065662-52534800-b9de-11eb-85fd-a47797daa062.png" align="center" alt="FModel">