Made unreal work and make the fake drive update with the json so you can edit the fake config and it writes to the real drive.
This commit is contained in:
471
MirrorFileSystem.cs
Normal file
471
MirrorFileSystem.cs
Normal file
@@ -0,0 +1,471 @@
|
||||
using System.Security.AccessControl;
|
||||
using DokanNet;
|
||||
using FileAccess = DokanNet.FileAccess;
|
||||
|
||||
namespace DevDrive;
|
||||
|
||||
/// <summary>
|
||||
/// A Dokan filesystem that mirrors a real folder but reports custom capacity.
|
||||
/// </summary>
|
||||
public class MirrorFileSystem : IDokanOperations
|
||||
{
|
||||
private const string FileSystemName = "NTFS";
|
||||
private const uint VolumeSerial = 0x19950101;
|
||||
|
||||
private readonly string _rootPath;
|
||||
private string _volumeName;
|
||||
private long _totalBytes;
|
||||
private long _freeBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new mirror filesystem.
|
||||
/// </summary>
|
||||
/// <param name="rootPath">The real folder to mirror.</param>
|
||||
/// <param name="volumeName">The volume name to display.</param>
|
||||
/// <param name="totalBytes">Fake total capacity in bytes.</param>
|
||||
/// <param name="freeBytes">Fake free space in bytes.</param>
|
||||
public MirrorFileSystem(string rootPath, string volumeName, long totalBytes, long freeBytes)
|
||||
{
|
||||
_rootPath = rootPath;
|
||||
_volumeName = volumeName;
|
||||
_totalBytes = totalBytes;
|
||||
_freeBytes = freeBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the volume metadata (name and capacity) without remounting.
|
||||
/// </summary>
|
||||
/// <param name="volumeName">New volume name.</param>
|
||||
/// <param name="totalBytes">New total capacity in bytes.</param>
|
||||
/// <param name="freeBytes">New free space in bytes.</param>
|
||||
public void UpdateMetadata(string volumeName, long totalBytes, long freeBytes)
|
||||
{
|
||||
_volumeName = volumeName;
|
||||
_totalBytes = totalBytes;
|
||||
_freeBytes = freeBytes;
|
||||
}
|
||||
|
||||
private string GetRealPath(string fileName)
|
||||
{
|
||||
var path = fileName.TrimStart('\\');
|
||||
return string.IsNullOrEmpty(path) ? _rootPath : Path.Combine(_rootPath, path);
|
||||
}
|
||||
|
||||
public NtStatus CreateFile(string fileName, FileAccess access, FileShare share, FileMode mode,
|
||||
FileOptions options, FileAttributes attributes, IDokanFileInfo info)
|
||||
{
|
||||
var realPath = GetRealPath(fileName);
|
||||
|
||||
if (info.IsDirectory)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case FileMode.CreateNew:
|
||||
if (Directory.Exists(realPath))
|
||||
return DokanResult.FileExists;
|
||||
Directory.CreateDirectory(realPath);
|
||||
return DokanResult.Success;
|
||||
|
||||
case FileMode.Open:
|
||||
if (!Directory.Exists(realPath))
|
||||
return DokanResult.PathNotFound;
|
||||
return DokanResult.Success;
|
||||
|
||||
default:
|
||||
if (!Directory.Exists(realPath))
|
||||
return DokanResult.PathNotFound;
|
||||
return DokanResult.Success;
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return DokanResult.AccessDenied;
|
||||
}
|
||||
}
|
||||
|
||||
var pathExists = File.Exists(realPath);
|
||||
var dirExists = Directory.Exists(realPath);
|
||||
|
||||
if (dirExists)
|
||||
{
|
||||
info.IsDirectory = true;
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case FileMode.Open:
|
||||
if (!pathExists)
|
||||
return DokanResult.FileNotFound;
|
||||
break;
|
||||
|
||||
case FileMode.CreateNew:
|
||||
if (pathExists)
|
||||
return DokanResult.FileExists;
|
||||
break;
|
||||
|
||||
case FileMode.Truncate:
|
||||
if (!pathExists)
|
||||
return DokanResult.FileNotFound;
|
||||
break;
|
||||
}
|
||||
|
||||
var fileAccess = System.IO.FileAccess.Read;
|
||||
if (access.HasFlag(FileAccess.WriteData) || access.HasFlag(FileAccess.AppendData) ||
|
||||
access.HasFlag(FileAccess.Delete) || access.HasFlag(FileAccess.GenericWrite))
|
||||
{
|
||||
fileAccess = System.IO.FileAccess.ReadWrite;
|
||||
}
|
||||
|
||||
info.Context = new FileStream(realPath, mode, fileAccess, share, 4096, options);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return DokanResult.AccessDenied;
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
return DokanResult.PathNotFound;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return DokanResult.FileNotFound;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return DokanResult.SharingViolation;
|
||||
}
|
||||
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
public void Cleanup(string fileName, IDokanFileInfo info)
|
||||
{
|
||||
(info.Context as FileStream)?.Dispose();
|
||||
info.Context = null;
|
||||
}
|
||||
|
||||
public void CloseFile(string fileName, IDokanFileInfo info)
|
||||
{
|
||||
(info.Context as FileStream)?.Dispose();
|
||||
info.Context = null;
|
||||
}
|
||||
|
||||
public NtStatus ReadFile(string fileName, byte[] buffer, out int bytesRead, long offset, IDokanFileInfo info)
|
||||
{
|
||||
bytesRead = 0;
|
||||
|
||||
if (info.Context is FileStream stream)
|
||||
{
|
||||
lock (stream)
|
||||
{
|
||||
stream.Position = offset;
|
||||
bytesRead = stream.Read(buffer, 0, buffer.Length);
|
||||
}
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
var realPath = GetRealPath(fileName);
|
||||
try
|
||||
{
|
||||
using var fs = new FileStream(realPath, FileMode.Open, System.IO.FileAccess.Read, FileShare.ReadWrite);
|
||||
fs.Position = offset;
|
||||
bytesRead = fs.Read(buffer, 0, buffer.Length);
|
||||
return DokanResult.Success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DokanResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public NtStatus WriteFile(string fileName, byte[] buffer, out int bytesWritten, long offset, IDokanFileInfo info)
|
||||
{
|
||||
bytesWritten = 0;
|
||||
|
||||
if (info.Context is FileStream stream)
|
||||
{
|
||||
lock (stream)
|
||||
{
|
||||
stream.Position = offset;
|
||||
stream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
bytesWritten = buffer.Length;
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
var realPath = GetRealPath(fileName);
|
||||
try
|
||||
{
|
||||
using var fs = new FileStream(realPath, FileMode.Open, System.IO.FileAccess.Write, FileShare.ReadWrite);
|
||||
fs.Position = offset;
|
||||
fs.Write(buffer, 0, buffer.Length);
|
||||
bytesWritten = buffer.Length;
|
||||
return DokanResult.Success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DokanResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public NtStatus FlushFileBuffers(string fileName, IDokanFileInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
(info.Context as FileStream)?.Flush();
|
||||
return DokanResult.Success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DokanResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public NtStatus GetFileInformation(string fileName, out FileInformation fileInfo, IDokanFileInfo info)
|
||||
{
|
||||
var realPath = GetRealPath(fileName);
|
||||
fileInfo = new FileInformation();
|
||||
|
||||
if (File.Exists(realPath))
|
||||
{
|
||||
var fi = new FileInfo(realPath);
|
||||
fileInfo.FileName = fi.Name;
|
||||
fileInfo.Attributes = fi.Attributes;
|
||||
fileInfo.CreationTime = fi.CreationTime;
|
||||
fileInfo.LastAccessTime = fi.LastAccessTime;
|
||||
fileInfo.LastWriteTime = fi.LastWriteTime;
|
||||
fileInfo.Length = fi.Length;
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
if (Directory.Exists(realPath))
|
||||
{
|
||||
var di = new DirectoryInfo(realPath);
|
||||
fileInfo.FileName = di.Name;
|
||||
fileInfo.Attributes = di.Attributes;
|
||||
fileInfo.CreationTime = di.CreationTime;
|
||||
fileInfo.LastAccessTime = di.LastAccessTime;
|
||||
fileInfo.LastWriteTime = di.LastWriteTime;
|
||||
fileInfo.Length = 0;
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
return DokanResult.FileNotFound;
|
||||
}
|
||||
|
||||
public NtStatus FindFiles(string fileName, out IList<FileInformation> files, IDokanFileInfo info)
|
||||
{
|
||||
return FindFilesWithPattern(fileName, "*", out files, info);
|
||||
}
|
||||
|
||||
public NtStatus FindFilesWithPattern(string fileName, string searchPattern, out IList<FileInformation> files, IDokanFileInfo info)
|
||||
{
|
||||
files = new List<FileInformation>();
|
||||
var realPath = GetRealPath(fileName);
|
||||
|
||||
if (!Directory.Exists(realPath))
|
||||
return DokanResult.PathNotFound;
|
||||
|
||||
try
|
||||
{
|
||||
var dirInfo = new DirectoryInfo(realPath);
|
||||
foreach (var fsi in dirInfo.EnumerateFileSystemInfos(searchPattern))
|
||||
{
|
||||
files.Add(new FileInformation
|
||||
{
|
||||
FileName = fsi.Name,
|
||||
Attributes = fsi.Attributes,
|
||||
CreationTime = fsi.CreationTime,
|
||||
LastAccessTime = fsi.LastAccessTime,
|
||||
LastWriteTime = fsi.LastWriteTime,
|
||||
Length = fsi is FileInfo fi ? fi.Length : 0
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DokanResult.Error;
|
||||
}
|
||||
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
public NtStatus SetFileAttributes(string fileName, FileAttributes attributes, IDokanFileInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.SetAttributes(GetRealPath(fileName), attributes);
|
||||
return DokanResult.Success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DokanResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public NtStatus SetFileTime(string fileName, DateTime? creationTime, DateTime? lastAccessTime,
|
||||
DateTime? lastWriteTime, IDokanFileInfo info)
|
||||
{
|
||||
var realPath = GetRealPath(fileName);
|
||||
try
|
||||
{
|
||||
if (creationTime.HasValue)
|
||||
File.SetCreationTime(realPath, creationTime.Value);
|
||||
if (lastAccessTime.HasValue)
|
||||
File.SetLastAccessTime(realPath, lastAccessTime.Value);
|
||||
if (lastWriteTime.HasValue)
|
||||
File.SetLastWriteTime(realPath, lastWriteTime.Value);
|
||||
return DokanResult.Success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DokanResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public NtStatus DeleteFile(string fileName, IDokanFileInfo info)
|
||||
{
|
||||
var realPath = GetRealPath(fileName);
|
||||
if (!File.Exists(realPath))
|
||||
return DokanResult.FileNotFound;
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
public NtStatus DeleteDirectory(string fileName, IDokanFileInfo info)
|
||||
{
|
||||
var realPath = GetRealPath(fileName);
|
||||
if (!Directory.Exists(realPath))
|
||||
return DokanResult.PathNotFound;
|
||||
if (Directory.EnumerateFileSystemEntries(realPath).Any())
|
||||
return DokanResult.DirectoryNotEmpty;
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
public NtStatus MoveFile(string oldName, string newName, bool replace, IDokanFileInfo info)
|
||||
{
|
||||
var oldPath = GetRealPath(oldName);
|
||||
var newPath = GetRealPath(newName);
|
||||
|
||||
(info.Context as FileStream)?.Dispose();
|
||||
info.Context = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (info.IsDirectory)
|
||||
{
|
||||
if (Directory.Exists(newPath))
|
||||
{
|
||||
if (!replace)
|
||||
return DokanResult.FileExists;
|
||||
Directory.Delete(newPath, true);
|
||||
}
|
||||
Directory.Move(oldPath, newPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (File.Exists(newPath))
|
||||
{
|
||||
if (!replace)
|
||||
return DokanResult.FileExists;
|
||||
File.Delete(newPath);
|
||||
}
|
||||
File.Move(oldPath, newPath);
|
||||
}
|
||||
return DokanResult.Success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DokanResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public NtStatus SetEndOfFile(string fileName, long length, IDokanFileInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (info.Context is FileStream stream)
|
||||
{
|
||||
stream.SetLength(length);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var fs = new FileStream(GetRealPath(fileName), FileMode.Open, System.IO.FileAccess.Write);
|
||||
fs.SetLength(length);
|
||||
}
|
||||
return DokanResult.Success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DokanResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public NtStatus SetAllocationSize(string fileName, long length, IDokanFileInfo info)
|
||||
{
|
||||
return SetEndOfFile(fileName, length, info);
|
||||
}
|
||||
|
||||
public NtStatus LockFile(string fileName, long offset, long length, IDokanFileInfo info)
|
||||
{
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
public NtStatus UnlockFile(string fileName, long offset, long length, IDokanFileInfo info)
|
||||
{
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
public NtStatus GetDiskFreeSpace(out long freeBytesAvailable, out long totalNumberOfBytes,
|
||||
out long totalNumberOfFreeBytes, IDokanFileInfo info)
|
||||
{
|
||||
freeBytesAvailable = _freeBytes;
|
||||
totalNumberOfBytes = _totalBytes;
|
||||
totalNumberOfFreeBytes = _freeBytes;
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
public NtStatus GetVolumeInformation(out string volumeLabel, out FileSystemFeatures features,
|
||||
out string fileSystemName, out uint maximumComponentLength, IDokanFileInfo info)
|
||||
{
|
||||
volumeLabel = _volumeName;
|
||||
features = FileSystemFeatures.CasePreservedNames | FileSystemFeatures.CaseSensitiveSearch |
|
||||
FileSystemFeatures.SupportsRemoteStorage | FileSystemFeatures.UnicodeOnDisk;
|
||||
fileSystemName = FileSystemName;
|
||||
maximumComponentLength = 256;
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
public NtStatus GetFileSecurity(string fileName, out FileSystemSecurity? security,
|
||||
AccessControlSections sections, IDokanFileInfo info)
|
||||
{
|
||||
security = null;
|
||||
return DokanResult.NotImplemented;
|
||||
}
|
||||
|
||||
public NtStatus SetFileSecurity(string fileName, FileSystemSecurity security,
|
||||
AccessControlSections sections, IDokanFileInfo info)
|
||||
{
|
||||
return DokanResult.NotImplemented;
|
||||
}
|
||||
|
||||
public NtStatus Mounted(string mountPoint, IDokanFileInfo info)
|
||||
{
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
public NtStatus Unmounted(IDokanFileInfo info)
|
||||
{
|
||||
return DokanResult.Success;
|
||||
}
|
||||
|
||||
public NtStatus FindStreams(string fileName, out IList<FileInformation> streams, IDokanFileInfo info)
|
||||
{
|
||||
streams = Array.Empty<FileInformation>();
|
||||
return DokanResult.NotImplemented;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user