Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions AnimeStudio.GUI/Components/AssetItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public class AssetItem : ListViewItem
public string InfoText;
public string UniqueID;
public GameObjectTreeNode TreeNode;
public bool IsVirtual;
public string ExternalPath;
public string VirtualContent;

public AssetItem(Object asset)
{
Expand All @@ -28,6 +31,21 @@ public AssetItem(Object asset)
Hash = asset.GetHash();
}

public AssetItem(string name, ClassIDType type, string externalPath, long fullSize, string container = "")
{
Asset = null;
SourceFile = null;
Text = name;
Type = type;
TypeString = type.ToString();
m_PathID = 0;
FullSize = fullSize;
Hash = string.Empty;
Container = container;
IsVirtual = true;
ExternalPath = externalPath;
}

public void SetSubItems()
{
SubItems.AddRange(new[]
Expand Down
61 changes: 61 additions & 0 deletions AnimeStudio.GUI/Exporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,67 @@ namespace AnimeStudio.GUI
{
internal static class Exporter
{
public static bool ExportVirtualAsset(AssetItem item, string exportPath, ExportType exportType)
{
return exportType switch
{
ExportType.Convert => ExportVirtualConvertFile(item, exportPath),
ExportType.Raw => ExportVirtualRawFile(item, exportPath),
ExportType.Dump => ExportVirtualDumpFile(item, exportPath),
ExportType.JSON => ExportVirtualJsonFile(item, exportPath),
_ => false,
};
}

private static bool ExportVirtualConvertFile(AssetItem item, string exportPath)
{
switch (item.Type)
{
case ClassIDType.Texture2D:
return AFKJourneyUtils.DecodeDxtFile(item.ExternalPath, Path.Combine(exportPath, item.Text)) != null;
case ClassIDType.TextAsset:
if (!TryExportFile(exportPath, item, ".json", out var textPath))
return false;
var text = item.VirtualContent ?? AFKJourneyUtils.DecryptJsoneToText(File.ReadAllBytes(item.ExternalPath));
File.WriteAllText(textPath, text);
return true;
default:
return false;
}
}

private static bool ExportVirtualRawFile(AssetItem item, string exportPath)
{
var extension = Path.GetExtension(item.ExternalPath);
if (!TryExportFile(exportPath, item, string.IsNullOrEmpty(extension) ? ".dat" : extension, out var rawPath))
return false;
File.Copy(item.ExternalPath, rawPath, overwrite: false);
return true;
}

private static bool ExportVirtualDumpFile(AssetItem item, string exportPath)
{
if (!TryExportFile(exportPath, item, ".txt", out var dumpPath))
return false;
var text = item.VirtualContent ?? item.InfoText ?? item.ExternalPath;
File.WriteAllText(dumpPath, text);
return true;
}

private static bool ExportVirtualJsonFile(AssetItem item, string exportPath)
{
if (item.Type != ClassIDType.TextAsset)
{
return ExportVirtualDumpFile(item, exportPath);
}

if (!TryExportFile(exportPath, item, ".json", out var jsonPath))
return false;
var text = item.VirtualContent ?? AFKJourneyUtils.DecryptJsoneToText(File.ReadAllBytes(item.ExternalPath));
File.WriteAllText(jsonPath, text);
return true;
}

public static bool ExportTexture2D(AssetItem item, string exportPath)
{
var m_Texture2D = (Texture2D)item.Asset;
Expand Down
155 changes: 140 additions & 15 deletions AnimeStudio.GUI/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ public async void LoadPaths(List<AssetFilterDataItem> filterData, params string[
{
await Task.Run(() => assetsManager.LoadFiles(paths));
}
BuildAssetStructures();
BuildAssetStructures(paths);
}

private async void loadFile_Click(object sender, EventArgs e)
Expand All @@ -405,7 +405,7 @@ private async void loadFile_Click(object sender, EventArgs e)
paths = File.ReadAllLines(paths[0]);
}
await Task.Run(() => assetsManager.LoadFiles(paths));
BuildAssetStructures();
BuildAssetStructures(paths);
}
}

Expand All @@ -424,7 +424,7 @@ private async void loadFolder_Click(object sender, EventArgs e)
assetsManager.SpecifyUnityVersion = specifyUnityVersion.Text;
assetsManager.Game = Studio.Game;
await Task.Run(() => assetsManager.LoadFolder(openFolderDialog.Folder));
BuildAssetStructures();
BuildAssetStructures(openFolderDialog.Folder);
}
}

Expand Down Expand Up @@ -461,10 +461,20 @@ private async void extractFolderToolStripMenuItem_Click(object sender, EventArgs
}
}

