mirror of
https://github.com/4sval/FModel.git
synced 2026-06-20 15:00:12 -05:00
Improved loose files support
This commit is contained in:
parent
dfd686e870
commit
cdbdc1f99f
|
|
@ -1 +1 @@
|
|||
Subproject commit acf11fb81a7239258e8c7ae0d33eedc865d92a03
|
||||
Subproject commit 024b005c4d15e8082ecebfb202700d59bb6113c0
|
||||
|
|
@ -218,6 +218,17 @@ public class AssetsFolderViewModel
|
|||
var treeItems = new RangeObservableCollection<TreeItem>();
|
||||
treeItems.SetSuppressionState(true);
|
||||
|
||||
static TreeItem FindByHeaderOrNull(IReadOnlyList<TreeItem> list, string header)
|
||||
{
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (list[i].Header == header)
|
||||
return list[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
TreeItem lastNode = null;
|
||||
|
|
@ -226,23 +237,31 @@ public class AssetsFolderViewModel
|
|||
var builder = new StringBuilder(64);
|
||||
var parentNode = treeItems;
|
||||
|
||||
if (folders.Length <= 1)
|
||||
{
|
||||
var rootNode = FindByHeaderOrNull(treeItems, "Content");
|
||||
if (rootNode == null)
|
||||
{
|
||||
rootNode = new TreeItem("Content", entry, "Content")
|
||||
{
|
||||
Parent = null
|
||||
};
|
||||
|
||||
rootNode.Folders.SetSuppressionState(true);
|
||||
rootNode.AssetsList.Assets.SetSuppressionState(true);
|
||||
treeItems.Add(rootNode);
|
||||
}
|
||||
|
||||
rootNode.AssetsList.Add(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var i = 0; i < folders.Length - 1; i++)
|
||||
{
|
||||
var folder = folders[i];
|
||||
builder.Append(folder).Append('/');
|
||||
lastNode = FindByHeaderOrNull(parentNode, folder);
|
||||
|
||||
static TreeItem FindByHeaderOrNull(IReadOnlyList<TreeItem> list, string header)
|
||||
{
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (list[i].Header == header)
|
||||
return list[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lastNode == null)
|
||||
{
|
||||
var nodePath = builder.ToString();
|
||||
|
|
|
|||
|
|
@ -336,6 +336,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
|
||||
Provider.Initialize();
|
||||
GameDirectory.AddLooseFiles(Provider.LooseFileCount);
|
||||
_wwiseProviderLazy = new Lazy<WwiseProvider>(() => new WwiseProvider(Provider, UserSettings.Default.GameDirectory));
|
||||
_fmodProviderLazy = new Lazy<FModProvider>(() => new FModProvider(Provider, UserSettings.Default.GameDirectory));
|
||||
_criWareProviderLazy = new Lazy<CriWareProvider>(() => new CriWareProvider(Provider, UserSettings.Default.GameDirectory));
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
private void FilterDirectoryFilesToDisplay(CancellationToken cancellationToken, IEnumerable<FileItem> directoryFiles)
|
||||
{
|
||||
HashSet<string> filter;
|
||||
var includeLooseFiles = false;
|
||||
if (directoryFiles == null) filter = null;
|
||||
else
|
||||
{
|
||||
|
|
@ -120,11 +121,17 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
foreach (var directoryFile in directoryFiles)
|
||||
{
|
||||
if (!directoryFile.IsEnabled) continue;
|
||||
if (directoryFile.IsLooseFilesContainer)
|
||||
{
|
||||
includeLooseFiles = true;
|
||||
continue;
|
||||
}
|
||||
filter.Add(directoryFile.Name);
|
||||
}
|
||||
}
|
||||
|
||||
var hasFilter = filter != null && filter.Count != 0;
|
||||
var hasSelection = hasFilter || includeLooseFiles;
|
||||
var entries = new List<GameFile>();
|
||||
|
||||
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
|
||||
|
|
@ -132,12 +139,16 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
|
||||
if (asset.IsUePackagePayload) continue;
|
||||
|
||||
if (hasFilter)
|
||||
if (hasSelection)
|
||||
{
|
||||
if (asset is VfsEntry entry && filter.Contains(entry.Vfs.Name))
|
||||
{
|
||||
entries.Add(asset);
|
||||
}
|
||||
else if (includeLooseFiles && asset is OsGameFile)
|
||||
{
|
||||
entries.Add(asset);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -56,6 +56,13 @@ public class FileItem : ViewModel
|
|||
set => SetProperty(ref _isEnabled, value);
|
||||
}
|
||||
|
||||
private bool _isLooseFilesContainer;
|
||||
public bool IsLooseFilesContainer
|
||||
{
|
||||
get => _isLooseFilesContainer;
|
||||
set => SetProperty(ref _isLooseFilesContainer, value);
|
||||
}
|
||||
|
||||
private string _key;
|
||||
public string Key
|
||||
{
|
||||
|
|
@ -83,6 +90,18 @@ public class FileItem : ViewModel
|
|||
Length = length;
|
||||
}
|
||||
|
||||
public FileItem(string name, int fileCount, long length, bool isLooseFile)
|
||||
{
|
||||
Name = name;
|
||||
Length = length;
|
||||
FileCount = fileCount;
|
||||
IsLooseFilesContainer = isLooseFile;
|
||||
IsEnabled = true;
|
||||
Key = string.Empty;
|
||||
MountPoint = string.Empty;
|
||||
CompressionMethods = [];
|
||||
}
|
||||
|
||||
public FileItem(IAesVfsReader reader)
|
||||
{
|
||||
Name = reader.Name;
|
||||
|
|
@ -90,6 +109,7 @@ public class FileItem : ViewModel
|
|||
Guid = reader.EncryptionKeyGuid;
|
||||
IsEncrypted = reader.IsEncrypted;
|
||||
IsEnabled = false;
|
||||
IsLooseFilesContainer = false;
|
||||
Key = string.Empty;
|
||||
FileCount = reader is IoStoreReader storeReader ? (int) storeReader.TocResource.Header.TocEntryCount - 1 : 0;
|
||||
CompressionMethods = reader.CompressionMethods;
|
||||
|
|
@ -101,19 +121,25 @@ public class FileItem : ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public class GameDirectoryViewModel : ViewModel
|
||||
public partial class GameDirectoryViewModel : ViewModel
|
||||
{
|
||||
public bool HasNoFile => DirectoryFiles.Count < 1;
|
||||
public readonly ObservableCollection<FileItem> DirectoryFiles;
|
||||
|
||||
public ICollectionView DirectoryFilesView { get; }
|
||||
|
||||
private readonly Regex _hiddenArchives = new(@"^(?!global|pakchunk.+(optional|ondemand)\-).+(pak|utoc)$", // should be universal
|
||||
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
private readonly Regex _hiddenArchives = ArchivesRegex();
|
||||
|
||||
public GameDirectoryViewModel()
|
||||
{
|
||||
DirectoryFiles = new ObservableCollection<FileItem>();
|
||||
DirectoryFilesView = new ListCollectionView(DirectoryFiles) { SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending) } };
|
||||
DirectoryFiles = [];
|
||||
DirectoryFilesView = new ListCollectionView(DirectoryFiles)
|
||||
{
|
||||
SortDescriptions =
|
||||
{
|
||||
new SortDescription(nameof(FileItem.IsLooseFilesContainer), ListSortDirection.Ascending),
|
||||
new SortDescription(nameof(FileItem.Name), ListSortDirection.Ascending)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void Add(IAesVfsReader reader)
|
||||
|
|
@ -124,6 +150,25 @@ public class GameDirectoryViewModel : ViewModel
|
|||
Application.Current.Dispatcher.Invoke(() => DirectoryFiles.Add(fileItem));
|
||||
}
|
||||
|
||||
public void AddLooseFiles(int fileCount)
|
||||
{
|
||||
if (fileCount < 1)
|
||||
return;
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var looseFilesContainer = DirectoryFiles.FirstOrDefault(x => x.IsLooseFilesContainer);
|
||||
if (looseFilesContainer is not null)
|
||||
{
|
||||
looseFilesContainer.FileCount += fileCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
DirectoryFiles.Add(new FileItem("Loose Files", fileCount, 0, true));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Verify(IAesVfsReader reader)
|
||||
{
|
||||
if (DirectoryFiles.FirstOrDefault(x => x.Name == reader.Name) is not { } file) return;
|
||||
|
|
@ -138,4 +183,7 @@ public class GameDirectoryViewModel : ViewModel
|
|||
if (DirectoryFiles.FirstOrDefault(x => x.Name == reader.Name) is not { } file) return;
|
||||
file.IsEnabled = false;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^(?!global|pakchunk.+(optional|ondemand)\-).+(pak|utoc)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant)]
|
||||
private static partial Regex ArchivesRegex();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,17 +98,35 @@
|
|||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DirectoryFilesListBox" TargetType="ListBox" BasedOn="{StaticResource {x:Type ListBox}}">
|
||||
<Setter Property="ItemsSource" Value="{Binding CUE4Parse.GameDirectory.DirectoryFilesView, IsAsync=True}" />
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
|
||||
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode" Value="NeverExpand"/>
|
||||
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement" Value="Docked"/>
|
||||
<Style x:Key="DirectoryFilesListBox"
|
||||
TargetType="ListBox"
|
||||
BasedOn="{StaticResource {x:Type ListBox}}">
|
||||
<Setter Property="ItemsSource"
|
||||
Value="{Binding CUE4Parse.GameDirectory.DirectoryFilesView, IsAsync=True}" />
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
|
||||
Value="Disabled" />
|
||||
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode"
|
||||
Value="NeverExpand" />
|
||||
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement"
|
||||
Value="Docked" />
|
||||
<Setter Property="ItemContainerStyle">
|
||||
<Setter.Value>
|
||||
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
|
||||
<Setter Property="Padding" Value="5 3" />
|
||||
<Style TargetType="{x:Type ListBoxItem}"
|
||||
BasedOn="{StaticResource {x:Type ListBoxItem}}">
|
||||
<Setter Property="HorizontalContentAlignment"
|
||||
Value="Stretch" />
|
||||
<Setter Property="IsEnabled"
|
||||
Value="{Binding IsEnabled}" />
|
||||
<Setter Property="Margin"
|
||||
Value="0 1" />
|
||||
<Setter Property="Padding"
|
||||
Value="7 5" />
|
||||
<Setter Property="Background"
|
||||
Value="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="Transparent" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="1" />
|
||||
</Style>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
|
@ -119,50 +137,97 @@
|
|||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="25" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="95" />
|
||||
<ColumnDefinition Width="85" />
|
||||
</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" />
|
||||
<TextBlock Grid.Column="3" HorizontalAlignment="Right" Text="{Binding Length, Converter={x:Static converters:SizeToStringConverter.Instance}}" />
|
||||
<Image x:Name="ListImage"
|
||||
Source="/FModel;component/Resources/archive.png"
|
||||
Width="16"
|
||||
Height="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 4 0"
|
||||
RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
<TextBlock Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Grid.Column="2"
|
||||
Margin="12 0 8 0"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
|
||||
Opacity="0.75"
|
||||
Text="{Binding FileCount, StringFormat={}{0:N0} files}" />
|
||||
<TextBlock x:Name="LengthText"
|
||||
Grid.Column="3"
|
||||
Margin="8 0 0 0"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Text="{Binding Length, Converter={x:Static converters:SizeToStringConverter.Instance}}" />
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
|
||||
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/archive_enabled.png" />
|
||||
<DataTrigger Binding="{Binding IsEnabled}"
|
||||
Value="True">
|
||||
<Setter TargetName="ListImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/archive_enabled.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
|
||||
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/archive_disabled.png" />
|
||||
<DataTrigger Binding="{Binding IsEnabled}"
|
||||
Value="False">
|
||||
<Setter TargetName="ListImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/archive_disabled.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsLooseFilesContainer}"
|
||||
Value="True">
|
||||
<Setter TargetName="LengthText"
|
||||
Property="Visibility"
|
||||
Value="Collapsed" />
|
||||
<Setter TargetName="ListImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/asset.png" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}, FallbackValue=0}" Value="0">
|
||||
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}, FallbackValue=0}"
|
||||
Value="0">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Grid>
|
||||
<TextBlock Text="No archives found in the specified directory" FontWeight="SemiBold" TextAlignment="Center"
|
||||
<TextBlock Text="No archives found in the specified directory"
|
||||
FontWeight="SemiBold"
|
||||
TextAlignment="Center"
|
||||
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Multiple}">
|
||||
<Setter Property="SelectionMode" Value="Extended" />
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
|
||||
Value="{x:Static local:ELoadingMode.Multiple}">
|
||||
<Setter Property="SelectionMode"
|
||||
Value="Extended" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.All}">
|
||||
<Setter Property="SelectionMode" Value="Extended" />
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
|
||||
Value="{x:Static local:ELoadingMode.All}">
|
||||
<Setter Property="SelectionMode"
|
||||
Value="Extended" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.AllButNew}">
|
||||
<Setter Property="SelectionMode" Value="Extended" />
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
|
||||
Value="{x:Static local:ELoadingMode.AllButNew}">
|
||||
<Setter Property="SelectionMode"
|
||||
Value="Extended" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.AllButModified}">
|
||||
<Setter Property="SelectionMode" Value="Extended" />
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
|
||||
Value="{x:Static local:ELoadingMode.AllButModified}">
|
||||
<Setter Property="SelectionMode"
|
||||
Value="Extended" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user