Skip to content

Commit ee6df72

Browse files
committed
fix extractTar to handle missing paths in unity template tgz files (but looks like probably no need to extract it manually, can just call -cloneFromTemplate ...tgz and it does all automatically >_<
1 parent f1dbeaf commit ee6df72

File tree

2 files changed

+207
-85
lines changed

2 files changed

+207
-85
lines changed

UnityLauncherPro/GetUnityInstallations.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,23 @@ public static List<UnityInstallation> Scan()
4848
var haveUninstaller = File.Exists(uninstallExe);
4949

5050
var exePath = Path.Combine(editorFolder, "Unity.exe");
51-
if (File.Exists(exePath) == false) continue;
51+
52+
// bool supportTuanjie = true;
53+
if (File.Exists(exePath) == false)
54+
{
55+
//if (supportTuanjie == false)
56+
{
57+
continue;
58+
}
59+
//else
60+
//{
61+
// exePath = Path.Combine(editorFolder, "Tuanjie.exe");
62+
// if (File.Exists(exePath) == false)
63+
// {
64+
// continue;
65+
// }
66+
//}
67+
}
5268

5369
// get full version number from uninstaller (or try exe, if no uninstaller)
5470
var version = Tools.GetFileVersionData(haveUninstaller ? uninstallExe : exePath);
Lines changed: 190 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// source https://gist.github.com/Su-s/438be493ae692318c73e30367cbc5c2a
2-
// updated source https://gist.github.com/Matheos96/da8990030dfe3e27b0a48722042d9c0b
3-
41
using System;
52
using System.IO;
63
using System.IO.Compression;
@@ -11,10 +8,8 @@ namespace TarLib
118
public class Tar
129
{
1310
/// <summary>
14-
/// Extracts a <i>.tar.gz</i> archive to the specified directory.
11+
/// Extracts a .tar.gz archive to the specified directory.
1512
/// </summary>
16-
/// <param name="filename">The <i>.tar.gz</i> to decompress and extract.</param>
17-
/// <param name="outputDir">Output directory to write the files.</param>
1813
public static void ExtractTarGz(string filename, string outputDir)
1914
{
2015
using (var stream = File.OpenRead(filename))
@@ -24,39 +19,29 @@ public static void ExtractTarGz(string filename, string outputDir)
2419
}
2520

2621
/// <summary>
27-
/// Extracts a <i>.tar.gz</i> archive stream to the specified directory.
22+
/// Extracts a .tar.gz archive stream to the specified directory.
2823
/// </summary>
29-
/// <param name="stream">The <i>.tar.gz</i> to decompress and extract.</param>
30-
/// <param name="outputDir">Output directory to write the files.</param>
3124
public static void ExtractTarGz(Stream stream, string outputDir)
3225
{
33-
int read;
34-
const int chunk = 4096;
26+
const int chunk = 4096*4;
3527
var buffer = new byte[chunk];
3628

37-
// A GZipStream is not seekable, so copy it first to a MemoryStream
3829
using (var gzipStream = new GZipStream(stream, CompressionMode.Decompress))
30+
using (var memStream = new MemoryStream())
3931
{
40-
using (var memStream = new MemoryStream())
32+
int read;
33+
while ((read = gzipStream.Read(buffer, 0, buffer.Length)) > 0)
4134
{
42-
//For .NET 6+
43-
while ((read = gzipStream.Read(buffer, 0, buffer.Length)) > 0)
44-
{
45-
memStream.Write(buffer, 0, read);
46-
}
47-
memStream.Seek(0, SeekOrigin.Begin);
48-
49-
//ExtractTar(gzip, outputDir);
50-
ExtractTar(memStream, outputDir);
35+
memStream.Write(buffer, 0, read);
5136
}
37+
memStream.Seek(0, SeekOrigin.Begin);
38+
ExtractTar(memStream, outputDir);
5239
}
5340
}
5441

5542
/// <summary>
56-
/// Extractes a <c>tar</c> archive to the specified directory.
43+
/// Extracts a tar archive file.
5744
/// </summary>
58-
/// <param name="filename">The <i>.tar</i> to extract.</param>
59-
/// <param name="outputDir">Output directory to write the files.</param>
6045
public static void ExtractTar(string filename, string outputDir)
6146
{
6247
using (var stream = File.OpenRead(filename))
@@ -66,85 +51,206 @@ public static void ExtractTar(string filename, string outputDir)
6651
}
6752

6853
/// <summary>
69-
/// Extractes a <c>tar</c> archive to the specified directory.
54+
/// Extracts a tar archive stream.
55+
/// Fixes path loss caused by ignoring the POSIX 'prefix' field and wrong header offsets.
7056
/// </summary>
71-
/// <param name="stream">The <i>.tar</i> to extract.</param>
72-
/// <param name="outputDir">Output directory to write the files.</param>
7357
public static void ExtractTar(Stream stream, string outputDir)
7458
{
75-
var buffer = new byte[100];
76-
var longFileName = string.Empty;
59+
// Tar header constants
60+
const int HeaderSize = 512;
61+
byte[] header = new byte[HeaderSize];
62+
63+
string pendingLongName = null; // For GNU long name ('L') entries
64+
7765
while (true)
7866
{
79-
stream.Read(buffer, 0, 100);
80-
string name = string.IsNullOrEmpty(longFileName) ? Encoding.ASCII.GetString(buffer).Trim('\0') : longFileName; //Use longFileName if we have one read
81-
82-
if (String.IsNullOrWhiteSpace(name)) break;
83-
stream.Seek(24, SeekOrigin.Current);
84-
stream.Read(buffer, 0, 12);
85-
var size = Convert.ToInt64(Encoding.UTF8.GetString(buffer, 0, 12).Trim('\0').Trim(), 8);
86-
stream.Seek(20, SeekOrigin.Current); //Move head to typeTag byte
87-
var typeTag = stream.ReadByte();
88-
stream.Seek(355L, SeekOrigin.Current); //Move head to beginning of data (byte 512)
89-
90-
if (typeTag == 'L')
67+
int bytesRead = ReadExact(stream, header, 0, HeaderSize);
68+
if (bytesRead == 0) break; // End of stream
69+
if (bytesRead < HeaderSize) throw new EndOfStreamException("Unexpected end of tar stream.");
70+
71+
// Detect two consecutive zero blocks (end of archive)
72+
bool allZero = IsAllZero(header);
73+
if (allZero)
74+
{
75+
// Peek next block; if also zero -> end
76+
bytesRead = ReadExact(stream, header, 0, HeaderSize);
77+
if (bytesRead == 0 || IsAllZero(header)) break;
78+
if (bytesRead < HeaderSize) throw new EndOfStreamException("Unexpected end of tar stream.");
79+
}
80+
81+
// Parse fields (POSIX ustar)
82+
string name = GetString(header, 0, 100);
83+
string mode = GetString(header, 100, 8);
84+
string uid = GetString(header, 108, 8);
85+
string gid = GetString(header, 116, 8);
86+
string sizeOctal = GetString(header, 124, 12);
87+
string mtime = GetString(header, 136, 12);
88+
string checksum = GetString(header, 148, 8);
89+
char typeFlag = (char)header[156];
90+
string linkName = GetString(header, 157, 100);
91+
string magic = GetString(header, 257, 6); // "ustar\0" or "ustar "
92+
string version = GetString(header, 263, 2);
93+
string uname = GetString(header, 265, 32);
94+
string gname = GetString(header, 297, 32);
95+
string prefix = GetString(header, 345, 155);
96+
97+
// Compose full name using prefix (if present and not using GNU long name override)
98+
if (!string.IsNullOrEmpty(prefix))
99+
{
100+
name = prefix + "/" + name;
101+
}
102+
103+
// If we previously read a GNU long name block, override current name
104+
if (!string.IsNullOrEmpty(pendingLongName))
91105
{
92-
//If Type Tag is 'L' we have a filename that is longer than the 100 bytes reserved for it in the header.
93-
//We read it here and save it temporarily as it will be the file name of the next block where the actual data is
94-
var buf = new byte[size];
95-
stream.Read(buf, 0, buf.Length);
96-
longFileName = Encoding.ASCII.GetString(buf).Trim('\0');
106+
name = pendingLongName;
107+
pendingLongName = null;
97108
}
98-
else
109+
110+
long size = ParseOctal(sizeOctal);
111+
112+
// Handle GNU long name extension block: the data of this entry is the filename of next entry.
113+
if (typeFlag == 'L')
99114
{
100-
longFileName = string.Empty; //Reset longFileName if current entry is not indicating one
115+
byte[] longNameData = new byte[size];
116+
ReadExact(stream, longNameData, 0, (int)size);
117+
pendingLongName = Encoding.ASCII.GetString(longNameData).Trim('\0', '\r', '\n');
118+
SkipPadding(stream, size);
119+
continue; // Move to next header
120+
}
121+
122+
// Skip PAX extended header (type 'x') - metadata only
123+
if (typeFlag == 'x')
124+
{
125+
SkipData(stream, size);
126+
SkipPadding(stream, size);
127+
continue;
128+
}
129+
130+
// Normalize name
131+
if (string.IsNullOrWhiteSpace(name)) continue;
101132

102-
var output = Path.Combine(outputDir, name);
133+
// Directory?
134+
bool isDirectory = typeFlag == '5' || name.EndsWith("/");
103135

104-
// only include these folders
105-
var include = (output.IndexOf("package/ProjectData~/Assets/") > -1);
106-
include |= (output.IndexOf("package/ProjectData~/ProjectSettings/") > -1);
107-
include |= (output.IndexOf("package/ProjectData~/Packages/") > -1);
136+
// Inclusion filter (original logic)
137+
string originalName = name;
138+
bool include =
139+
originalName.IndexOf("package/ProjectData~/Assets/", StringComparison.Ordinal) > -1 ||
140+
originalName.IndexOf("package/ProjectData~/ProjectSettings/", StringComparison.Ordinal) > -1 ||
141+
originalName.IndexOf("package/ProjectData~/Library/", StringComparison.Ordinal) > -1 ||
142+
originalName.IndexOf("package/ProjectData~/Packages/", StringComparison.Ordinal) > -1;
108143

109-
// rename output path from "package/ProjectData~/Assets/" into "Assets/"
110-
output = output.Replace("package/ProjectData~/", "");
144+
// Strip leading prefix.
145+
string cleanedName = originalName.StartsWith("package/ProjectData~/", StringComparison.Ordinal)
146+
? originalName.Substring("package/ProjectData~/".Length)
147+
: originalName;
148+
149+
string finalPath = Path.Combine(outputDir, cleanedName.Replace('/', Path.DirectorySeparatorChar));
150+
151+
if (isDirectory)
152+
{
153+
if (include && !Directory.Exists(finalPath))
154+
Directory.CreateDirectory(finalPath);
155+
// No data to read for directory; continue to next header
156+
SkipData(stream, size); // size should be 0
157+
SkipPadding(stream, size);
158+
continue;
159+
}
160+
161+
// Ensure directory exists
162+
if (include)
163+
{
164+
string dir = Path.GetDirectoryName(finalPath);
165+
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
166+
Directory.CreateDirectory(dir);
167+
}
111168

112-
if (include == true && !Directory.Exists(Path.GetDirectoryName(output))) Directory.CreateDirectory(Path.GetDirectoryName(output));
169+
// Read file data (always advance stream even if not included)
170+
byte[] fileData = new byte[size];
171+
ReadExact(stream, fileData, 0, (int)size);
113172

114-
// not folder
115-
//if (name.Equals("./", StringComparison.InvariantCulture) == false)
116-
if (name.EndsWith("/") == false) //Directories are zero size and don't need anything written
173+
if (include)
174+
{
175+
using (var fs = File.Open(finalPath, FileMode.Create, FileAccess.Write))
117176
{
118-
if (include == true)
119-
{
120-
//Console.WriteLine("output=" + output);
121-
using (var str = File.Open(output, FileMode.OpenOrCreate, FileAccess.ReadWrite))
122-
{
123-
var buf = new byte[size];
124-
stream.Read(buf, 0, buf.Length);
125-
// take only data from this folder
126-
str.Write(buf, 0, buf.Length);
127-
}
128-
}
129-
else
130-
{
131-
var buf = new byte[size];
132-
stream.Read(buf, 0, buf.Length);
133-
}
177+
fs.Write(fileData, 0, fileData.Length);
134178
}
135179
}
136180

137-
//Move head to next 512 byte block
138-
var pos = stream.Position;
139-
var offset = 512 - (pos % 512);
140-
if (offset == 512) offset = 0;
181+
// Skip padding to 512 boundary
182+
SkipPadding(stream, size);
183+
}
184+
}
141185

142-
stream.Seek(offset, SeekOrigin.Current);
186+
private static string GetString(byte[] buffer, int offset, int length)
187+
{
188+
var s = Encoding.ASCII.GetString(buffer, offset, length);
189+
int nullIndex = s.IndexOf('\0');
190+
if (nullIndex >= 0) s = s.Substring(0, nullIndex);
191+
return s.Trim();
192+
}
193+
194+
private static long ParseOctal(string s)
195+
{
196+
s = s.Trim();
197+
if (string.IsNullOrEmpty(s)) return 0;
198+
try
199+
{
200+
return Convert.ToInt64(s, 8);
201+
}
202+
catch
203+
{
204+
// Fallback: treat as decimal if malformed
205+
long val;
206+
return long.TryParse(s, out val) ? val : 0;
143207
}
144208
}
145-
} // class Tar
146-
} // namespace TarLib
147209

