Skip to content

Instantly share code, notes, and snippets.

@viruseg
Last active May 18, 2024 10:24
Show Gist options
  • Save viruseg/7953345c633fcb0e7f8bff722a7e8775 to your computer and use it in GitHub Desktop.
Save viruseg/7953345c633fcb0e7f8bff722a7e8775 to your computer and use it in GitHub Desktop.
Unity Package Extractor. Extract your .unitypackage with .net C#
Use the command line to extract the package.
program.exe packagePath outputPath extractMetaFiles
program.exe "d:\dir\package.unitypackage" "d:\outputDir" "true"
using System.Diagnostics;
using System.IO.Compression;
using System.Text;
using ICSharpCode.SharpZipLib.Tar; //nuget package SharpZipLib
var packagePath = args.Length >= 1 ? args[0] : null;
var outputPath = args.Length >= 2 ? args[1] : null;
var extractMetaFiles = args.Length < 3 || Convert.ToBoolean(args[2]);
if (string.IsNullOrEmpty(packagePath))
throw new ArgumentException("No .unitypackage path was given. \n\nUSAGE: program.exe \"XXX.unitypackage\" \"optional\\output\\path\" \"optional extractMetaFiles(true or false)\" \nprogram.exe \"d:\\dir\\package.unitypackage\" \"d:\\outputDir\" \"true\"\n");
var startTime = Stopwatch.StartNew();
UnityPackageExtractor.ExtractPackage(packagePath, outputPath, extractMetaFiles);
startTime.Stop();
Console.WriteLine($"--- Finished in {startTime.Elapsed.TotalSeconds} seconds ---");
internal static class UnityPackageExtractor
{
public static void ExtractPackage(string packagePath, string? outputPath, bool extractMetaFiles)
{
outputPath ??= Directory.GetCurrentDirectory();
using var tmpDir = new TemporaryDirectory();
ExtractTarGz(packagePath, tmpDir.Path);
Directory.EnumerateDirectories(tmpDir.Path)
.ToList()
.ForEach(dirEntry =>
{
var assetEntryDir = Path.Combine(tmpDir.Path, dirEntry);
if (!File.Exists(Path.Combine(assetEntryDir, "pathname")) ||
!File.Exists(Path.Combine(assetEntryDir, "asset")))
return;
using var reader = new StreamReader(Path.Combine(assetEntryDir, "pathname"));
var pathname = reader.ReadLine();
if (string.IsNullOrEmpty(pathname)) return;
pathname = pathname.TrimEnd('\n');
var name = Path.GetFileName(pathname);
var path = pathname[..^name.Length];
name = string.Join("_", name.Split(Path.GetInvalidFileNameChars()));
path = string.Join("_", path.Split(Path.GetInvalidPathChars()));
pathname = path + name;
var assetOutPath = Path.Combine(outputPath, pathname);
if (!Path.GetFullPath(assetOutPath).StartsWith(Path.GetFullPath(outputPath)))
{
Console.WriteLine($"WARNING: Skipping '{dirEntry}' as '{assetOutPath}' is outside of '{outputPath}'.");
return;
}
Console.WriteLine($"Extracting '{dirEntry}' as '{pathname}'");
Directory.CreateDirectory(Path.GetDirectoryName(assetOutPath));
File.Move(Path.Combine(assetEntryDir, "asset"), assetOutPath);
if (extractMetaFiles) File.Move(Path.Combine(assetEntryDir, "asset.meta"), assetOutPath + ".meta");
});
}
private static void ExtractTarGz(string filename, string outputDir)
{
using var fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
using var gzipStream = new GZipStream(fs, CompressionMode.Decompress);
using var tarArchive = TarArchive.CreateInputTarArchive(gzipStream, Encoding.UTF8);
tarArchive.ExtractContents(outputDir);
}
private class TemporaryDirectory : IDisposable
{
public string Path { get; }
public TemporaryDirectory()
{
Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName());
Directory.CreateDirectory(Path);
}
public void Dispose()
{
if (Directory.Exists(Path)) Directory.Delete(Path, true);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment