Skip to content

Instantly share code, notes, and snippets.

@SuperFlue
Last active September 20, 2022 17:52
Show Gist options
  • Save SuperFlue/dfecd60d2fa173ccdd2b5cdc46ad972e to your computer and use it in GitHub Desktop.
Save SuperFlue/dfecd60d2fa173ccdd2b5cdc46ad972e to your computer and use it in GitHub Desktop.
Unity package extractor in PowerShell
# This script can unpack Unitypackages that are based on tar.gz
# Regires an updated version of Windows 10 that has tar.exe buildt in (any currently in-support version of W10 should have this).
# MIT License <SuperFlue@GitHub>
[CmdletBinding()]
param (
# The path to the unitypackage
[Parameter(ValueFromRemainingArguments=$true,Position=0)]
[String]$UnityPackage = (Read-Host "Enter package path"),
# Optional temp folder
# If no folder is specified we create a temporary folder in the same folder as the unitypackage
[Parameter()]
[string]$TempPath,
# Optional out folder
# If no one is specified we output the result in a folder in the same folder as the unitypackage
[Parameter()]
[string]$OutPath,
# Switch to prepare the output to be placed directly in a Unity Project
# Point -OutPath to your Unity Project root folder
# NOTE! You should only do this if you know what you are doing, and preferably only on fresh projects
# This is meant to be able to deploy a set of UnityPackages directly so you don't have to import them one by one
# But doing it this way might cause collisions between assets
[Parameter()]
[Switch]$DeployToProject,
# Switch that decides if we want to include .meta files or not in the extracted result
# If these are included you can just copy and paste the output directly into a Unity folder and things should link up properly
# Though prefered way in that case is to simply import the package properly into Unity in the first place...
[switch]$IncludeMetaFile = $true
)
# For easier handling we create a class to represent each upacked item
# This makes things a bit more predictable when we process the files
class UnityPackageItem {
[System.IO.DirectoryInfo]$CurrentDir
[string]$FullPath
[string]$Type
[int]$AssetLevel
UnityPackageItem(){}
UnityPackageItem([System.IO.DirectoryInfo]$ItemDirectory){
$this.CurrentDir = $ItemDirectory
# Read the full item path from the "pathname" file
$File = [System.IO.StreamReader]::new($ItemDirectory.FullName+"\pathname",[System.Text.Encoding]::UTF8)
$this.FullPath = $File.ReadToEnd()
$File.Dispose()
# To make it easier to create folders in order we figure out which "level" the asset is on
$this.AssetLevel = @($this.FullPath -split "/").Count
# Check if this is a file or directory based on wether the "asset" file exists
if([System.IO.File]::Exists($ItemDirectory.FullName+"\asset")){
$this.Type = "File"
}
else {
$this.Type = "Directory"
}
}
}
# Bit of sanity checking to make sure that tar.exe exist in path.
$TarExe = where.exe tar.exe
if([string]::IsNullOrWhiteSpace($TarExe)){
Throw "No tar.exe found in path, halting script..."
}
# Get the file info of the package
# Also since PowerShell is weird with pasting " and ' in a Mandatory parameter, we make sure to strip those out of the input
try {
$UnityPkg = Get-Item ($UnityPackage.Replace('"','').Replace("'",''))
}
catch {
Throw "Unable to get item ""$UnityPackage"", halting...."
}
# Handling temp and output folders
if([string]::IsNullOrWhiteSpace($TempPath)){
$TempFolder = (Join-Path $UnityPkg.Directory.FullName ("TEMP_"+$UnityPkg.BaseName))
}
else {
$TempFolder = (Join-Path $TempPath ("TEMP_"+$UnityPkg.BaseName))
}
if([string]::IsNullOrWhiteSpace($OutPath)){
$OutPath = $UnityPkg.Directory.FullName
}
if($DeployToProject){
$OutFolder = $OutPath
}
else {
$OutFolder = (Join-Path $OutPath $UnityPkg.BaseName)
}
if(-not (Test-Path -Path $TempFolder)){
$null = New-Item $TempFolder -ItemType Directory -Force
}
if(-not (Test-Path -Path $OutFolder )){
$null = New-Item $OutFolder -ItemType Directory -Force
}
# Untar the files into the temp folder
$TarUnpackString = " -xzf ""{0}"" -C ""{1}"""
Start-Process tar.exe -ArgumentList "$($TarUnpackString -f $UnityPkg.FullName, $TempFolder)" -NoNewWindow -Wait
# Get all directories and process them as UnityPackageItems
$AllUnityItems = Get-ChildItem $TempFolder -Directory
$ProcessedList = [System.Collections.Generic.List[UnityPackageItem]]::new($AllUnityItems.Count)
foreach($UItem in $AllUnityItems){
$ProcessedList.Add( [UnityPackageItem]::new($UItem) )
}
# Pick out all the folders and sort by level
$LevelSortedList = $ProcessedList.FindAll({($args[0]).Type -eq "Directory"}) | Sort-Object AssetLevel
# To handle certain scenarios we just create the Assets folder directly
$null = New-Item (Join-Path $OutFolder "Assets") -ItemType Directory -Force
# Create folders
$LevelSortedList | ForEach-Object -Process {
$null = New-Item (Join-Path $OutFolder $psitem.FullPath) -ItemType Directory -Force
}
# Pick out all the files
$AllFiles = $ProcessedList.FindAll({($args[0]).Type -eq "File"})
# Move the "asset" files into the right folder and rename them with the name as described in the "pathname" files
$AllFiles | ForEach-Object -Process {
Move-Item -Path (Join-Path $psitem.CurrentDir.FullName "asset") -Destination (Join-Path $OutFolder $psitem.FullPath)
}
# If we want the meta files we move them into the right place here
if ($IncludeMetaFile) {
$ProcessedList | ForEach-Object -Process {
Move-Item -Path (Join-Path $psitem.CurrentDir.FullName "asset.meta") -Destination "$(Join-Path $OutFolder $psitem.FullPath).meta"
}
}
# Remove the temp folder afterwards
Remove-Item $TempFolder -Recurse
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment