ProjectArcade
535 строк · 18.8 Кб
1using System;2using System.Collections.Generic;3using System.Linq;4using System.Text;5using System.IO;6using System.Diagnostics;7using System.Text.RegularExpressions;8using System.ComponentModel;9
10namespace EmulatorLauncher.Common.Compression11{
12public class Zip13{14public static bool IsCompressedFile(string fileName)15{16if (string.IsNullOrEmpty(fileName) || !File.Exists(fileName) || Directory.Exists(fileName))17return false;18
19var ext = Path.GetExtension(fileName).ToLowerInvariant();20return ext == ".zip" || ext == ".7z" || ext == ".rar" || ext.Contains("squashfs");21}22
23public static bool IsSevenZipAvailable24{25get { return File.Exists(GetSevenZipPath()); }26}27
28public static string GetSevenZipPath()29{30string fn = Path.Combine(Path.GetDirectoryName(typeof(Zip).Assembly.Location), "7z.exe");31if (File.Exists(fn))32return fn;33
34return Path.Combine(Path.GetDirectoryName(typeof(Zip).Assembly.Location), "7za.exe");35}36
37public static string GetRdSquashFSPath()38{39return Path.Combine(Path.GetDirectoryName(typeof(Zip).Assembly.Location), "rdsquashfs.exe");40}41
42public static bool IsFreeDiskSpaceAvailableForExtraction(string filename, string pathForExtraction)43{44try45{46var totalRequiredSize = Zip.ListEntries(filename).Sum(f => f.Length);47if (totalRequiredSize == 0)48totalRequiredSize = new FileInfo(filename).Length;49
50long freeSpaceOnDrive = new DriveInfo(Path.GetPathRoot(pathForExtraction)).AvailableFreeSpace;51if (freeSpaceOnDrive < totalRequiredSize)52return false;53}54catch { }55
56return true;57}58
59private static System.Reflection.MethodInfo _zipOpenRead;60
61public static ZipEntry[] ListEntries(string path)62{63ZipEntry[] ret;64if (!_entriesCache.TryGetValue(path, out ret))65{66ret = ListEntriesInternal(path);67_entriesCache[path] = ret;68}69
70return ret;71}72
73// Dotnet 4.0 compatible Zip entries reader ( ZipFile exists since 4.5 )74private static ZipEntry[] ListEntriesInternal(string path)75{76if (!IsCompressedFile(path))77return new ZipEntry[] { };78
79var ext = Path.GetExtension(path).ToLowerInvariant();80
81if (ext.Contains("squashfs") && File.Exists(GetRdSquashFSPath()))82return GetSquashFsEntries(path);83
84if (ext != ".zip")85return GetSevenZipEntries(path);86
87IDisposable zipArchive = null;88
89try90{91if (_zipOpenRead == null)92{93var afs = System.Reflection.Assembly.Load("System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");94if (afs == null)95return GetSevenZipEntries(path);96
97var ass = System.Reflection.Assembly.Load("System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");98if (ass == null)99return GetSevenZipEntries(path);100
101var zipFile = afs.GetTypes().FirstOrDefault(t => t.Name == "ZipFile");102if (zipFile == null)103return GetSevenZipEntries(path);104
105_zipOpenRead = zipFile.GetMember("OpenRead", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public).FirstOrDefault() as System.Reflection.MethodInfo;106if (_zipOpenRead == null)107return GetSevenZipEntries(path);108}109
110zipArchive = _zipOpenRead.Invoke(null, new object[] { path }) as IDisposable;111if (zipArchive == null)112return new ZipEntry[] { };113
114List<ZipEntry> ret = new List<ZipEntry>();115
116var entries = zipArchive.GetType().GetValue<System.Collections.IEnumerable>(zipArchive, "Entries");117
118foreach (var entry in entries)119{120var zipArchiveEntry = entry.GetType();121
122string fullName = zipArchiveEntry.GetValue<string>(entry, "FullName");123if (!string.IsNullOrEmpty(fullName))124{125ZipEntry e = new ZipEntry();126e.Filename = fullName;127e.Length = zipArchiveEntry.GetValue<long>(entry, "Length");128e.LastModified = zipArchiveEntry.GetValue<DateTimeOffset>(entry, "LastWriteTime").DateTime;129
130if (fullName.EndsWith("/"))131{132e.Filename = e.Filename.Substring(0, e.Filename.Length - 1);133e.IsDirectory = true;134}135
136ret.Add(e);137}138}139
140return ret.ToArray();141}142catch143{144return GetSevenZipEntries(path);145}146finally147{148if (zipArchive != null)149zipArchive.Dispose();150}151}152
153public class SquashFsEntry : ZipEntry154{155private string _arch;156
157public SquashFsEntry(string arch)158{159_arch = arch;160base.Length = -1;161}162
163public override long Length164{165get166{167if (base.Length == -1)168{169if (IsDirectory)170base.Length = 0;171else172{173string lineOutput = ProcessExtensions.RunWithOutput(GetRdSquashFSPath(), "-s \"" + Filename + "\" \"" + _arch + "\"");174
175var fs = lineOutput.ExtractString("File size: ", "\r");176
177long len;178if (long.TryParse(fs, out len))179base.Length = len;180else181base.Length = 0;182}183}184
185return base.Length;186}187set188{189base.Length = value;190}191}192}193
194private static Dictionary<string, ZipEntry[]> _entriesCache = new Dictionary<string, ZipEntry[]>();195
196private static ZipEntry[] GetSquashFsEntries(string archive)197{198ZipEntry[] ret;199if (!_entriesCache.TryGetValue(archive, out ret))200{201ret = GetSquashFsEntriesInternal(archive);202_entriesCache[archive] = ret;203}204
205return ret;206}207
208private static ZipEntry[] GetSquashFsEntriesInternal(string archive)209{210var sevenZip = GetRdSquashFSPath();211if (!File.Exists(sevenZip))212return new ZipEntry[] { };213
214string output = ProcessExtensions.RunWithOutput(GetRdSquashFSPath(), "-d \"" + archive + "\"");215if (output == null)216return new ZipEntry[] { };217
218List<ZipEntry> ret = new List<ZipEntry>();219
220foreach (string str in output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))221{222var args = str.SplitCommandLine();223if (args.Length < 5)224continue;225
226if (args[0] != "file" && args[0] != "dir")227continue;228
229ZipEntry e = new SquashFsEntry(archive);230e.Filename = args[1];231e.IsDirectory = args[0] == "dir";232if (e.IsDirectory)233e.Length = 0;234
235if (args.Length >= 6)236{237long lastModifiedSpan;238if (long.TryParse(args[5], out lastModifiedSpan))239{240var dt = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);241dt = dt.AddSeconds(lastModifiedSpan);242e.LastModified = dt;243}244
245e.Length = 0;246}247
248if (args.Length >= 7)249{250long len;251if (long.TryParse(args[6], out len))252e.Length = len;253}254
255ret.Add(e);256}257
258return ret.ToArray();259}260
261private static Regex _listArchiveRegex = new Regex(@"^(\d{2,4}-\d{2,4}-\d{2,4})\s+(\d{2}:\d{2}:\d{2})\s+(.{5})\s+(\d+)\s+(\d+)?\s+(.+)");262
263private static ZipEntry[] GetSevenZipEntries(string archive)264{265var sevenZip = GetSevenZipPath();266if (!File.Exists(sevenZip))267return new ZipEntry[] { };268
269string output = ProcessExtensions.RunWithOutput(GetSevenZipPath(), "l \"" + archive + "\"");270if (output == null)271return new ZipEntry[] { };272
273int num = 0;274
275List<ZipEntry> ret = new List<ZipEntry>();276
277foreach (string str in output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))278{279if (str.StartsWith("---"))280num++;281else if (_listArchiveRegex.IsMatch(str) && num == 1)282{283var matches = _listArchiveRegex.Matches(str);284
285List<string> groups = matches[0]286.Groups.Cast<Group>()287.Select(x => x.Value)288.ToList();289
290if (groups.Count == 7)291{292ZipEntry e = new ZipEntry();293e.Filename = groups[6];294e.IsDirectory = groups[3].Contains("D");295
296DateTime date;297if (DateTime.TryParse(groups[1] + " " + groups[2], out date))298e.LastModified = date;299
300long len;301if (long.TryParse(groups[4], out len))302e.Length = len;303
304ret.Add(e);305}306}307}308
309return ret.ToArray();310}311
312static void ExtractSquashFS(string archive, string destination, string fileNameToExtract = null, ProgressChangedEventHandler progress = null)313{314var rdsquashfs = GetRdSquashFSPath();315if (!File.Exists(rdsquashfs))316return;317
318HashSet<string> entries = null;319
320if (progress != null && fileNameToExtract == null)321{322entries = new HashSet<string>(GetSquashFsEntries(archive).Where(e => !e.IsDirectory).Select(e => e.Filename));323if (entries == null || entries.Count == 0)324return;325}326
327if (fileNameToExtract == null)328{329try { Directory.Delete(destination, true); }330catch { }331
332Directory.CreateDirectory(destination);333}334else335{336destination = Path.GetDirectoryName(Path.Combine(destination, fileNameToExtract));337if (!Directory.Exists(destination))338Directory.CreateDirectory(destination);339}340
341string args = "--no-dev --no-sock --no-fifo --no-slink -u /. \"" + archive + "\"";342if (!string.IsNullOrEmpty(fileNameToExtract))343args = "--no-dev --no-sock --no-fifo --no-slink -u \"" + fileNameToExtract.Replace("\\", "/") + "\" \"" + archive + "\"";344
345if (progress == null)346args = "-q " + args;347
348var px = new ProcessStartInfo()349{350FileName = rdsquashfs,351WorkingDirectory = destination,352Arguments = args,353UseShellExecute = false,354RedirectStandardOutput = (progress != null),355CreateNoWindow = true356};357
358var proc = Process.Start(px);359if (proc != null && progress != null && entries != null)360{361var unpacking = new HashSet<string>(entries);362
363int totalEntries = entries.Count; // *2;364int lastpc = 0;365
366try367{368while (!proc.StandardOutput.EndOfStream)369{370string line = proc.StandardOutput.ReadLine();371Debug.WriteLine(line);372
373if (line.StartsWith("creating "))374{375line = line.Substring("creating ".Length).Trim();376entries.Remove(line);377}378
379if (line.StartsWith("unpacking "))380{381line = line.Substring("unpacking ".Length).Trim();382unpacking.Remove(line);383}384
385int pc = ((totalEntries - unpacking.Count) * 100) / totalEntries;386if (pc != lastpc)387{388lastpc = pc;389progress(null, new ProgressChangedEventArgs(pc, null));390}391}392}393catch { }394}395
396proc.WaitForExit();397
398int code = proc.ExitCode;399if (code == 2)400throw new ApplicationException("Cannot open archive");401}402
403public static void Extract(string archive, string destination, string fileNameToExtract = null, ProgressChangedEventHandler progress = null, bool keepFolder = false)404{405var ext = Path.GetExtension(archive).ToLowerInvariant();406if (ext.Contains("squashfs") && File.Exists(GetRdSquashFSPath()))407{408ExtractSquashFS(archive, destination, fileNameToExtract, progress);409return;410}411
412var sevenZip = GetSevenZipPath();413if (!File.Exists(sevenZip))414return;415
416string args = "x -bsp1 \"" + archive + "\" -y -o\"" + destination + "\"";417if (!string.IsNullOrEmpty(fileNameToExtract))418{419// Multiple files :420// H:\[Emulz]\[batocera-ports]\7z x -bsp1 "h:\\applewin.7z" "History.txt" "MASTER.DSK" -y -o"h:\\tmp\\"421
422if (keepFolder)423args = "x -bsp1 \"" + archive + "\" \"" + fileNameToExtract + "\" -y -o\"" + destination + "\"";424else425args = "e -bsp1 \"" + archive + "\" \"" + fileNameToExtract + "\" -y -o\"" + destination + "\"";426}427
428var px = new ProcessStartInfo()429{430FileName = GetSevenZipPath(),431WorkingDirectory = Path.GetDirectoryName(GetSevenZipPath()),432Arguments = args,433UseShellExecute = false,434RedirectStandardOutput = (progress != null),435CreateNoWindow = true436};437
438var proc = Process.Start(px);439
440if (proc != null && progress != null)441{442try443{444StringBuilder sbOutput = new StringBuilder();445
446while (!proc.StandardOutput.EndOfStream)447{448string line = proc.StandardOutput.ReadLine();449if (line.Length >= 4 && line[3] == '%')450{451int pc;452if (int.TryParse(line.Substring(0, 3), out pc))453progress(null, new ProgressChangedEventArgs(pc, null));454}455
456sbOutput.AppendLine(line);457}458}459catch { }460}461
462proc.WaitForExit();463
464int code = proc.ExitCode;465if (code == 2)466throw new ApplicationException("Cannot open archive");467}468
469
470public static void CleanupUncompressedWSquashFS(string zipFile, string uncompressedPath)471{472if (Path.GetExtension(zipFile).ToLowerInvariant() != ".wsquashfs")473return;474
475string[] pathsToDelete = new string[]476{477"dosdevices",478"system.reg",479"userdef.reg",480"user.reg",481".update-timestamp",482"drive_c\\windows",483"drive_c\\Program Files\\Common Files\\System",484"drive_c\\Program Files\\Common Files\\Microsoft Shared",485"drive_c\\Program Files\\Internet Explorer",486"drive_c\\Program Files\\Windows Media Player",487"drive_c\\Program Files\\Windows NT",488"drive_c\\Program Files (x86)\\Common Files\\System",489"drive_c\\Program Files (x86)\\Common Files\\Microsoft Shared",490"drive_c\\Program Files (x86)\\Internet Explorer",491"drive_c\\Program Files (x86)\\Windows Media Player",492"drive_c\\Program Files (x86)\\Windows NT",493"drive_c\\users\\Public",494"drive_c\\ProgramData\\Microsoft"495};496
497foreach (var path in pathsToDelete)498{499string folder = Path.Combine(uncompressedPath, path);500if (Directory.Exists(folder))501{502try { Directory.Delete(folder, true); }503catch { }504}505else if (File.Exists(folder))506{507try { File.Delete(folder); }508catch { }509}510
511try512{513var parent = Path.GetDirectoryName(folder);514if (Directory.Exists(parent))515Directory.Delete(parent);516}517catch { }518}519}520}521
522public class ZipEntry523{524public string Filename { get; set; }525public bool IsDirectory { get; set; }526
527virtual public long Length { get; set; }528virtual public DateTime LastModified { get; set; }529
530public override string ToString()531{532return Filename;533}534}535}
536