210+
private static bool IsAllZero(byte[] buffer)
211+
{
212+
for (int i = 0; i < buffer.Length; i++)
213+
if (buffer[i] != 0) return false;
214+
return true;
215+
}
216+
217+
private static int ReadExact(Stream stream, byte[] buffer, int offset, int count)
218+
{
219+
int total = 0;
220+
while (total < count)
221+
{
222+
int read = stream.Read(buffer, offset + total, count - total);
223+
if (read <= 0) break;
224+
total += read;
225+
}
226+
return total;
227+
}
228+
229+
private static void SkipData(Stream stream, long size)
230+
{
231+
if (size <= 0) return;
232+
const int chunk = 8192;
233+
byte[] tmp = new byte[Math.Min(chunk, (int)size)];
234+
long remaining = size;
235+
while (remaining > 0)
236+
{
237+
int toRead = (int)Math.Min(tmp.Length, remaining);
238+
int read = stream.Read(tmp, 0, toRead);
239+
if (read <= 0) throw new EndOfStreamException("Unexpected end while skipping data.");
240+
remaining -= read;
241+
}
242+
}
243+
244+
private static void SkipPadding(Stream stream, long size)
245+
{
246+
long padding = (512 - (size % 512)) % 512;
247+
if (padding > 0)
248+
{
249+
stream.Seek(padding, SeekOrigin.Current);
250+
}
251+
}
252+
}
253+
}
148254

149255
/*
150256
This software is available under 2 licenses-- choose whichever you prefer.
@@ -184,4 +290,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
184290
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
185291
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
186292
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
187-
*/
293+
*/

0 commit comments

Comments
 (0)