private async void BuildAssetStructures()
private async void BuildAssetStructures(params string[] sourcePaths)
{
if (assetsManager.assetsFileList.Count == 0)
{
if (Studio.Game?.Type == GameType.AFKJourney && AddAFKJourneySpecialAssets(sourcePaths) > 0)
{
visibleAssets = exportableAssets;
assetListView.VirtualListSize = visibleAssets.Count;
PopulateTypeFilters();
Text = $"AnimeStudio v{System.Windows.Forms.Application.ProductVersion} - AFK Journey loose assets";
StatusStripUpdate($"Loaded {visibleAssets.Count} AFK Journey loose files for preview.");
return;
}

StatusStripUpdate("No Unity file can be loaded.");
return;
}
Expand Down Expand Up @@ -493,6 +503,12 @@ private async void BuildAssetStructures()

Text = $"AnimeStudio v{System.Windows.Forms.Application.ProductVersion} - {productName} - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}";

if (Studio.Game?.Type == GameType.AFKJourney)
{
AddAFKJourneySpecialAssets(sourcePaths);
visibleAssets = exportableAssets;
}

assetListView.VirtualListSize = visibleAssets.Count;

sceneTreeView.BeginUpdate();
Expand All @@ -515,6 +531,69 @@ private async void BuildAssetStructures()
typeMap.Clear();
classesListView.EndUpdate();

PopulateTypeFilters();
var log = $"Finished loading {assetsManager.assetsFileList.Count} files with {visibleAssets.Count} exportable assets";
var m_ObjectsCount = assetsManager.assetsFileList.Sum(x => x.m_Objects.Count);
var objectsCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count);
if (m_ObjectsCount != objectsCount)
{
log += $" and {m_ObjectsCount - objectsCount} assets failed to read";
}
StatusStripUpdate(log);
}

private int AddAFKJourneySpecialAssets(IEnumerable<string> sourcePaths)
{
var added = 0;
var existing = new HashSet<string>(
exportableAssets.Where(asset => asset.IsVirtual && !string.IsNullOrEmpty(asset.ExternalPath)).Select(asset => asset.ExternalPath),
StringComparer.OrdinalIgnoreCase);
foreach (var (file, container) in EnumerateAFKJourneySpecialFiles(sourcePaths))
{
var type = Path.GetExtension(file).Equals(".jsone", StringComparison.OrdinalIgnoreCase)
? ClassIDType.TextAsset
: ClassIDType.Texture2D;
if (!existing.Add(file))
{
continue;
}

var item = new AssetItem(Path.GetFileName(file), type, file, new FileInfo(file).Length, container);
item.InfoText = $"Path: {file}";
item.SetSubItems();
exportableAssets.Add(item);
added++;
}

return added;
}

private static IEnumerable<(string File, string Container)> EnumerateAFKJourneySpecialFiles(IEnumerable<string> sourcePaths)
{
foreach (var sourcePath in sourcePaths.Where(path => !string.IsNullOrEmpty(path)).Distinct(StringComparer.OrdinalIgnoreCase))
{
if (Directory.Exists(sourcePath))
{
var root = Path.GetFullPath(sourcePath);
foreach (var file in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories))
{
if (AFKJourneyUtils.IsPreviewableSpecialFile(file))
{
var directory = Path.GetDirectoryName(Path.GetFullPath(file)) ?? root;
var container = Path.GetRelativePath(root, directory);
yield return (file, container == "." ? string.Empty : container);
}
}
}
else if (File.Exists(sourcePath) && AFKJourneyUtils.IsPreviewableSpecialFile(sourcePath))
{
yield return (sourcePath, string.Empty);
}
}
}

private void PopulateTypeFilters()
{
var types = exportableAssets.Select(x => x.Type).Distinct().OrderBy(x => x.ToString()).ToArray();
foreach (var type in types)
{
Expand All @@ -529,14 +608,6 @@ private async void BuildAssetStructures()
filterTypeToolStripMenuItem.DropDownItems.Add(typeItem);
}
allToolStripMenuItem.Checked = true;
var log = $"Finished loading {assetsManager.assetsFileList.Count} files with {assetListView.Items.Count} exportable assets";
var m_ObjectsCount = assetsManager.assetsFileList.Sum(x => x.m_Objects.Count);
var objectsCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count);
if (m_ObjectsCount != objectsCount)
{
log += $" and {m_ObjectsCount - objectsCount} assets failed to read";
}
StatusStripUpdate(log);
}

