mirror of
https://github.com/4sval/FModel.git
synced 2026-03-21 17:24:26 -05:00
ui now deals with GameFile instead of a bad replica
Some checks failed
FModel QA Builder / build (push) Has been cancelled
Some checks failed
FModel QA Builder / build (push) Has been cancelled
that means loose files are now supported, or should be Export Raw Data shows the correct extension
This commit is contained in:
parent
3d0bdf05e1
commit
dbf618e417
|
|
@ -1 +1 @@
|
|||
Subproject commit ab6dff8e98e94335916a549810466eb4571a072b
|
||||
Subproject commit 11a92870024a088888aae79c74d8ae0c6c8af3e5
|
||||
27
FModel/Framework/FakeCUE4Parse.cs
Normal file
27
FModel/Framework/FakeCUE4Parse.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using CUE4Parse.Compression;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.UE4.Readers;
|
||||
|
||||
namespace FModel.Framework;
|
||||
|
||||
public class FakeGameFile : GameFile
|
||||
{
|
||||
public FakeGameFile(string path) : base(path, 0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
public override CompressionMethod CompressionMethod => CompressionMethod.None;
|
||||
|
||||
public override byte[] Read()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override FArchive CreateReader()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -502,7 +502,14 @@
|
|||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Export Raw Data (.uasset)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.Header>
|
||||
<TextBlock
|
||||
Text="{Binding DataContext.SelectedItem.Extension,
|
||||
FallbackValue='uasset',
|
||||
StringFormat='Export Raw Data (.{0})',
|
||||
RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
</MenuItem.Header>
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Export_Data" />
|
||||
|
|
@ -649,11 +656,11 @@
|
|||
<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" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="Size" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.Compression, ElementName=AssetsListName, FallbackValue='Unknown'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.CompressionMethod, ElementName=AssetsListName, FallbackValue='Unknown'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="Compression Method" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.IsEncrypted, ElementName=AssetsListName, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="Is Encrypted" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Archive, ElementName=AssetsListName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Vfs.Name, ElementName=AssetsListName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Row="4" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Windows;
|
|||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using AdonisUI.Controls;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels;
|
||||
|
|
@ -84,7 +85,7 @@ public partial class MainWindow
|
|||
#if DEBUG
|
||||
// await _threadWorkerView.Begin(cancellationToken =>
|
||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
// "MyProject/Content/FirstPerson/Meshes/FirstPersonProjectileMesh.uasset"));
|
||||
// "Marvel/Content/Marvel/Characters/1016/1016501/Meshes/SK_1016_1016501.uasset"));
|
||||
// await _threadWorkerView.Begin(cancellationToken =>
|
||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
// "RED/Content/Chara/ABA/Costume01/Animation/Charaselect/body/stand_body01.uasset"));
|
||||
|
|
@ -162,7 +163,7 @@ public partial class MainWindow
|
|||
{
|
||||
if (sender is not ListBox listBox) return;
|
||||
|
||||
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
|
||||
var selectedItems = listBox.SelectedItems.Cast<GameFile>().ToList();
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
||||
}
|
||||
|
||||
|
|
@ -266,7 +267,7 @@ public partial class MainWindow
|
|||
return;
|
||||
|
||||
var filters = textBox.Text.Trim().Split(' ');
|
||||
folder.AssetsList.AssetsView.Filter = o => { return o is AssetItem assetItem && filters.All(x => assetItem.FileName.Contains(x, StringComparison.OrdinalIgnoreCase)); };
|
||||
folder.AssetsList.AssetsView.Filter = o => { return o is GameFile entry && filters.All(x => entry.Name.Contains(x, StringComparison.OrdinalIgnoreCase)); };
|
||||
}
|
||||
|
||||
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
|
|
@ -283,7 +284,7 @@ public partial class MainWindow
|
|||
switch (e.Key)
|
||||
{
|
||||
case Key.Enter:
|
||||
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
|
||||
var selectedItems = listBox.SelectedItems.Cast<GameFile>().ToList();
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.ComponentModel;
|
|||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse.UE4.VirtualFileSystem;
|
||||
using FModel.Framework;
|
||||
|
|
@ -60,12 +61,15 @@ public class TreeItem : ViewModel
|
|||
public RangeObservableCollection<TreeItem> Folders { get; }
|
||||
public ICollectionView FoldersView { get; }
|
||||
|
||||
public TreeItem(string header, string archive, string mountPoint, FPackageFileVersion version, string pathHere)
|
||||
public TreeItem(string header, GameFile entry, string pathHere)
|
||||
{
|
||||
Header = header;
|
||||
Archive = archive;
|
||||
MountPoint = mountPoint;
|
||||
Version = version;
|
||||
if (entry is VfsEntry vfsEntry)
|
||||
{
|
||||
Archive = vfsEntry.Vfs.Name;
|
||||
MountPoint = vfsEntry.Vfs.MountPoint;
|
||||
Version = vfsEntry.Vfs.Ver;
|
||||
}
|
||||
PathAtThisPoint = pathHere;
|
||||
AssetsList = new AssetsListViewModel();
|
||||
Folders = new RangeObservableCollection<TreeItem>();
|
||||
|
|
@ -86,7 +90,7 @@ public class AssetsFolderViewModel
|
|||
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
|
||||
}
|
||||
|
||||
public void BulkPopulate(IReadOnlyCollection<VfsEntry> entries)
|
||||
public void BulkPopulate(IReadOnlyCollection<GameFile> entries)
|
||||
{
|
||||
if (entries == null || entries.Count == 0)
|
||||
return;
|
||||
|
|
@ -95,54 +99,48 @@ public class AssetsFolderViewModel
|
|||
{
|
||||
var treeItems = new RangeObservableCollection<TreeItem>();
|
||||
treeItems.SetSuppressionState(true);
|
||||
var items = new List<AssetItem>(entries.Count);
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var item = new AssetItem(entry.Path, entry.IsEncrypted, entry.Offset, entry.Size, entry.Vfs.Name, entry.CompressionMethod);
|
||||
items.Add(item);
|
||||
TreeItem lastNode = null;
|
||||
var folders = entry.Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
var builder = new StringBuilder(64);
|
||||
var parentNode = treeItems;
|
||||
|
||||
for (var i = 0; i < folders.Length - 1; i++)
|
||||
{
|
||||
TreeItem lastNode = null;
|
||||
var folders = item.FullPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
var builder = new StringBuilder(64);
|
||||
var parentNode = treeItems;
|
||||
var folder = folders[i];
|
||||
builder.Append(folder).Append('/');
|
||||
lastNode = FindByHeaderOrNull(parentNode, folder);
|
||||
|
||||
for (var i = 0; i < folders.Length - 1; i++)
|
||||
static TreeItem FindByHeaderOrNull(IReadOnlyList<TreeItem> list, string header)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (list[i].Header == header)
|
||||
return list[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
if (list[i].Header == header)
|
||||
return list[i];
|
||||
}
|
||||
|
||||
if (lastNode == null)
|
||||
{
|
||||
var nodePath = builder.ToString();
|
||||
lastNode = new TreeItem(folder, item.Archive, entry.Vfs.MountPoint, entry.Vfs.Ver, nodePath[..^1]);
|
||||
lastNode.Folders.SetSuppressionState(true);
|
||||
lastNode.AssetsList.Assets.SetSuppressionState(true);
|
||||
parentNode.Add(lastNode);
|
||||
}
|
||||
|
||||
parentNode = lastNode.Folders;
|
||||
return null;
|
||||
}
|
||||
|
||||
lastNode?.AssetsList.Assets.Add(item);
|
||||
if (lastNode == null)
|
||||
{
|
||||
var nodePath = builder.ToString();
|
||||
lastNode = new TreeItem(folder, entry, nodePath[..^1]);
|
||||
lastNode.Folders.SetSuppressionState(true);
|
||||
lastNode.AssetsList.Assets.SetSuppressionState(true);
|
||||
parentNode.Add(lastNode);
|
||||
}
|
||||
|
||||
parentNode = lastNode.Folders;
|
||||
}
|
||||
|
||||
lastNode?.AssetsList.Assets.Add(entry);
|
||||
}
|
||||
|
||||
Folders.AddRange(treeItems);
|
||||
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(items);
|
||||
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(entries);
|
||||
|
||||
foreach (var folder in Folders)
|
||||
InvokeOnCollectionChanged(folder);
|
||||
|
|
|
|||
|
|
@ -1,109 +1,21 @@
|
|||
using System.ComponentModel;
|
||||
using System.Windows.Data;
|
||||
using CUE4Parse.Compression;
|
||||
using CUE4Parse.Utils;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using FModel.Framework;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class AssetItem : ViewModel
|
||||
{
|
||||
private string _fullPath;
|
||||
public string FullPath
|
||||
{
|
||||
get => _fullPath;
|
||||
private set => SetProperty(ref _fullPath, value);
|
||||
}
|
||||
|
||||
private bool _isEncrypted;
|
||||
public bool IsEncrypted
|
||||
{
|
||||
get => _isEncrypted;
|
||||
private set => SetProperty(ref _isEncrypted, value);
|
||||
}
|
||||
|
||||
private long _offset;
|
||||
public long Offset
|
||||
{
|
||||
get => _offset;
|
||||
private set => SetProperty(ref _offset, value);
|
||||
}
|
||||
|
||||
private long _size;
|
||||
public long Size
|
||||
{
|
||||
get => _size;
|
||||
private set => SetProperty(ref _size, value);
|
||||
}
|
||||
|
||||
private string _archive;
|
||||
public string Archive
|
||||
{
|
||||
get => _archive;
|
||||
private set => SetProperty(ref _archive, value);
|
||||
}
|
||||
|
||||
private CompressionMethod _compression;
|
||||
public CompressionMethod Compression
|
||||
{
|
||||
get => _compression;
|
||||
private set => SetProperty(ref _compression, value);
|
||||
}
|
||||
|
||||
private string _directory;
|
||||
public string Directory
|
||||
{
|
||||
get => _directory;
|
||||
private set => SetProperty(ref _directory, value);
|
||||
}
|
||||
|
||||
private string _fileName;
|
||||
public string FileName
|
||||
{
|
||||
get => _fileName;
|
||||
private set => SetProperty(ref _fileName, value);
|
||||
}
|
||||
|
||||
private string _extension;
|
||||
public string Extension
|
||||
{
|
||||
get => _extension;
|
||||
private set => SetProperty(ref _extension, value);
|
||||
}
|
||||
|
||||
public AssetItem(string titleExtra, AssetItem asset) : this(asset.FullPath, asset.IsEncrypted, asset.Offset, asset.Size, asset.Archive, asset.Compression)
|
||||
{
|
||||
FullPath += titleExtra;
|
||||
}
|
||||
|
||||
public AssetItem(string fullPath, bool isEncrypted = false, long offset = 0, long size = 0, string archive = "", CompressionMethod compression = CompressionMethod.None)
|
||||
{
|
||||
FullPath = fullPath;
|
||||
IsEncrypted = isEncrypted;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
Archive = archive;
|
||||
Compression = compression;
|
||||
|
||||
Directory = FullPath.SubstringBeforeLast('/');
|
||||
FileName = FullPath.SubstringAfterLast('/');
|
||||
Extension = FullPath.SubstringAfterLast('.').ToLowerInvariant();
|
||||
}
|
||||
|
||||
public override string ToString() => FullPath;
|
||||
}
|
||||
|
||||
public class AssetsListViewModel
|
||||
{
|
||||
public RangeObservableCollection<AssetItem> Assets { get; }
|
||||
public RangeObservableCollection<GameFile> Assets { get; }
|
||||
public ICollectionView AssetsView { get; }
|
||||
|
||||
public AssetsListViewModel()
|
||||
{
|
||||
Assets = new RangeObservableCollection<AssetItem>();
|
||||
Assets = new RangeObservableCollection<GameFile>();
|
||||
AssetsView = new ListCollectionView(Assets)
|
||||
{
|
||||
SortDescriptions = { new SortDescription("FullPath", ListSortDirection.Ascending) }
|
||||
SortDescriptions = { new SortDescription("Path", ListSortDirection.Ascending) }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ using CUE4Parse.UE4.Versions;
|
|||
using CUE4Parse.UE4.Wwise;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Sounds;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.UE4.Assets;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse.Utils;
|
||||
|
|
@ -499,25 +500,25 @@ public class CUE4ParseViewModel : ViewModel
|
|||
});
|
||||
}
|
||||
|
||||
public void ExtractSelected(CancellationToken cancellationToken, IEnumerable<AssetItem> assetItems)
|
||||
public void ExtractSelected(CancellationToken cancellationToken, IEnumerable<GameFile> assetItems)
|
||||
{
|
||||
foreach (var asset in assetItems)
|
||||
foreach (var entry in assetItems)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Extract(cancellationToken, asset, TabControl.HasNoTabs);
|
||||
Extract(cancellationToken, entry, TabControl.HasNoTabs);
|
||||
}
|
||||
}
|
||||
|
||||
private void BulkFolder(CancellationToken cancellationToken, TreeItem folder, Action<AssetItem> action)
|
||||
private void BulkFolder(CancellationToken cancellationToken, TreeItem folder, Action<GameFile> action)
|
||||
{
|
||||
foreach (var asset in folder.AssetsList.Assets)
|
||||
foreach (var entry in folder.AssetsList.Assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
action(asset);
|
||||
action(entry);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -530,10 +531,10 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
public void ExportFolder(CancellationToken cancellationToken, TreeItem folder)
|
||||
{
|
||||
Parallel.ForEach(folder.AssetsList.Assets, asset =>
|
||||
Parallel.ForEach(folder.AssetsList.Assets, entry =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ExportData(asset, false);
|
||||
ExportData(entry, false);
|
||||
});
|
||||
|
||||
foreach (var f in folder.Folders) ExportFolder(cancellationToken, f);
|
||||
|
|
@ -554,23 +555,23 @@ public class CUE4ParseViewModel : ViewModel
|
|||
public void AnimationFolder(CancellationToken cancellationToken, TreeItem folder)
|
||||
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Animations | EBulkType.Auto));
|
||||
|
||||
public void Extract(CancellationToken cancellationToken, AssetItem asset, bool addNewTab = false, EBulkType bulk = EBulkType.None)
|
||||
public void Extract(CancellationToken cancellationToken, GameFile entry, bool addNewTab = false, EBulkType bulk = EBulkType.None)
|
||||
{
|
||||
Log.Information("User DOUBLE-CLICKED to extract '{FullPath}'", asset.FullPath);
|
||||
Log.Information("User DOUBLE-CLICKED to extract '{FullPath}'", entry.Path);
|
||||
|
||||
if (addNewTab && TabControl.CanAddTabs) TabControl.AddTab(asset);
|
||||
else TabControl.SelectedTab.SoftReset(asset);
|
||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(asset.Extension);
|
||||
if (addNewTab && TabControl.CanAddTabs) TabControl.AddTab(entry);
|
||||
else TabControl.SelectedTab.SoftReset(entry);
|
||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(entry.Extension);
|
||||
|
||||
var updateUi = !HasFlag(bulk, EBulkType.Auto);
|
||||
var saveProperties = HasFlag(bulk, EBulkType.Properties);
|
||||
var saveTextures = HasFlag(bulk, EBulkType.Textures);
|
||||
switch (asset.Extension)
|
||||
switch (entry.Extension)
|
||||
{
|
||||
case "uasset":
|
||||
case "umap":
|
||||
{
|
||||
var pkg = Provider.LoadPackage(asset.FullPath, asset.Archive);
|
||||
var pkg = Provider.LoadPackage(entry);
|
||||
if (saveProperties || updateUi)
|
||||
{
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(pkg.GetExports(), Formatting.Indented), saveProperties, updateUi);
|
||||
|
|
@ -614,7 +615,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "po":
|
||||
case "h":
|
||||
{
|
||||
var data = Provider.SaveAsset(asset.FullPath, asset.Archive);
|
||||
var data = Provider.SaveAsset(entry);
|
||||
using var stream = new MemoryStream(data) { Position = 0 };
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
|
|
@ -624,7 +625,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
case "locmeta":
|
||||
{
|
||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
||||
var archive = entry.CreateReader();
|
||||
var metadata = new FTextLocalizationMetaDataResource(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(metadata, Formatting.Indented), saveProperties, updateUi);
|
||||
|
||||
|
|
@ -632,23 +633,23 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
case "locres":
|
||||
{
|
||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
||||
var archive = entry.CreateReader();
|
||||
var locres = new FTextLocalizationResource(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(locres, Formatting.Indented), saveProperties, updateUi);
|
||||
|
||||
break;
|
||||
}
|
||||
case "bin" when asset.FileName.Contains("AssetRegistry", StringComparison.OrdinalIgnoreCase):
|
||||
case "bin" when entry.Name.Contains("AssetRegistry", StringComparison.OrdinalIgnoreCase):
|
||||
{
|
||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
||||
var archive = entry.CreateReader();
|
||||
var registry = new FAssetRegistryState(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), saveProperties, updateUi);
|
||||
|
||||
break;
|
||||
}
|
||||
case "bin" when asset.FileName.Contains("GlobalShaderCache", StringComparison.OrdinalIgnoreCase):
|
||||
case "bin" when entry.Name.Contains("GlobalShaderCache", StringComparison.OrdinalIgnoreCase):
|
||||
{
|
||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
||||
var archive = entry.CreateReader();
|
||||
var registry = new FGlobalShaderCache(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), saveProperties, updateUi);
|
||||
|
||||
|
|
@ -657,26 +658,26 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "bnk":
|
||||
case "pck":
|
||||
{
|
||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
||||
var archive = entry.CreateReader();
|
||||
var wwise = new WwiseReader(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), saveProperties, updateUi);
|
||||
foreach (var (name, data) in wwise.WwiseEncodedMedias)
|
||||
{
|
||||
SaveAndPlaySound(asset.FullPath.SubstringBeforeWithLast('/') + name, "WEM", data);
|
||||
SaveAndPlaySound(entry.Path.SubstringBeforeWithLast('/') + name, "WEM", data);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "wem":
|
||||
{
|
||||
var data = Provider.SaveAsset(asset.FullPath, asset.Archive);
|
||||
SaveAndPlaySound(asset.FullPath, "WEM", data);
|
||||
var data = Provider.SaveAsset(entry);
|
||||
SaveAndPlaySound(entry.Path, "WEM", data);
|
||||
|
||||
break;
|
||||
}
|
||||
case "udic":
|
||||
{
|
||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
||||
var archive = entry.CreateReader();
|
||||
var header = new FOodleDictionaryArchive(archive).Header;
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(header, Formatting.Indented), saveProperties, updateUi);
|
||||
|
||||
|
|
@ -686,15 +687,15 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "jpg":
|
||||
case "bmp":
|
||||
{
|
||||
var data = Provider.SaveAsset(asset.FullPath, asset.Archive);
|
||||
var data = Provider.SaveAsset(entry);
|
||||
using var stream = new MemoryStream(data) { Position = 0 };
|
||||
TabControl.SelectedTab.AddImage(asset.FileName.SubstringBeforeLast("."), false, SKBitmap.Decode(stream), saveTextures, updateUi);
|
||||
TabControl.SelectedTab.AddImage(entry.NameWithoutExtension, false, SKBitmap.Decode(stream), saveTextures, updateUi);
|
||||
|
||||
break;
|
||||
}
|
||||
case "svg":
|
||||
{
|
||||
var data = Provider.SaveAsset(asset.FullPath, asset.Archive);
|
||||
var data = Provider.SaveAsset(entry);
|
||||
using var stream = new MemoryStream(data) { Position = 0 };
|
||||
var svg = new SkiaSharp.Extended.Svg.SKSvg(new SKSize(512, 512));
|
||||
svg.Load(stream);
|
||||
|
|
@ -706,7 +707,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
canvas.DrawPicture(svg.Picture, paint);
|
||||
}
|
||||
|
||||
TabControl.SelectedTab.AddImage(asset.FileName.SubstringBeforeLast("."), false, bitmap, saveTextures, updateUi);
|
||||
TabControl.SelectedTab.AddImage(entry.NameWithoutExtension, false, bitmap, saveTextures, updateUi);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -714,12 +715,12 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "otf":
|
||||
case "ttf":
|
||||
FLogger.Append(ELog.Warning, () =>
|
||||
FLogger.Text($"Export '{asset.FileName}' raw data and change its extension if you want it to be an installable font file", Constants.WHITE, true));
|
||||
FLogger.Text($"Export '{entry.Name}' raw data and change its extension if you want it to be an installable font file", Constants.WHITE, true));
|
||||
break;
|
||||
case "ushaderbytecode":
|
||||
case "ushadercode":
|
||||
{
|
||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
||||
var archive = entry.CreateReader();
|
||||
var ar = new FShaderCodeArchive(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(ar, Formatting.Indented), saveProperties, updateUi);
|
||||
|
||||
|
|
@ -728,7 +729,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
default:
|
||||
{
|
||||
FLogger.Append(ELog.Warning, () =>
|
||||
FLogger.Text($"The package '{asset.FileName}' is of an unknown type.", Constants.WHITE, true));
|
||||
FLogger.Text($"The package '{entry.Name}' is of an unknown type.", Constants.WHITE, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -737,10 +738,12 @@ public class CUE4ParseViewModel : ViewModel
|
|||
public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName, string parentExportType)
|
||||
{
|
||||
Log.Information("User CTRL-CLICKED to extract '{FullPath}'", fullPath);
|
||||
TabControl.AddTab(new AssetItem(fullPath), parentExportType);
|
||||
|
||||
var entry = Provider[fullPath];
|
||||
TabControl.AddTab(entry, parentExportType);
|
||||
TabControl.SelectedTab.ScrollTrigger = objectName;
|
||||
|
||||
var pkg = Provider.LoadPackage(fullPath);
|
||||
var pkg = Provider.LoadPackage(entry);
|
||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); // json
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(pkg.GetExports(), Formatting.Indented), false, false);
|
||||
|
||||
|
|
@ -797,7 +800,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
var fileName = sourceFile.SubstringAfterLast('/');
|
||||
var path = Path.Combine(UserSettings.Default.TextureDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? TabControl.SelectedTab.Asset.Directory : "", fileName!).Replace('\\', '/');
|
||||
UserSettings.Default.KeepDirectoryStructure ? TabControl.SelectedTab.Entry.Directory : "", fileName!).Replace('\\', '/');
|
||||
|
||||
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
||||
|
||||
|
|
@ -838,7 +841,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
return false;
|
||||
}
|
||||
|
||||
SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Asset.FullPath.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data);
|
||||
SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension).Replace('\\', '/'), audioFormat, data);
|
||||
return false;
|
||||
}
|
||||
case UWorld when isNone && UserSettings.Default.PreviewWorlds:
|
||||
|
|
@ -900,13 +903,11 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public void ShowMetadata(AssetItem asset)
|
||||
public void ShowMetadata(GameFile entry)
|
||||
{
|
||||
var package = Provider.LoadPackage(asset.FullPath, asset.Archive);
|
||||
var package = Provider.LoadPackage(entry);
|
||||
|
||||
var a = new AssetItem(" (Metadata)", asset);
|
||||
if (TabControl.CanAddTabs) TabControl.AddTab(a);
|
||||
else TabControl.SelectedTab.SoftReset(a);
|
||||
TabControl.AddTab($"{entry.Name} (Metadata)");
|
||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("");
|
||||
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(package, Formatting.Indented), false, false);
|
||||
|
|
@ -963,11 +964,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
|
||||
private readonly object _rawData = new ();
|
||||
public void ExportData(AssetItem asset, bool updateUi = true)
|
||||
public void ExportData(GameFile entry, bool updateUi = true)
|
||||
{
|
||||
// TODO: export by archive
|
||||
// is that even useful? if user doesn't rename manually it's gonna overwrite the file anyway
|
||||
if (Provider.TrySavePackage(asset.FullPath, out var assets))
|
||||
if (Provider.TrySavePackage(entry, out var assets))
|
||||
{
|
||||
string path = UserSettings.Default.RawDataDirectory;
|
||||
Parallel.ForEach(assets, kvp =>
|
||||
|
|
@ -980,21 +979,21 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
});
|
||||
|
||||
Log.Information("{FileName} successfully exported", asset.FileName);
|
||||
Log.Information("{FileName} successfully exported", entry.Name);
|
||||
if (updateUi)
|
||||
{
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully exported ", Constants.WHITE);
|
||||
FLogger.Link(asset.FileName, path, true);
|
||||
FLogger.Link(entry.Name, path, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("{FileName} could not be exported", asset.FileName);
|
||||
Log.Error("{FileName} could not be exported", entry.Name);
|
||||
if (updateUi)
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not export '{asset.FileName}'", Constants.WHITE, true));
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not export '{entry.Name}'", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using CUE4Parse.Utils;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using FModel.Framework;
|
||||
|
||||
namespace FModel.ViewModels.Commands;
|
||||
|
|
@ -18,26 +18,26 @@ public class CopyCommand : ViewModelCommand<ApplicationViewModel>
|
|||
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
||||
return;
|
||||
|
||||
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
|
||||
if (!assetItems.Any()) return;
|
||||
var entries = ((IList) parameters[1]).Cast<GameFile>().ToArray();
|
||||
if (!entries.Any()) return;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
switch (trigger)
|
||||
{
|
||||
case "File_Path":
|
||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath);
|
||||
foreach (var entry in entries) sb.AppendLine(entry.Path);
|
||||
break;
|
||||
case "File_Name":
|
||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/'));
|
||||
foreach (var entry in entries) sb.AppendLine(entry.Name);
|
||||
break;
|
||||
case "Directory_Path":
|
||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('/'));
|
||||
foreach (var entry in entries) sb.AppendLine(entry.Directory);
|
||||
break;
|
||||
case "File_Path_No_Extension":
|
||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('.'));
|
||||
foreach (var entry in entries) sb.AppendLine(entry.PathWithoutExtension);
|
||||
break;
|
||||
case "File_Name_No_Extension":
|
||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/').SubstringBeforeLast('.'));
|
||||
foreach (var entry in entries) sb.AppendLine(entry.NameWithoutExtension);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AdonisUI.Controls;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.UE4.Readers;
|
||||
using CUE4Parse.UE4.VirtualFileSystem;
|
||||
using CUE4Parse.Utils;
|
||||
|
|
@ -37,13 +38,17 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
|
||||
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
|
||||
{
|
||||
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
|
||||
if (_applicationView.CUE4Parse.Provider.Keys.Count == 0 && _applicationView.CUE4Parse.Provider.RequiredKeys.Count > 0)
|
||||
{
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
FLogger.Text("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true));
|
||||
return;
|
||||
}
|
||||
if (_applicationView.CUE4Parse.Provider.Files.Count == 0)
|
||||
{
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text("No files were found in the archives or the specified directory", Constants.WHITE, true));
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
var loadingTime = Stopwatch.StartNew();
|
||||
|
|
@ -59,6 +64,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
_threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
// filter what to show
|
||||
_applicationView.Status.UpdateStatusLabel("Packages", "Filtering");
|
||||
switch (UserSettings.Default.LoadingMode)
|
||||
{
|
||||
case ELoadingMode.Multiple:
|
||||
|
|
@ -100,42 +106,36 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
if (directoryFiles == null) filter = null;
|
||||
else
|
||||
{
|
||||
filter = new HashSet<string>();
|
||||
filter = [];
|
||||
foreach (var directoryFile in directoryFiles)
|
||||
{
|
||||
if (!directoryFile.IsEnabled)
|
||||
continue;
|
||||
|
||||
if (!directoryFile.IsEnabled) continue;
|
||||
filter.Add(directoryFile.Name);
|
||||
}
|
||||
}
|
||||
|
||||
var hasFilter = filter != null && filter.Count != 0;
|
||||
var entries = new List<VfsEntry>();
|
||||
var entries = new List<GameFile>();
|
||||
|
||||
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
|
||||
|
||||
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
|
||||
continue;
|
||||
if (asset.IsUePackagePayload) continue;
|
||||
|
||||
if (hasFilter)
|
||||
{
|
||||
if (filter.Contains(entry.Vfs.Name))
|
||||
if (asset is VfsEntry entry && filter.Contains(entry.Vfs.Name))
|
||||
{
|
||||
entries.Add(entry);
|
||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
||||
entries.Add(asset);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
entries.Add(entry);
|
||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
||||
entries.Add(asset);
|
||||
}
|
||||
}
|
||||
|
||||
_applicationView.Status.UpdateStatusLabel("Folders & Packages");
|
||||
_applicationView.Status.UpdateStatusLabel($"{entries.Count:### ### ###} Packages");
|
||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||
}
|
||||
|
||||
|
|
@ -157,11 +157,11 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
var mode = UserSettings.Default.LoadingMode;
|
||||
var entries = ParseBackup(openFileDialog.FileName, mode, cancellationToken);
|
||||
|
||||
_applicationView.Status.UpdateStatusLabel($"{mode.ToString()[6..]} Folders & Packages");
|
||||
_applicationView.Status.UpdateStatusLabel($"{entries.Count:### ### ###} Packages");
|
||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||
}
|
||||
|
||||
private List<VfsEntry> ParseBackup(string path, ELoadingMode mode, CancellationToken cancellationToken = default)
|
||||
private List<GameFile> ParseBackup(string path, ELoadingMode mode, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var fileStream = new FileStream(path, FileMode.Open);
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
|
@ -176,7 +176,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
|
||||
var entries = new List<VfsEntry>();
|
||||
var entries = new List<GameFile>();
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
|
|
@ -209,14 +209,12 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
|
||||
foreach (var (key, asset) in _applicationView.CUE4Parse.Provider.Files)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (value is not VfsEntry entry || paths.Contains(key) || entry.Path.EndsWith(".uexp") ||
|
||||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
|
||||
if (asset.IsUePackagePayload || paths.Contains(key)) continue;
|
||||
|
||||
entries.Add(entry);
|
||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
||||
entries.Add(asset);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -263,14 +261,12 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
return entries;
|
||||
}
|
||||
|
||||
private void AddEntry(string path, long uncompressedSize, bool isEncrypted, List<VfsEntry> entries)
|
||||
private void AddEntry(string path, long uncompressedSize, bool isEncrypted, List<GameFile> entries)
|
||||
{
|
||||
if (path.EndsWith(".uexp") || path.EndsWith(".ubulk") || path.EndsWith(".uptnl") ||
|
||||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(path, out var asset) || asset is not VfsEntry entry ||
|
||||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
|
||||
if (!_applicationView.CUE4Parse.Provider.Files.TryGetValue(path, out var asset) ||
|
||||
asset.IsUePackagePayload || asset.Size == uncompressedSize && asset.IsEncrypted == isEncrypted)
|
||||
return;
|
||||
|
||||
entries.Add(entry);
|
||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
||||
entries.Add(asset);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
|
||||
|
|
@ -19,68 +20,68 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
||||
return;
|
||||
|
||||
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
|
||||
if (!assetItems.Any()) return;
|
||||
var entries = ((IList) parameters[1]).Cast<GameFile>().ToArray();
|
||||
if (!entries.Any()) return;
|
||||
|
||||
var updateUi = assetItems.Length > 1 ? EBulkType.Auto : EBulkType.None;
|
||||
var updateUi = entries.Length > 1 ? EBulkType.Auto : EBulkType.None;
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
switch (trigger)
|
||||
{
|
||||
case "Assets_Extract_New_Tab":
|
||||
foreach (var asset in assetItems)
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, true);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, true);
|
||||
}
|
||||
break;
|
||||
case "Assets_Show_Metadata":
|
||||
foreach (var asset in assetItems)
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ShowMetadata(asset);
|
||||
contextViewModel.CUE4Parse.ShowMetadata(entry);
|
||||
}
|
||||
break;
|
||||
case "Assets_Export_Data":
|
||||
foreach (var asset in assetItems)
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ExportData(asset);
|
||||
contextViewModel.CUE4Parse.ExportData(entry);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Properties":
|
||||
foreach (var asset in assetItems)
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Properties | updateUi);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Properties | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Textures":
|
||||
foreach (var asset in assetItems)
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Textures | updateUi);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Textures | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Models":
|
||||
foreach (var asset in assetItems)
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Meshes | updateUi);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Meshes | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Animations":
|
||||
foreach (var asset in assetItems)
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Animations | updateUi);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Animations | updateUi);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,44 +32,44 @@ public class TabCommand : ViewModelCommand<TabItem>
|
|||
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel);
|
||||
break;
|
||||
case "Asset_Export_Data":
|
||||
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(contextViewModel.Asset));
|
||||
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(contextViewModel.Entry));
|
||||
break;
|
||||
case "Asset_Save_Properties":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Properties);
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Properties);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Textures":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Textures);
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Textures);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Models":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Meshes);
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Meshes);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Animations":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Animations);
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Animations);
|
||||
});
|
||||
break;
|
||||
case "Open_Properties":
|
||||
if (contextViewModel.Asset.FileName == "New Tab" || contextViewModel.Document == null) return;
|
||||
Helper.OpenWindow<AdonisWindow>(contextViewModel.Asset.FileName + " (Properties)", () =>
|
||||
if (contextViewModel.Entry.Name == "New Tab" || contextViewModel.Document == null) return;
|
||||
Helper.OpenWindow<AdonisWindow>(contextViewModel.Entry.Name + " (Properties)", () =>
|
||||
{
|
||||
new PropertiesPopout(contextViewModel)
|
||||
{
|
||||
Title = contextViewModel.Asset.FileName + " (Properties)"
|
||||
Title = contextViewModel.Entry.Name + " (Properties)"
|
||||
}.Show();
|
||||
});
|
||||
break;
|
||||
case "Copy_Asset_Path":
|
||||
Clipboard.SetText(contextViewModel.Asset.FullPath);
|
||||
Clipboard.SetText(contextViewModel.Entry.Path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.ComponentModel;
|
|||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Data;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using FModel.Framework;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
|
@ -32,12 +33,12 @@ public class SearchViewModel : ViewModel
|
|||
}
|
||||
|
||||
public int ResultsCount => SearchResults?.Count ?? 0;
|
||||
public RangeObservableCollection<AssetItem> SearchResults { get; }
|
||||
public RangeObservableCollection<GameFile> SearchResults { get; }
|
||||
public ICollectionView SearchResultsView { get; }
|
||||
|
||||
public SearchViewModel()
|
||||
{
|
||||
SearchResults = new RangeObservableCollection<AssetItem>();
|
||||
SearchResults = new RangeObservableCollection<GameFile>();
|
||||
SearchResultsView = new ListCollectionView(SearchResults);
|
||||
}
|
||||
|
||||
|
|
@ -51,14 +52,14 @@ public class SearchViewModel : ViewModel
|
|||
|
||||
private bool ItemFilter(object item, IEnumerable<string> filters)
|
||||
{
|
||||
if (item is not AssetItem assetItem)
|
||||
if (item is not GameFile entry)
|
||||
return true;
|
||||
|
||||
if (!HasRegexEnabled)
|
||||
return filters.All(x => assetItem.FullPath.Contains(x, HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
||||
return filters.All(x => entry.Path.Contains(x, HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var o = RegexOptions.None;
|
||||
if (!HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase;
|
||||
return new Regex(FilterText, o).Match(assetItem.FullPath).Success;
|
||||
return new Regex(FilterText, o).Match(entry.Path).Success;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using ICSharpCode.AvalonEdit.Document;
|
|||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
using Serilog;
|
||||
using SkiaSharp;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
@ -16,6 +15,7 @@ using System.Windows;
|
|||
using System.Windows.Media.Imaging;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.Utils;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
|
@ -93,11 +93,11 @@ public class TabItem : ViewModel
|
|||
{
|
||||
public string ParentExportType { get; private set; }
|
||||
|
||||
private AssetItem _asset;
|
||||
public AssetItem Asset
|
||||
private GameFile _entry;
|
||||
public GameFile Entry
|
||||
{
|
||||
get => _asset;
|
||||
set => SetProperty(ref _asset, value);
|
||||
get => _entry;
|
||||
set => SetProperty(ref _entry, value);
|
||||
}
|
||||
|
||||
private bool _hasSearchOpen;
|
||||
|
|
@ -209,16 +209,16 @@ public class TabItem : ViewModel
|
|||
private GoToCommand _goToCommand;
|
||||
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
|
||||
|
||||
public TabItem(AssetItem asset, string parentExportType)
|
||||
public TabItem(GameFile entry, string parentExportType)
|
||||
{
|
||||
Asset = asset;
|
||||
Entry = entry;
|
||||
ParentExportType = parentExportType;
|
||||
_images = new ObservableCollection<TabImage>();
|
||||
}
|
||||
|
||||
public void SoftReset(AssetItem asset)
|
||||
public void SoftReset(GameFile entry)
|
||||
{
|
||||
Asset = asset;
|
||||
Entry = entry;
|
||||
ParentExportType = string.Empty;
|
||||
ScrollTrigger = null;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
|
|
@ -306,7 +306,7 @@ public class TabItem : ViewModel
|
|||
|
||||
var fileName = image.ExportName + ext;
|
||||
var path = Path.Combine(UserSettings.Default.TextureDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Asset.Directory : "", fileName!).Replace('\\', '/');
|
||||
UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", fileName!).Replace('\\', '/');
|
||||
|
||||
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
||||
|
||||
|
|
@ -327,9 +327,9 @@ public class TabItem : ViewModel
|
|||
|
||||
public void SaveProperty(bool updateUi)
|
||||
{
|
||||
var fileName = Path.ChangeExtension(Asset.FileName, ".json");
|
||||
var fileName = Path.ChangeExtension(Entry.Name, ".json");
|
||||
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Asset.Directory : "", fileName).Replace('\\', '/');
|
||||
UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", fileName).Replace('\\', '/');
|
||||
|
||||
Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
||||
|
||||
|
|
@ -386,21 +386,19 @@ public class TabControlViewModel : ViewModel
|
|||
}
|
||||
|
||||
public void AddTab() => AddTab("New Tab");
|
||||
public void AddTab(string title) => AddTab(new AssetItem(title));
|
||||
public void AddTab(AssetItem asset, string parentExportType = null)
|
||||
public void AddTab(string title) => AddTab(new FakeGameFile(title));
|
||||
public void AddTab(GameFile entry, string parentExportType = null)
|
||||
{
|
||||
if (!CanAddTabs) return;
|
||||
|
||||
var p = parentExportType ?? string.Empty;
|
||||
if (SelectedTab?.Asset.FileName == "New Tab")
|
||||
if (SelectedTab?.Entry.Name == "New Tab")
|
||||
{
|
||||
SelectedTab.Asset = asset;
|
||||
SelectedTab.Entry = entry;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanAddTabs) return;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
_tabItems.Add(new TabItem(asset, p));
|
||||
_tabItems.Add(new TabItem(entry, parentExportType ?? string.Empty));
|
||||
SelectedTab = _tabItems.Last();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ public partial class AvalonEditor
|
|||
if (sender is not TextEditor avalonEditor || DataContext is not TabItem tabItem ||
|
||||
avalonEditor.Document == null || string.IsNullOrEmpty(avalonEditor.Document.Text))
|
||||
return;
|
||||
avalonEditor.Document.FileName = tabItem.Asset.FullPath.SubstringBeforeLast('.');
|
||||
avalonEditor.Document.FileName = tabItem.Entry.PathWithoutExtension;
|
||||
|
||||
if (!_savedCarets.ContainsKey(avalonEditor.Document.FileName))
|
||||
_ignoreCaret = true;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public partial class PropertiesPopout
|
|||
MyAvalonEditor.Document = new TextDocument
|
||||
{
|
||||
Text = contextViewModel.Document.Text,
|
||||
FileName = contextViewModel.Asset.FullPath.SubstringBeforeLast('.')
|
||||
FileName = contextViewModel.Entry.PathWithoutExtension
|
||||
};
|
||||
MyAvalonEditor.FontSize = contextViewModel.FontSize;
|
||||
MyAvalonEditor.SyntaxHighlighting = contextViewModel.Highlighter;
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FModel.Views.Resources.Converters;
|
||||
|
||||
public class FileExtensionEqualsConverter : IValueConverter
|
||||
{
|
||||
public static readonly FileExtensionEqualsConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value.ToString().EndsWith(parameter.ToString());
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using CUE4Parse.Utils;
|
||||
|
||||
namespace FModel.Views.Resources.Converters;
|
||||
|
||||
public class FullPathToFileConverter : IValueConverter
|
||||
{
|
||||
public static readonly FullPathToFileConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value.ToString().SubstringAfterLast('/');
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -203,7 +203,7 @@
|
|||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Grid>
|
||||
<TextBlock Text="No archives found in given directory yet" 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>
|
||||
|
|
@ -596,19 +596,19 @@
|
|||
|
||||
<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}}" />
|
||||
<TextBlock Grid.Column="1" HorizontalAlignment="Left" Text="{Binding Name}" />
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding FullPath, Converter={x:Static converters:FileExtensionEqualsConverter.Instance}, ConverterParameter='.uasset'}" Value="True">
|
||||
<DataTrigger Binding="{Binding Extension}" Value="uasset">
|
||||
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding FullPath, Converter={x:Static converters:FileExtensionEqualsConverter.Instance}, ConverterParameter='.ini'}" Value="True">
|
||||
<DataTrigger Binding="{Binding Extension}" Value="ini">
|
||||
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_ini.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding FullPath, Converter={x:Static converters:FileExtensionEqualsConverter.Instance}, ConverterParameter='.png'}" Value="True">
|
||||
<DataTrigger Binding="{Binding Extension}" Value="png">
|
||||
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_png.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding FullPath, Converter={x:Static converters:FileExtensionEqualsConverter.Instance}, ConverterParameter='.psd'}" Value="True">
|
||||
<DataTrigger Binding="{Binding Extension}" Value="psd">
|
||||
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_psd.png" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
|
|
@ -651,7 +651,7 @@
|
|||
<MouseBinding MouseAction="MiddleClick" Command="{Binding SelectedItem.TabCommand, ElementName=TabControlName}" CommandParameter="{Binding}" />
|
||||
</DockPanel.InputBindings>
|
||||
|
||||
<TextBlock DockPanel.Dock="Left" Text="{Binding Asset.FileName}" TextTrimming="CharacterEllipsis" ToolTip="{Binding Asset.FileName}">
|
||||
<TextBlock DockPanel.Dock="Left" Text="{Binding Entry.Name}" TextTrimming="CharacterEllipsis" ToolTip="{Binding Entry.Name}">
|
||||
<TextBlock.Width>
|
||||
<MultiBinding Converter="{x:Static converters:TabSizeConverter.Instance}" ConverterParameter="6">
|
||||
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}" />
|
||||
|
|
@ -856,7 +856,10 @@
|
|||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Export Raw Data (.uasset)" Command="{Binding TabCommand}" CommandParameter="Asset_Export_Data">
|
||||
<MenuItem Command="{Binding TabCommand}" CommandParameter="Asset_Export_Data">
|
||||
<MenuItem.Header>
|
||||
<TextBlock Text="{Binding Entry.Extension, FallbackValue='uasset', StringFormat='Export Raw Data (.{0})'}" />
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -913,7 +916,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Jump to Package Folder" Command="{Binding GoToCommand}" CommandParameter="{Binding Asset.Directory}">
|
||||
<MenuItem Header="Jump to Package Folder" Command="{Binding GoToCommand}" CommandParameter="{Binding Entry.Directory}">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
|
|||
|
|
@ -96,14 +96,14 @@
|
|||
<GridViewColumn Width="900" Header="Path" adonisExtensions:GridViewSortExtension.PropertyName="Path">
|
||||
<GridViewColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock HorizontalAlignment="Left" Text="{Binding FullPath}" />
|
||||
<TextBlock HorizontalAlignment="Left" Text="{Binding Path}" />
|
||||
</DataTemplate>
|
||||
</GridViewColumn.CellTemplate>
|
||||
</GridViewColumn>
|
||||
<GridViewColumn Width="225" Header="Archive" adonisExtensions:GridViewSortExtension.PropertyName="Archive">
|
||||
<GridViewColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock HorizontalAlignment="Left" Text="{Binding Archive}" />
|
||||
<TextBlock HorizontalAlignment="Left" Text="{Binding Vfs.Name}" />
|
||||
</DataTemplate>
|
||||
</GridViewColumn.CellTemplate>
|
||||
</GridViewColumn>
|
||||
|
|
@ -155,7 +155,14 @@
|
|||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Export Raw Data (.uasset)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.Header>
|
||||
<TextBlock
|
||||
Text="{Binding DataContext.SelectedItem.Extension,
|
||||
FallbackValue='uasset',
|
||||
StringFormat='Export Raw Data (.{0})',
|
||||
RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
</MenuItem.Header>
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Export_Data" />
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using FModel.Services;
|
||||
using FModel.ViewModels;
|
||||
|
||||
|
|
@ -30,12 +31,12 @@ public partial class SearchView
|
|||
|
||||
private async void OnAssetDoubleClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (SearchListView.SelectedItem is not AssetItem assetItem)
|
||||
if (SearchListView.SelectedItem is not GameFile entry)
|
||||
return;
|
||||
|
||||
WindowState = WindowState.Minimized;
|
||||
MainWindow.YesWeCats.AssetsListName.ItemsSource = null;
|
||||
var folder = _applicationView.CustomDirectories.GoToCommand.JumpTo(assetItem.Directory);
|
||||
var folder = _applicationView.CustomDirectories.GoToCommand.JumpTo(entry.Directory);
|
||||
if (folder == null) return;
|
||||
|
||||
MainWindow.YesWeCats.Activate();
|
||||
|
|
@ -46,18 +47,18 @@ public partial class SearchView
|
|||
do
|
||||
{
|
||||
await Task.Delay(100);
|
||||
MainWindow.YesWeCats.AssetsListName.SelectedItem = assetItem;
|
||||
MainWindow.YesWeCats.AssetsListName.ScrollIntoView(assetItem);
|
||||
MainWindow.YesWeCats.AssetsListName.SelectedItem = entry;
|
||||
MainWindow.YesWeCats.AssetsListName.ScrollIntoView(entry);
|
||||
} while (MainWindow.YesWeCats.AssetsListName.SelectedItem == null);
|
||||
}
|
||||
|
||||
private async void OnAssetExtract(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (SearchListView.SelectedItem is not AssetItem assetItem)
|
||||
if (SearchListView.SelectedItem is not GameFile entry)
|
||||
return;
|
||||
|
||||
WindowState = WindowState.Minimized;
|
||||
await _threadWorkerView.Begin(cancellationToken => _applicationView.CUE4Parse.Extract(cancellationToken, assetItem, true));
|
||||
await _threadWorkerView.Begin(cancellationToken => _applicationView.CUE4Parse.Extract(cancellationToken, entry, true));
|
||||
|
||||
MainWindow.YesWeCats.Activate();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user