ProjectArcade
765 строк · 26.6 Кб
1using System;2using System.Collections.Generic;3using System.IO;4using System.Linq;5using System.Text;6using System.Threading.Tasks;7using DokanNet;8using System.Text.RegularExpressions;9using System.Diagnostics;10using System.Threading;11using EmulatorLauncher.Common;12using EmulatorLauncher.Common.Compression;13
14namespace Mount15{
16public class DokanOperations : IDokanOperations17{18public string FileName { get; private set; }19public string OverlayPath { get; private set; }20public string ExtractionDirectory { get; private set; }21
22private Dictionary<string, FileEntry> _entries;23private OverlayDeletionRepository _overlay;24
25public DokanOperations(string filepath, string extractionDirectory = null, string overlayPath = null)26{27FileName = filepath;28ExtractionDirectory = string.IsNullOrEmpty(extractionDirectory) ? Path.Combine(Path.GetTempPath(), ".mountfs", Path.GetFileName(filepath)) : extractionDirectory;29
30if (overlayPath != null && (overlayPath.StartsWith("./") || overlayPath.StartsWith(".\\")))31overlayPath = Path.GetFullPath(Path.Combine(ExtractionDirectory, overlayPath));32else if (overlayPath == ".")33overlayPath = Path.GetFullPath(Path.Combine(ExtractionDirectory, ".overlay"));34
35OverlayPath = overlayPath;36
37_entries = new Dictionary<string, FileEntry>(StringComparer.OrdinalIgnoreCase);38_entries["\\"] = new MountedFileEntry(new ZipEntry() { Filename = "\\", Length = 0, IsDirectory = true }, this);39
40var entries = Zip.ListEntries(FileName);41foreach (var z in entries)42{43var entryName = "\\" + z.Filename.Replace("/", "\\");44if (IsEntryNameValid(entryName))45_entries[entryName] = new MountedFileEntry(z, this);46}47
48LoadOverlay();49RebuildTreeChildren();50}51
52HashSet<string> hiddenEntries = new HashSet<string>53{54"\\.update-timestamp",55"\\dosdevices",56"\\drive_c\\windows",57"\\drive_c\\.windows-serial",58"\\drive_c\\program files\\common files",59"\\drive_c\\program files\\internet explorer",60"\\drive_c\\program files\\windows media player",61"\\drive_c\\program files\\windows nt",62"\\drive_c\\program files (x86)\\common files",63"\\drive_c\\program files (x86)\\internet explorer",64"\\drive_c\\program files (x86)\\windows media player",65"\\drive_c\\program files (x86)\\windows nt",66"\\drive_c\\programdata",67"\\system.reg",68"\\user.reg",69"\\userdef.reg"70};71
72private bool IsEntryNameValid(string fileName)73{74return !hiddenEntries.Contains(fileName.ToLowerInvariant());75}76
77public static bool DebugOutput { get; set; }78
79#region IDokanOperations80public NtStatus GetVolumeInformation(out string volumeLabel, out FileSystemFeatures features, out string fileSystemName, out uint maximumComponentLength, IDokanFileInfo info)81{82maximumComponentLength = 1024;83fileSystemName = Path.GetExtension(FileName).Substring(1).ToUpperInvariant();84volumeLabel = Path.GetFileNameWithoutExtension(FileName);85features = FileSystemFeatures.CasePreservedNames | FileSystemFeatures.UnicodeOnDisk;86
87if (string.IsNullOrEmpty(OverlayPath))88features |= FileSystemFeatures.ReadOnlyVolume; // | FileSystemFeatures.VolumeIsCompressed89
90return NtStatus.Success;91}92
93public NtStatus CreateFile(string fileName, DokanNet.FileAccess access, FileShare share, FileMode mode, FileOptions options, FileAttributes attributes, IDokanFileInfo info)94{95bool isWrite = access.HasFlag(DokanNet.FileAccess.WriteData) || access.HasFlag(DokanNet.FileAccess.GenericWrite);96bool isRead = access.HasFlag(DokanNet.FileAccess.ReadData) || access.HasFlag(DokanNet.FileAccess.GenericRead);97
98if (fileName == "\\")99{100info.IsDirectory = true;101return DokanResult.Success;102}103
104if (DebugOutput)105Console.WriteLine("Open: " + fileName + " : " + mode.ToString() + " ( " + access.ToString() + " ) ");106
107var item = GetFile(fileName);108
109if (mode == FileMode.CreateNew || (mode == FileMode.Create && item == null))110item = CreateToOverlay(fileName, null, info.IsDirectory);111
112if (item == null)113return DokanResult.FileNotFound;114
115info.IsDirectory = item.IsDirectory;116
117if (access == DokanNet.FileAccess.Delete)118{119if (string.IsNullOrEmpty(OverlayPath))120return NtStatus.AccessDenied;121
122return NtStatus.Success;123}124
125if (access == DokanNet.FileAccess.Synchronize || access == DokanNet.FileAccess.ReadAttributes)126return NtStatus.Success;127
128if (isWrite && string.IsNullOrEmpty(OverlayPath))129return NtStatus.AccessDenied;130
131if (!info.IsDirectory && info.Context == null)132{133if (isRead)134{135System.IO.FileAccess fileAccess = System.IO.FileAccess.Read;136if (isWrite)137fileAccess |= System.IO.FileAccess.Write;138
139var m = item as MountedFileEntry;140if (m != null && !m.Queryed)141{142m.Queryed = true;143PreloadParentFolderEntries(fileName);144}145
146info.Context = item.GetPhysicalFileStream(fileAccess);147
148if (DebugOutput)149Console.WriteLine("CreateFile (" + fileAccess.ToString() + "): " + fileName);150}151else if (isWrite)152{153if (DebugOutput)154Console.WriteLine("CreateFile (FileAccess.Write): " + fileName);155}156}157
158return NtStatus.Success;159}160
161private string Extension(string fileName)162{163return Path.GetExtension(fileName).ToLowerInvariant();164}165
166private bool IsPreloadable(string fileName)167{168if (string.IsNullOrEmpty(fileName))169return false;170
171var ext = Extension(fileName);172return ext == ".dll" || ext == ".exe";173}174
175private void PreloadParentFolderEntries(string fileName)176{177var parent = GetFile(Path.GetDirectoryName(fileName)) as MountedFileEntry;178if (parent == null || !parent.IsDirectory)179return;180
181Task.Factory.StartNew(() =>182{183int take = Environment.ProcessorCount;184
185string ext = Extension(fileName);186if (ext == ".exe" || ext == ".dll")187take = 2 * Environment.ProcessorCount;188
189var children = parent.Children.OfType<MountedFileEntry>()190.Where(p => !p.Queryed && !p.IsDirectory && !File.Exists(p.PhysicalPath)) // (Extension(p.Filename) == ext || IsPreloadable(p.Filename)) &&191.OrderByDescending(p => Extension(p.Filename) == ext)192.ThenByDescending(p => IsPreloadable(p.Filename))193.Take(take)194.ToArray();195
196foreach (var child in children)197child.Queryed = true;198
199Parallel.ForEach(children, new ParallelOptions { MaxDegreeOfParallelism = take }, child =>200{201if (DebugOutput)202Console.WriteLine("Preloading: " + child.Filename);203
204child.GetPhysicalFileStream((System.IO.FileAccess)0);205
206if (DebugOutput)207Console.WriteLine("Preloaded: " + child.Filename);208});209});210}211
212public void Cleanup(string fileName, IDokanFileInfo info)213{214if (info.IsDirectory)215return;216
217if (DebugOutput)218Console.WriteLine("Cleanup: " + fileName);219
220Stream stream = info.Context as Stream;221if (stream != null)222{223info.Context = null;224
225ThreadPool.QueueUserWorkItem((a) =>226{227stream.Close();228stream.Dispose();229});230}231
232if (info.DeleteOnClose)233{234if (info.IsDirectory)235DeleteDirectory(fileName, info);236else237DeleteFile(fileName, info);238}239}240
241public void CloseFile(string fileName, IDokanFileInfo info)242{243if (info.IsDirectory)244return;245
246if (DebugOutput)247Console.WriteLine("CloseFile: " + fileName);248
249Stream stream = info.Context as Stream;250if (stream != null)251{252info.Context = null;253
254ThreadPool.QueueUserWorkItem((a) =>255{256stream.Close();257stream.Dispose();258});259}260}261
262public NtStatus ReadFile(string fileName, byte[] buffer, out int bytesRead, long offset, IDokanFileInfo info)263{264if (DebugOutput)265Console.WriteLine("ReadFile: " + fileName + " @ " + offset + " => " + buffer.Length);266
267Stream stream = info.Context as Stream;268
269if (stream == null)270{271if (DebugOutput)272Console.WriteLine("ReadFile: NULL CONTEXT");273
274//called when file is read as memory memory mapeded file usualy notepad and stuff275var item = GetFile(fileName);276if (item == null)277{278bytesRead = 0;279return NtStatus.FileInvalid;280}281
282using (var tempStream = item.GetPhysicalFileStream())283{284tempStream.Position = offset;285bytesRead = tempStream.Read(buffer, 0, buffer.Length);286tempStream.Close();287}288
289return NtStatus.Success;290}291
292lock (stream)293{294if (stream.Position != offset)295stream.Position = offset;296
297bytesRead = stream.Read(buffer, 0, buffer.Length);298}299
300return NtStatus.Success;301}302
303public NtStatus MoveFile(string oldName, string newName, bool replace, IDokanFileInfo info)304{305var item = GetFile(oldName);306if (item == null)307return DokanResult.FileNotFound;308
309string fullPath = Path.Combine(OverlayPath, newName.Substring(1));310
311if (!replace)312{313var destItem = GetFile(newName);314if (destItem != null)315return NtStatus.CannotDelete;316}317
318var dir = Path.GetDirectoryName(fullPath);319if (!Directory.Exists(dir))320Directory.CreateDirectory(dir);321
322if (item is OverlayFileEntry)323{324if (item.IsDirectory)325Directory.Move(item.PhysicalPath, fullPath);326else327File.Move(item.PhysicalPath, fullPath);328
329item.Filename = newName.Substring(1);330((OverlayFileEntry)item).SetPhysicalPath(fullPath);331
332_entries.Remove(oldName.Replace("/", "\\"));333_entries[newName.Replace("/", "\\")] = item;334_overlay.RestoreFile(item.Filename);335}336else337{338if (item.IsDirectory)339{340FileTools.CopyDirectory(item.PhysicalPath, fullPath, true);341
342var childItems = item.Traverse(c => c.Children).Skip(1).ToList();343var keysToRemove = _entries.Where(e => childItems.Contains(e.Value)).Select(e => e.Key).ToList();344foreach (var keyToRemove in keysToRemove)345_entries.Remove(keyToRemove);346
347AddPathToOverlay(fullPath);348}349else350{351using (var fs = new FileStream(fullPath, FileMode.CreateNew, System.IO.FileAccess.Write, FileShare.ReadWrite))352using (var stream = item.GetPhysicalFileStream())353stream.CopyTo(fs);354}355
356DeleteFile(oldName, info);357
358var ret = new OverlayFileEntry(newName, fullPath);359_entries[newName.Replace("/", "\\")] = ret;360_overlay.RestoreFile(ret.Filename);361}362
363RebuildTreeChildren();364
365return NtStatus.Success;366}367
368public NtStatus WriteFile(string fileName, byte[] buffer, out int bytesWritten, long offset, IDokanFileInfo info)369{370bytesWritten = 0;371
372Stream stream = info.Context as Stream;373
374var item = GetFile(fileName);375if (item == null || !(item is OverlayFileEntry))376{377item = CreateToOverlay(fileName, item);378
379if (stream != null)380{381stream.Dispose();382stream = item.GetPhysicalFileStream(System.IO.FileAccess.ReadWrite);383info.Context = stream;384}385}386
387if (stream == null && item is OverlayFileEntry)388{389if (DebugOutput)390Console.WriteLine("WriteFile: NULL CONTEXT");391
392if (offset == 0)393{394_overlay.RestoreFile(item.Filename);395File.WriteAllText(item.PhysicalPath, "");396}397
398using (var tempStream = item.GetPhysicalFileStream(System.IO.FileAccess.Write))399{400tempStream.Position = offset;401tempStream.Write(buffer, 0, buffer.Length);402bytesWritten = (int)(tempStream.Position - offset);403tempStream.Seek(0, SeekOrigin.End);404((OverlayFileEntry)item).SetLength(tempStream.Length);405tempStream.Close();406}407
408return NtStatus.Success;409}410
411lock (stream)412{413stream.Position = offset;414stream.Write(buffer, 0, buffer.Length);415bytesWritten = (int)(stream.Position - offset);416
417return NtStatus.Success;418}419}420
421public NtStatus FlushFileBuffers(string fileName, IDokanFileInfo info)422{423Stream stream = info.Context as Stream;424if (stream != null)425stream.Flush();426
427return NtStatus.Success;428}429
430public NtStatus GetFileInformation(string fileName, out FileInformation fileInfo, IDokanFileInfo info)431{432var item = GetFile(fileName);433if (item == null)434{435fileInfo = default(FileInformation);436return DokanResult.FileNotFound;437}438
439fileInfo = GetFileInformation(item);440
441return NtStatus.Success;442}443
444public NtStatus FindFiles(string fileName, out IList<FileInformation> files, IDokanFileInfo info)445{446files = FindFilesHelper(fileName, "*");447return DokanResult.Success;448}449
450public NtStatus FindFilesWithPattern(string fileName, string searchPattern, out IList<FileInformation> files, IDokanFileInfo info)451{452files = FindFilesHelper(fileName, searchPattern);453return DokanResult.Success;454}455
456public NtStatus SetFileAttributes(string fileName, FileAttributes attributes, IDokanFileInfo info)457{458return NtStatus.DiskFull;459}460
461public NtStatus SetFileTime(string fileName, DateTime? creationTime, DateTime? lastAccessTime, DateTime? lastWriteTime, IDokanFileInfo info)462{463return NtStatus.DiskFull;464}465
466public NtStatus DeleteFile(string fileName, IDokanFileInfo info)467{468var item = GetFile(fileName);469if (item == null)470return DokanResult.Success;471
472_entries.Remove(fileName);473
474foreach (var entry in _entries)475entry.Value.Children.Remove(item);476
477if (item is OverlayFileEntry)478{479if (item.IsDirectory)480{481try { Directory.Delete(item.PhysicalPath, true); }482catch { return NtStatus.DirectoryNotEmpty; }483}484else485File.Delete(item.PhysicalPath);486}487else488_overlay.DeleteFile(item.Filename);489
490return NtStatus.Success;491}492
493public NtStatus DeleteDirectory(string fileName, IDokanFileInfo info)494{495return DeleteFile(fileName, info);496}497
498public NtStatus SetEndOfFile(string fileName, long length, IDokanFileInfo info)499{500FileStream stream = info.Context as FileStream;501if (stream != null)502stream.SetLength(length);503
504var item = GetFile(fileName) as OverlayFileEntry;505if (item != null)506item.SetLength(length);507
508return NtStatus.Success;509}510
511public NtStatus SetAllocationSize(string fileName, long length, IDokanFileInfo info)512{513FileStream stream = info.Context as FileStream;514if (stream != null)515stream.SetLength(length);516
517var item = GetFile(fileName) as OverlayFileEntry;518if (item != null)519item.SetLength(length);520
521return NtStatus.Success;522}523
524public NtStatus LockFile(string fileName, long offset, long length, IDokanFileInfo info)525{526return DokanResult.Success;527}528
529public NtStatus UnlockFile(string fileName, long offset, long length, IDokanFileInfo info)530{531return DokanResult.Success;532}533
534public NtStatus GetDiskFreeSpace(out long freeBytesAvailable, out long totalNumberOfBytes, out long totalNumberOfFreeBytes, IDokanFileInfo info)535{536if (string.IsNullOrEmpty(OverlayPath))537{538totalNumberOfBytes = new FileInfo(FileName).Length;539totalNumberOfFreeBytes = 0;540freeBytesAvailable = 0;541}542else543{544try545{546var driveInfo = new DriveInfo(Path.GetPathRoot(OverlayPath));547freeBytesAvailable = driveInfo.AvailableFreeSpace;548totalNumberOfBytes = driveInfo.TotalSize;549totalNumberOfFreeBytes = driveInfo.TotalFreeSpace;550}551catch552{553totalNumberOfBytes = new FileInfo(FileName).Length;554totalNumberOfFreeBytes = 0;555freeBytesAvailable = 0;556}557}558
559return NtStatus.Success;560}561
562public NtStatus GetFileSecurity(string fileName, out System.Security.AccessControl.FileSystemSecurity security, System.Security.AccessControl.AccessControlSections sections, IDokanFileInfo info)563{564security = null;565return NtStatus.NotImplemented;566}567
568public NtStatus SetFileSecurity(string fileName, System.Security.AccessControl.FileSystemSecurity security, System.Security.AccessControl.AccessControlSections sections, IDokanFileInfo info)569{570return NtStatus.DiskFull;571}572
573public NtStatus Mounted(IDokanFileInfo info)574{575return NtStatus.Success;576}577
578public NtStatus Unmounted(IDokanFileInfo info)579{580return NtStatus.Success;581}582
583public NtStatus FindStreams(string fileName, out IList<FileInformation> streams, IDokanFileInfo info)584{585streams = new FileInformation[0];586return DokanResult.NotImplemented;587}588#endregion589
590#region Helpers591
592private void RebuildTreeChildren()593{594foreach (var file in _entries.Values)595file.Children.Clear();596
597foreach (var file in _entries.Values)598{599if (file.Filename == "\\")600continue;601
602var dir = "\\" + Path.GetDirectoryName(file.Filename);603
604FileEntry entry;605if (_entries.TryGetValue(dir, out entry))606entry.Children.Add(file);607}608}609
610private void LoadOverlay()611{612if (string.IsNullOrEmpty(OverlayPath))613return;614
615// Add local files & directories616if (!Directory.Exists(OverlayPath))617Directory.CreateDirectory(OverlayPath);618
619AddPathToOverlay(OverlayPath);620
621// Overlay of deleted files622_overlay = new OverlayDeletionRepository(Path.Combine(OverlayPath, ".deletions"));623foreach (var removed in _overlay.DeletedFiles)624_entries.Remove(removed.Replace("/", "\\"));625}626
627private void AddPathToOverlay(string path)628{629var allLocalFiles = Directory.GetDirectories(path, "*.*", SearchOption.AllDirectories).Concat(630Directory.GetFiles(path, "*.*", SearchOption.AllDirectories));631
632foreach (var file in allLocalFiles)633{634if (file.IndexOf(OverlayPath) != 0)635continue;636
637var relative = file.Substring(OverlayPath.Length);638var relativeLower = relative;639
640if (relativeLower == "\\.deletions")641continue;642
643FileEntry entry = new OverlayFileEntry(relative, file);644_entries[relativeLower] = entry;645}646}647
648private FileEntry GetFile(string fileName)649{650FileEntry value;651if (_entries.TryGetValue(fileName, out value))652return value;653
654return null;655}656
657IList<FileInformation> FindFilesHelper(string fileName, string searchPattern)658{659var item = GetFile(fileName);660if (item == null)661return null;662
663if (item.IsDirectory)664{665if (item.Children == null)666return new FileInformation[] { };667
668var matcher = GetMatcher(searchPattern);669return item.Children.Where(x => matcher(Path.GetFileName(x.Filename))).Select(x => GetFileInformation(x)).ToList();670}671return null;672}673
674static Func<string, bool> GetMatcher(string searchPattern)675{676// Strange : dokan replaces * with < -> Restore * back677searchPattern = searchPattern.Replace("<", "*");678
679if (searchPattern == "*")680return (k) => true;681
682if (searchPattern.IndexOf('?') == -1 && searchPattern.IndexOf('*') == -1)683return key => key.Equals(searchPattern, StringComparison.OrdinalIgnoreCase);684
685var regex = "^" + Regex.Escape(searchPattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$";686return key => Regex.IsMatch(key, regex, RegexOptions.IgnoreCase);687}688
689FileEntry CreateToOverlay(string fileName, FileEntry item, bool directory = false)690{691if (string.IsNullOrEmpty(fileName) || fileName == "\\")692return item;693
694string fullPath = Path.Combine(OverlayPath, fileName.Substring(1));695
696if (item == null)697{698if (directory)699Directory.CreateDirectory(fullPath);700else701File.WriteAllText(fullPath, "");702
703var ret = new OverlayFileEntry(fileName, fullPath);704ret.IsDirectory = directory;705_entries[fileName.Replace("/", "\\")] = ret;706_overlay.RestoreFile(ret.Filename);707RebuildTreeChildren();708
709return ret;710}711
712if (item != null && !(item is OverlayFileEntry))713{714if (directory)715{716FileTools.CopyDirectory(item.PhysicalPath, fullPath, true);717
718var childItems = item.Traverse(c => c.Children).Skip(1).ToList();719var keysToRemove = _entries.Where(e => childItems.Contains(e.Value)).Select(e => e.Key).ToList();720foreach (var keyToRemove in keysToRemove)721_entries.Remove(keyToRemove);722
723AddPathToOverlay(fullPath);724}725else726{727using (var fs = new FileStream(fullPath, FileMode.CreateNew, System.IO.FileAccess.Write, FileShare.ReadWrite))728using (var stream = item.GetPhysicalFileStream())729stream.CopyTo(fs);730}731
732var ret = new OverlayFileEntry(fileName, fullPath);733ret.IsDirectory = directory;734_entries[fileName.Replace("/", "\\")] = ret;735_overlay.RestoreFile(ret.Filename);736RebuildTreeChildren();737
738return ret;739}740
741return item;742}743
744private FileInformation GetFileInformation(FileEntry item)745{746return new FileInformation()747{748Attributes = item.IsDirectory ? FileAttributes.Directory : FileAttributes.NotContentIndexed, //(FileAttributes)item.Attributes,749Length = item.Length,750CreationTime = item.CreationTime,751FileName = Path.GetFileName(item.Filename),752LastAccessTime = item.LastAccessTime,753LastWriteTime = item.LastWriteTime754};755}756
757#endregion758
759
760public NtStatus Mounted(string mountPoint, IDokanFileInfo info)761{762return NtStatus.Success;763}764}765}
766