private void typeToolStripMenuItem_Click(object sender, EventArgs e)
Expand Down Expand Up @@ -972,7 +1043,9 @@ private void selectAsset(object sender, ListViewItemSelectionChangedEventArgs e)
{
if (tabControl2.SelectedIndex == 1)
{
dumpTextBox.Text = DumpAsset(lastSelectedItem.Asset);
dumpTextBox.Text = lastSelectedItem.IsVirtual
? lastSelectedItem.VirtualContent ?? lastSelectedItem.InfoText ?? lastSelectedItem.ExternalPath
: DumpAsset(lastSelectedItem.Asset);
}
if (enablePreview.Checked)
{
Expand Down Expand Up @@ -1017,6 +1090,12 @@ private void PreviewAsset(AssetItem assetItem)
return;
try
{
if (assetItem.IsVirtual)
{
PreviewVirtualAsset(assetItem);
return;
}

switch (assetItem.Asset)
{
case GameObject m_GameObject when Properties.Settings.Default.enableModelPreview:
Expand Down Expand Up @@ -1080,6 +1159,43 @@ private void PreviewAsset(AssetItem assetItem)
}
}

private void PreviewVirtualAsset(AssetItem assetItem)
{
switch (assetItem.Type)
{
case ClassIDType.Texture2D:
var decoded = AFKJourneyUtils.DecodeDxtToBitmapData(assetItem.ExternalPath);
FlipPixelsVertically(decoded.Pixels, decoded.Width, decoded.Height);
assetItem.InfoText = $"Width: {decoded.Width}\nHeight: {decoded.Height}\nFormat: {decoded.Format}\nPath: {assetItem.ExternalPath}";
PreviewTexture(new DirectBitmap(decoded.Pixels, decoded.Width, decoded.Height));
StatusStripUpdate("AFK Journey loose DXT texture preview");
break;
case ClassIDType.TextAsset:
assetItem.VirtualContent ??= AFKJourneyUtils.DecryptJsoneToText(File.ReadAllBytes(assetItem.ExternalPath));
assetItem.InfoText = $"Path: {assetItem.ExternalPath}";
PreviewText(assetItem.VirtualContent);
StatusStripUpdate("AFK Journey loose JSOne preview");
break;
default:
StatusStripUpdate("Unsupported AFK Journey loose asset preview.");
break;
}
}

private static void FlipPixelsVertically(byte[] pixels, int width, int height)
{
var stride = width * 4;
var row = new byte[stride];
for (var y = 0; y < height / 2; y++)
{
var top = y * stride;
var bottom = (height - y - 1) * stride;
System.Buffer.BlockCopy(pixels, top, row, 0, stride);
System.Buffer.BlockCopy(pixels, bottom, pixels, top, stride);
System.Buffer.BlockCopy(row, 0, pixels, bottom, stride);
}
}

private void PreviewTexture2D(AssetItem assetItem, Texture2D m_Texture2D)
{
var image = m_Texture2D.ConvertToImage(true);
Expand Down Expand Up @@ -1722,8 +1838,12 @@ private void assetListView_MouseClick(object sender, MouseEventArgs e)

if (assetListView.SelectedIndices.Count == 1)
{
goToSceneHierarchyToolStripMenuItem.Visible = true;
showOriginalFileToolStripMenuItem.Visible = true;
var selectedAsset = (AssetItem)assetListView.Items[assetListView.SelectedIndices[0]];
if (!selectedAsset.IsVirtual)
{
goToSceneHierarchyToolStripMenuItem.Visible = true;
showOriginalFileToolStripMenuItem.Visible = true;
}
}
if (assetListView.SelectedIndices.Count >= 1)
{
Expand Down Expand Up @@ -1767,6 +1887,11 @@ private void exportSelectedAssetsToolStripMenuItem_Click(object sender, EventArg
private void showOriginalFileToolStripMenuItem_Click(object sender, EventArgs e)
{
var selectasset = (AssetItem)assetListView.Items[assetListView.SelectedIndices[0]];
if (selectasset.IsVirtual || selectasset.SourceFile == null)
{
return;
}

var args = $"/select, \"{selectasset.SourceFile.originalPath ?? selectasset.SourceFile.fullName}\"";
var pfi = new ProcessStartInfo("explorer.exe", args);
Process.Start(pfi);
Expand Down
21 changes: 20 additions & 1 deletion AnimeStudio.GUI/Studio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ public static int ExtractFile(string[] fileNames, string savePath)

public static int ExtractFile(string fileName, string savePath)
{
if (Game?.Type == GameType.AFKJourney && AFKJourneyUtils.IsSupportedSpecialFile(fileName))
{
return AFKJourneyUtils.TryExtractSpecialFile(fileName, savePath);
}

int extractedCount = 0;
var reader = new FileReader(fileName);
reader = reader.PreProcessing(Game);
Expand Down Expand Up @@ -688,7 +693,11 @@ public static Task ExportAssets(string savePath, List<AssetItem> toExportAssets,
}
break;
case AssetGroupOption.BySource: //source file
if (string.IsNullOrEmpty(asset.SourceFile.originalPath))
if (asset.IsVirtual)
{
exportPath = Path.Combine(savePath, Path.GetFileName(asset.ExternalPath) + "_export");
}
else if (string.IsNullOrEmpty(asset.SourceFile.originalPath))
{
exportPath = Path.Combine(savePath, asset.SourceFile.fileName + "_export");
}
Expand All @@ -705,6 +714,16 @@ public static Task ExportAssets(string savePath, List<AssetItem> toExportAssets,
StatusStripUpdate($"[{exportedCount}/{toExportCount}] Exporting {asset.TypeString}: {asset.Text}");
try
{
if (asset.IsVirtual)
{
if (ExportVirtualAsset(asset, exportPath, exportType))
{
exportedCount++;
}
Progress.Report(++i, toExportCount);
continue;
}

switch (exportType)
{
case ExportType.Raw:
Expand Down
Loading