- [GUI] Add option to export selected assets in AssetBrowser.

This commit is contained in:
Razmoth
2024-02-02 22:36:50 +04:00
parent 0a02ea9cee
commit b32843860e
7 changed files with 408 additions and 136 deletions

View File

@@ -2,11 +2,13 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using static AssetStudio.GUI.Studio;
namespace AssetStudio.GUI
{
@@ -14,7 +16,7 @@ namespace AssetStudio.GUI
{
private readonly MainForm _parent;
private readonly List<AssetEntry> _assetEntries;
private readonly List<string> _columnNames;
private readonly Dictionary<string, Regex> _filters;
private SortOrder _sortOrder;
private DataGridViewColumn _sortedColumn;
@@ -23,7 +25,7 @@ namespace AssetStudio.GUI
{
InitializeComponent();
_parent = form;
_columnNames = new List<string>();
_filters = new Dictionary<string, Regex>();
_assetEntries = new List<AssetEntry>();
}
@@ -39,14 +41,20 @@ namespace AssetStudio.GUI
await Task.Run(() => ResourceMap.FromFile(path));
_sortedColumn = null;
_columnNames.Clear();
_columnNames.AddRange(typeof(AssetEntry).GetProperties().Select(x => x.Name).ToList());
var names = typeof(AssetEntry).GetProperties().Select(x => x.Name);
_filters.Clear();
foreach(var name in names)
{
_filters.Add(name, new Regex(""));
}
_assetEntries.Clear();
_assetEntries.AddRange(ResourceMap.GetEntries());
assetDataGridView.Columns.Clear();
assetDataGridView.Columns.AddRange(_columnNames.Select(x => new DataGridViewTextBoxColumn() { Name = x, HeaderText = x, SortMode = DataGridViewColumnSortMode.Programmatic }).ToArray());
assetDataGridView.Columns.AddRange(names.Select(x => new DataGridViewTextBoxColumn() { Name = x, HeaderText = x, SortMode = DataGridViewColumnSortMode.Programmatic }).ToArray());
assetDataGridView.Columns.GetLastColumn(DataGridViewElementStates.None, DataGridViewElementStates.None).AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
assetDataGridView.Rows.Clear();
@@ -75,48 +83,242 @@ namespace AssetStudio.GUI
_parent.Invoke(() => _parent.LoadPaths(files.ToArray()));
}
}
private void searchTextBox_KeyPress(object sender, KeyPressEventArgs e)
private async void exportSelected_Click(object sender, EventArgs e)
{
if (e.KeyChar == (char)Keys.Enter)
var saveFolderDialog = new OpenFolderDialog();
if (saveFolderDialog.ShowDialog(this) == DialogResult.OK)
{
var filters = new Dictionary<string, Regex>();
var entries = assetDataGridView.SelectedRows.Cast<DataGridViewRow>().Select(x => _assetEntries[x.Index]).ToArray();
var value = searchTextBox.Text;
var options = value.Split(' ');
for (int i = 0; i < options.Length; i++)
_parent.Invoke(_parent.ResetForm);
var statusStripUpdate = StatusStripUpdate;
assetsManager.Game = Studio.Game;
StatusStripUpdate = Logger.Info;
var files = new List<string>(entries.Select(x => x.Source).ToHashSet());
await Task.Run(async () =>
{
var option = options[i];
var arguments = option.Split('=');
if (arguments.Length != 2)
for (int i = 0; i < files.Count; i++)
{
Logger.Error($"Invalid argument at index {i + 1}");
continue;
}
var toExportAssets = new List<AssetItem>();
var (name, regex) = (arguments[0], arguments[1]);
if (!_columnNames.Contains(name, StringComparer.OrdinalIgnoreCase))
{
Logger.Error($"Unknonw argument {name}");
continue;
var file = files[i];
assetsManager.LoadFiles(file);
if (assetsManager.assetsFileList.Count > 0)
{
BuildAssetData(toExportAssets, entries);
await ExportAssets(saveFolderDialog.Folder, toExportAssets, ExportType.Convert, i == files.Count - 1);
}
toExportAssets.Clear();
assetsManager.Clear();
}
try
});
StatusStripUpdate = statusStripUpdate;
}
}
private void BuildAssetData(List<AssetItem> exportableAssets, AssetEntry[] entries)
{
var objectAssetItemDic = new Dictionary<Object, AssetItem>();
var mihoyoBinDataNames = new List<(PPtr<Object>, string)>();
var containers = new List<(PPtr<Object>, string)>();
foreach (var assetsFile in assetsManager.assetsFileList)
{
foreach (var asset in assetsFile.Objects)
{
ProcessAssetData(asset, exportableAssets, entries, objectAssetItemDic, mihoyoBinDataNames, containers);
}
}
foreach ((var pptr, var name) in mihoyoBinDataNames)
{
if (pptr.TryGet<MiHoYoBinData>(out var obj))
{
var assetItem = objectAssetItemDic[obj];
if (int.TryParse(name, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var hash))
{
filters[name] = new Regex(regex, RegexOptions.IgnoreCase);
assetItem.Text = name;
assetItem.Container = hash.ToString();
}
catch (Exception)
else assetItem.Text = $"BinFile #{assetItem.m_PathID}";
}
}
foreach ((var pptr, var container) in containers)
{
if (pptr.TryGet(out var obj))
{
var item = objectAssetItemDic[obj];
if (entries.Any(x => x.Container == container))
{
Logger.Error($"Invalid regex {regex} at argument {name}");
continue;
item.Container = container;
}
else
{
exportableAssets.Remove(item);
}
}
}
containers.Clear();
}
private void ProcessAssetData(Object asset, List<AssetItem> exportableAssets, AssetEntry[] entries, Dictionary<Object, AssetItem> objectAssetItemDic, List<(PPtr<Object>, string)> mihoyoBinDataNames, List<(PPtr<Object>, string)> containers)
{
var assetItem = new AssetItem(asset);
objectAssetItemDic.Add(asset, assetItem);
var exportable = false;
switch (asset)
{
case GameObject m_GameObject:
exportable = ClassIDType.GameObject.CanExport() && m_GameObject.HasModel();
break;
case Texture2D m_Texture2D:
if (!string.IsNullOrEmpty(m_Texture2D.m_StreamData?.path))
assetItem.FullSize = asset.byteSize + m_Texture2D.m_StreamData.size;
exportable = ClassIDType.Texture2D.CanExport();
break;
case AudioClip m_AudioClip:
if (!string.IsNullOrEmpty(m_AudioClip.m_Source))
assetItem.FullSize = asset.byteSize + m_AudioClip.m_Size;
exportable = ClassIDType.AudioClip.CanExport();
break;
case VideoClip m_VideoClip:
if (!string.IsNullOrEmpty(m_VideoClip.m_OriginalPath))
assetItem.FullSize = asset.byteSize + m_VideoClip.m_ExternalResources.m_Size;
exportable = ClassIDType.VideoClip.CanExport();
break;
case MonoBehaviour m_MonoBehaviour:
exportable = ClassIDType.MonoBehaviour.CanExport();
break;
case AssetBundle m_AssetBundle:
foreach (var m_Container in m_AssetBundle.m_Container)
{
var preloadIndex = m_Container.Value.preloadIndex;
var preloadSize = m_Container.Value.preloadSize;
var preloadEnd = preloadIndex + preloadSize;
for (int k = preloadIndex; k < preloadEnd; k++)
{
containers.Add((m_AssetBundle.m_PreloadTable[k], m_Container.Key));
}
}
_assetEntries.Clear();
_assetEntries.AddRange(ResourceMap.GetEntries().FindAll(x => x.Matches(filters)));
exportable = ClassIDType.AssetBundle.CanExport();
break;
case IndexObject m_IndexObject:
foreach (var index in m_IndexObject.AssetMap)
{
mihoyoBinDataNames.Add((index.Value.Object, index.Key));
}
assetDataGridView.Rows.Clear();
assetDataGridView.RowCount = _assetEntries.Count;
assetDataGridView.Refresh();
exportable = ClassIDType.IndexObject.CanExport();
break;
case ResourceManager m_ResourceManager:
foreach (var m_Container in m_ResourceManager.m_Container)
{
containers.Add((m_Container.Value, m_Container.Key));
}
exportable = ClassIDType.GameObject.CanExport();
break;
case Mesh _ when ClassIDType.Mesh.CanExport():
case TextAsset _ when ClassIDType.TextAsset.CanExport():
case AnimationClip _ when ClassIDType.Font.CanExport():
case Font _ when ClassIDType.GameObject.CanExport():
case MovieTexture _ when ClassIDType.MovieTexture.CanExport():
case Sprite _ when ClassIDType.Sprite.CanExport():
case Material _ when ClassIDType.Material.CanExport():
case MiHoYoBinData _ when ClassIDType.MiHoYoBinData.CanExport():
case Shader _ when ClassIDType.Shader.CanExport():
case Animator _ when ClassIDType.Animator.CanExport():
exportable = true;
break;
}
if (assetItem.Text == "")
{
assetItem.Text = assetItem.TypeString + assetItem.UniqueID;
}
if (entries.Any(x => x.Name == assetItem.Text && x.Type == assetItem.Type) && exportable)
{
exportableAssets.Add(assetItem);
}
}
private void FilterAssetDataGrid()
{
_assetEntries.Clear();
_assetEntries.AddRange(ResourceMap.GetEntries().FindAll(x => x.Matches(_filters)));
assetDataGridView.Rows.Clear();
assetDataGridView.RowCount = _assetEntries.Count;
assetDataGridView.Refresh();
}
private void TryAddFilter(string name, string value)
{
Regex regex;
try
{
regex = new Regex(value, RegexOptions.IgnoreCase);
}
catch (Exception)
{
Logger.Error($"Invalid regex {value}");
return;
}
if (!_filters.TryGetValue(name, out var filter))
{
_filters.Add(name, regex);
}
else if (filter != regex)
{
_filters[name] = regex;
}
}
private void NameTextBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (sender is TextBox textBox && e.KeyChar == (char)Keys.Enter)
{
var value = textBox.Text;
TryAddFilter("Name", value);
FilterAssetDataGrid();
}
}
private void ContainerTextBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (sender is TextBox textBox && e.KeyChar == (char)Keys.Enter)
{
var value = textBox.Text;
TryAddFilter("Container", value);
FilterAssetDataGrid();
}
}
private void SourceTextBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (sender is TextBox textBox && e.KeyChar == (char)Keys.Enter)
{
var value = textBox.Text;
TryAddFilter("Source", value);
FilterAssetDataGrid();
}
}
private void PathTextBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (sender is TextBox textBox && e.KeyChar == (char)Keys.Enter)
{
var value = textBox.Text;
TryAddFilter("PathID", value);
FilterAssetDataGrid();
}
}
private void TypeTextBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (sender is TextBox textBox && e.KeyChar == (char)Keys.Enter)
{
var value = textBox.Text;
TryAddFilter("Type", value);
FilterAssetDataGrid();
}
}
private void AssetDataGridView_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)