Skip to content

Commit d68100a

Browse files
committed
Updated packer and added unpacking functionality
1 parent 4e74a0a commit d68100a

File tree

2 files changed

+163
-23
lines changed

2 files changed

+163
-23
lines changed

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# UnityPackager
2+
UnityPackager is a simple utility program written in C# to pack and unpack Unity's own UnityPackage file format (usually associated with the .unitypackage extension). It's very useful for CI servers.
3+
4+
The UnityPackage format is not open and everything this utility does is based on reverse engineering the fairly simplistic file format. Thus it might not fit all specifications, as they are not public.
5+
6+
_Note that this is not officially supported or endorsed by Unity. Use at your own risk._
7+
8+
## Runtime
9+
Runs on Linux with the Mono runtime and Windows
10+
11+
## Usage
12+
``./UnityPackager pack Output.unitypackage MyInputFile.cs Assets/Editor/TargetExportPath.cs``
13+
``./UnityPackager unpack Input.unitypackage ProjectFolder``

UnityPackager/Program.cs

+150-23
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,88 @@ internal class Program
1313
{
1414
public static void Main(string[] args)
1515
{
16-
if (args.Length < 4) return;
16+
if (args.Length == 0)
17+
{
18+
PrintUsage();
19+
Environment.Exit(1);
20+
return;
21+
}
22+
else if (args[0].ToLower() == "pack")
23+
{
24+
if (args.Length <= 3)
25+
{
26+
PrintUsage();
27+
Environment.Exit(1);
28+
return;
29+
}
30+
else if ((args.Length - 2) % 2 != 0)
31+
{
32+
PrintUsage();
33+
Environment.Exit(1);
34+
return;
35+
}
36+
37+
string outputFile = args[1];
1738

18-
string inputDir = args[0];
19-
string outputFile = args[1];
39+
if (!Path.IsPathRooted(outputFile))
40+
outputFile = Path.GetFullPath(outputFile);
2041

21-
List<FileEntry> entries = new List<FileEntry>();
22-
for (int i = 2; i < args.Length; i += 2)
42+
List<FileEntry> entries = new List<FileEntry>();
43+
44+
for (int i = 2; i < args.Length; i += 2)
45+
{
46+
FileEntry entry = new FileEntry();
47+
48+
if (!Path.IsPathRooted(args[i]))
49+
entry.FullPath = Path.GetFullPath(args[i]);
50+
else
51+
entry.FullPath = args[i];
52+
53+
entry.OutputExportPath = args[i + 1];
54+
55+
entries.Add(entry);
56+
}
57+
58+
Pack(entries.ToArray(), outputFile);
59+
}
60+
else if (args[0].ToLower() == "unpack")
2361
{
24-
entries.Add(new FileEntry()
62+
if (args.Length != 3)
2563
{
26-
FullPath = args[i],
27-
OutputExportPath = args[i + 1]
28-
});
29-
}
64+
PrintUsage();
65+
Environment.Exit(1);
66+
return;
67+
}
68+
69+
string inputFile = args[1];
70+
71+
if (!Path.IsPathRooted(inputFile))
72+
inputFile = Path.GetFullPath(inputFile);
73+
74+
string outputFolder = args[2];
3075

31-
Pack(entries.ToArray(), inputDir, outputFile);
76+
if (!Path.IsPathRooted(outputFolder))
77+
outputFolder = Path.GetFullPath(outputFolder);
78+
79+
Unpack(inputFile, outputFolder);
80+
}
81+
else
82+
{
83+
PrintUsage();
84+
}
3285
}
3386

34-
private static void Pack(FileEntry[] files, string inputDirectory, string outputFile)
87+
private static void PrintUsage()
88+
{
89+
Console.WriteLine("Usage:");
90+
Console.WriteLine("\t" + "UnityPackager pack <output> [(<input-file> <target-path>)]...");
91+
Console.WriteLine("\t" + "UnityPackager unpack <input-file> <output-folder>");
92+
Console.WriteLine("Example:");
93+
Console.WriteLine("\t" + "UnityPackager pack MyPackage.unitypackage MyFile.cs Assets/MyFile.cs");
94+
Console.WriteLine("\t" + "UnityPackager unpack MyPackage.unitypackage MyProjectFolder");
95+
}
96+
97+
private static void Pack(FileEntry[] files, string outputFile)
3598
{
3699
string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
37100
Directory.CreateDirectory(tempPath);
@@ -41,22 +104,34 @@ private static void Pack(FileEntry[] files, string inputDirectory, string output
41104
string guid = CreateGuid(files[i].OutputExportPath);
42105

43106
Directory.CreateDirectory(Path.Combine(tempPath, guid));
44-
File.Copy(files[i].FullPath, Path.Combine(tempPath, guid, "asset"));
107+
File.Copy(files[i].FullPath, Path.Combine(tempPath, guid, "asset"));
108+
45109
File.WriteAllText(Path.Combine(tempPath, guid, "pathname"), files[i].OutputExportPath);
46110

47-
using (StreamWriter writer = new StreamWriter(Path.Combine(tempPath, guid, "asset.meta")))
111+
112+
string metaPath = Path.Combine(Path.GetDirectoryName(files[i].FullPath), Path.GetFileNameWithoutExtension(files[i].FullPath) + ".meta");
113+
114+
if (File.Exists(metaPath))
48115
{
49-
new YamlStream(new YamlDocument(new YamlMappingNode()
50-
{
51-
{"guid", guid},
52-
{"fileFormatVersion", "2"}
53-
})).Save(writer);
116+
File.Copy(metaPath, Path.Combine(tempPath, guid, "asset.meta"));
54117
}
55-
56-
FileInfo metaFile = new FileInfo(Path.Combine(Path.Combine(tempPath, guid, "asset.meta")));
57-
using (FileStream metaFileStream = metaFile.Open(FileMode.Open))
118+
else
58119
{
59-
metaFileStream.SetLength(metaFile.Length - 3 - Environment.NewLine.Length);
120+
using (StreamWriter writer = new StreamWriter(Path.Combine(tempPath, guid, "asset.meta")))
121+
{
122+
new YamlStream(new YamlDocument(new YamlMappingNode()
123+
{
124+
{"guid", guid},
125+
{"fileFormatVersion", "2"}
126+
})).Save(writer);
127+
}
128+
129+
FileInfo metaFile = new FileInfo(Path.Combine(Path.Combine(tempPath, guid, "asset.meta")));
130+
131+
using (FileStream metaFileStream = metaFile.Open(FileMode.Open))
132+
{
133+
metaFileStream.SetLength(metaFile.Length - 3 - Environment.NewLine.Length);
134+
}
60135
}
61136
}
62137

@@ -70,6 +145,7 @@ private static void Pack(FileEntry[] files, string inputDirectory, string output
70145
using (TarArchive archive = TarArchive.CreateOutputTarArchive(zipStream))
71146
{
72147
archive.RootPath = tempPath.Replace('\\', '/');
148+
73149
if (archive.RootPath.EndsWith("/"))
74150
archive.RootPath = archive.RootPath.Remove(archive.RootPath.Length - 1);
75151

@@ -81,10 +157,59 @@ private static void Pack(FileEntry[] files, string inputDirectory, string output
81157
// Clean up
82158
Directory.Delete(tempPath, true);
83159
}
160+
161+
private static void Unpack(string inputFile, string outputFolder)
162+
{
163+
string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
164+
Directory.CreateDirectory(tempPath);
165+
166+
using (FileStream stream = new FileStream(inputFile, FileMode.Open))
167+
{
168+
using (GZipInputStream zipStream = new GZipInputStream(stream))
169+
{
170+
using (TarArchive archive = TarArchive.CreateInputTarArchive(zipStream))
171+
{
172+
archive.ExtractContents(tempPath);
173+
}
174+
}
175+
}
176+
177+
string[] dirEntries = Directory.GetDirectories(tempPath);
178+
179+
for (int i = 0; i < dirEntries.Length; i++)
180+
{
181+
if (!File.Exists(Path.Combine(dirEntries[i], "pathname")) || !File.Exists(Path.Combine(dirEntries[i], "asset")) || !File.Exists(Path.Combine(dirEntries[i], "asset.meta")))
182+
{
183+
// Invalid format
184+
continue;
185+
}
186+
187+
string targetPath = Path.Combine(outputFolder, File.ReadAllText(Path.Combine(dirEntries[i], "pathname")));
188+
string targetFileNameWithoutExtension = Path.GetFileNameWithoutExtension(targetPath);
189+
string targetFolder = Path.GetDirectoryName(targetPath);
190+
string targetMetaPath = Path.Combine(outputFolder, targetFolder, targetFileNameWithoutExtension + ".meta");
191+
192+
if (!Directory.Exists(targetFolder))
193+
Directory.CreateDirectory(targetFolder);
194+
195+
if (File.Exists(targetPath))
196+
File.Delete(targetPath);
197+
198+
if (File.Exists(targetMetaPath))
199+
File.Delete(targetMetaPath);
200+
201+
File.WriteAllText(targetPath, File.ReadAllText(Path.Combine(dirEntries[i], "asset")));
202+
File.WriteAllText(targetMetaPath, File.ReadAllText(Path.Combine(dirEntries[i], "asset.meta")));
203+
}
204+
205+
// Clean up
206+
Directory.Delete(tempPath, true);
207+
}
84208

85209
private static void AddFilesInDirRecursive(TarArchive archive, string directory)
86210
{
87211
string[] files = Directory.GetFiles(directory);
212+
88213
for (int i = 0; i < files.Length; i++)
89214
{
90215
TarEntry entry = TarEntry.CreateEntryFromFile(files[i]);
@@ -93,6 +218,7 @@ private static void AddFilesInDirRecursive(TarArchive archive, string directory)
93218
}
94219

95220
string[] subDirs = Directory.GetDirectories(directory);
221+
96222
for (int i = 0; i < subDirs.Length; i++)
97223
{
98224
AddFilesInDirRecursive(archive, subDirs[i]);
@@ -107,6 +233,7 @@ private static string CreateGuid(string input)
107233
byte[] hashBytes = md5.ComputeHash(inputBytes);
108234

109235
StringBuilder stringBuilder = new StringBuilder();
236+
110237
foreach (byte b in hashBytes)
111238
{
112239
stringBuilder.Append(b.ToString("X2"));

0 commit comments

Comments
 (0)