Browse Source

WIP: PDD;

HOME 3 months ago
parent
commit
c0dd127e60

+ 6 - 3
DiskAccessLibrary/DiskAccessLibrary.csproj

@@ -203,9 +203,11 @@
     <Compile Include="LogicalDiskManager\Volumes\StripedVolume.cs" />
     <Compile Include="Mod\BlockDifferencingDiskImage\BddInfo.cs" />
     <Compile Include="Mod\BlockDifferencingDiskImage\BlockDifferencingDiskImage.cs" />
-    <Compile Include="Mod\BlockDifferencingDiskImage\BlockEntryAllocateFlagIndexer.cs" />
-    <Compile Include="Mod\BlockDifferencingDiskImage\BlockEntryOffsetIndexer.cs" />
-    <Compile Include="Mod\BlockDifferencingDiskImage\IReadOnlyIndexer.cs" />
+    <Compile Include="Mod\PhysicalDiskDifferencingDiskImage\PddInfo.cs" />
+    <Compile Include="Mod\PhysicalDiskDifferencingDiskImage\PhysicalDiskDifferencingDiskImage.cs" />
+    <Compile Include="Mod\Utility\BlockEntryAllocateFlagIndexer.cs" />
+    <Compile Include="Mod\Utility\BlockEntryOffsetIndexer.cs" />
+    <Compile Include="Mod\Utility\ReadOnlyIndexer.cs" />
     <Compile Include="Mod\Consts.cs" />
     <Compile Include="Mod\DiskImage.cs" />
     <Compile Include="Mod\DiskImageCreator.cs" />
@@ -257,6 +259,7 @@
   </ItemGroup>
   <ItemGroup>
     <Content Include="Mod\BlockDifferencingDiskImage\BddFormat.txt" />
+    <Content Include="Mod\PhysicalDiskDifferencingDiskImage\PddFormat.txt" />
     <Content Include="RevisionHistory.txt" />
   </ItemGroup>
   <ItemGroup>

+ 1 - 0
DiskAccessLibrary/Mod/BlockDifferencingDiskImage/BddInfo.cs

@@ -1,6 +1,7 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
+using DiskAccessLibrary.Mod.Utility;
 
 namespace DiskAccessLibrary
 {

+ 4 - 4
DiskAccessLibrary/Mod/BlockDifferencingDiskImage/BlockDifferencingDiskImage.cs

@@ -1,9 +1,9 @@
-using DiskAccessLibrary.Mod.BlockDifferencingDiskImage;
-using System;
+using System;
 using System.IO;
 using System.Security.Authentication;
 using System.Text;
 using DiskAccessLibrary.FileSystems;
+using DiskAccessLibrary.Mod.Utility;
 
 namespace DiskAccessLibrary
 {
@@ -18,7 +18,7 @@ namespace DiskAccessLibrary
         public int NumberOfBlocks => BddInfo.NumberOfBlocks;
         public int BlockSize => BddInfo.BlockSize;
 
-        public IReadOnlyIndexer<int, bool> IsBlockAllocated { get; }
+        public ReadOnlyIndexer<int, bool> IsBlockAllocated { get; }
 
         private bool _isExclusiveLock;
 
@@ -28,7 +28,7 @@ namespace DiskAccessLibrary
         public BlockDifferencingDiskImage(string diskImagePath, bool isReadOnly) : base(diskImagePath, isReadOnly)
         {
             BddInfo = new BddInfo(diskImagePath);
-            IsBlockAllocated = new IReadOnlyIndexer<int, bool>(i => BddInfo.Allocated[i]);
+            IsBlockAllocated = new ReadOnlyIndexer<int, bool>(i => BddInfo.Allocated[i]);
         }
 
         public void OpenForRead()

+ 0 - 17
DiskAccessLibrary/Mod/BlockDifferencingDiskImage/IReadOnlyIndexer.cs

@@ -1,17 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace DiskAccessLibrary.Mod.BlockDifferencingDiskImage
-{
-    public class IReadOnlyIndexer<TI, TV>
-    {
-        private readonly Func<TI, TV> _func;
-
-        public IReadOnlyIndexer(Func<TI, TV> _func) => this._func = _func;
-
-        public TV this[TI index] => _func(index);
-    }
-}

+ 1 - 1
DiskAccessLibrary/Mod/DiskImageFormat.cs

@@ -4,6 +4,6 @@
     {
         BlockDifferencingDiskImage,
         RawDiskImage,
-        VirtualHardDisk,
+        VirtualHardDisk
     }
 }

+ 14 - 0
DiskAccessLibrary/Mod/PhysicalDiskDifferencingDiskImage/PddFormat.txt

@@ -0,0 +1,14 @@
+File Structure
+=======
+4: Size of Block Byte
+4: Number of Blocks | BlockSize * NumberOfBlocks Must equal to Underlying Disk
+N: Block index table
+N: Dynamic allocated data blocks
+
+Block index entry 8 Bytes
+=======================
+8: Compound bits
+    Bit0~62: Offset of data block
+    Bit63: Allocated flag
+
+ 

+ 73 - 0
DiskAccessLibrary/Mod/PhysicalDiskDifferencingDiskImage/PddInfo.cs

@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using DiskAccessLibrary.Mod.Utility;
+
+namespace DiskAccessLibrary
+{
+    public class PddInfo
+    {
+        public const int BlockIndexEntrySize = 8;
+
+        public string SnapshotImagePath { get; }
+
+        public long Length { get; }
+
+        public int BlockSize { get; }
+        public int NumberOfBlocks { get; }
+
+        public long DataOffset { get; }
+
+        public long EntryTableOffset { get; }
+
+        public BlockEntryAllocateFlagIndexer Allocated { get; }
+
+        public BlockEntryOffsetIndexer Offset { get; set; }
+
+        public IReadOnlyList<ulong> Entries { get; }
+
+        public PddInfo(string snapshotImagePath, bool readEntries = true)
+        {
+            SnapshotImagePath = snapshotImagePath;
+            using var fs = new FileStream(snapshotImagePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+            using var reader = new BinaryReader(fs);
+
+            BlockSize = reader.ReadInt32(); //4
+            NumberOfBlocks = reader.ReadInt32(); //4
+
+            var snapMatchLength = NumberOfBlocks * (long)BlockSize;
+            Length = snapMatchLength;
+
+            if (false == readEntries) return;
+
+            EntryTableOffset = fs.Position;
+
+            var arr = new ulong[NumberOfBlocks];
+            for (var i = 0; i < NumberOfBlocks; i++)
+            {
+                arr[i] = reader.ReadUInt64();
+            }
+
+            Entries = arr;
+            DataOffset = fs.Position;
+
+            Allocated = new BlockEntryAllocateFlagIndexer(arr);
+            Offset = new BlockEntryOffsetIndexer(arr);
+        }
+
+        public static int CalcHeaderSize(string basedImagePath, int blockSize, out int blocks)
+        {
+            var baseLength = new FileInfo(basedImagePath).Length;
+            blocks = (int)(baseLength / blockSize);
+
+            return 2 + Encoding.UTF8.GetByteCount(basedImagePath) + 8 + blocks * BlockIndexEntrySize;
+        }
+
+        public static int CalcHeaderSize(long baseLength, int blockSize, out int blocks)
+        {
+            blocks = (int)(baseLength / blockSize);
+
+            return 2 + +8 + blocks * BlockIndexEntrySize;
+        }
+    }
+}

+ 297 - 0
DiskAccessLibrary/Mod/PhysicalDiskDifferencingDiskImage/PhysicalDiskDifferencingDiskImage.cs

@@ -0,0 +1,297 @@
+using System;
+using System.IO;
+using System.Security.Authentication;
+using System.Text;
+using DiskAccessLibrary.FileSystems;
+using DiskAccessLibrary.Mod.Utility;
+
+namespace DiskAccessLibrary
+{
+    public class PhysicalDiskDifferencingDiskImage : DiskImage
+    {
+        private const int SectorSize = 512;
+
+        private PddInfo PddInfo { get; }
+
+
+        public int NumberOfBlocks => PddInfo.NumberOfBlocks;
+        public int BlockSize => PddInfo.BlockSize;
+
+        public ReadOnlyIndexer<int, bool> IsBlockAllocated { get; }
+
+        private bool _isExclusiveLock;
+
+        private readonly Disk _underlyingDisk;
+        private FileStream _snapshotFileStream;
+
+        public PhysicalDiskDifferencingDiskImage(string diskImagePath, bool isReadOnly, Disk underlyingDisk) : base(diskImagePath, isReadOnly)
+        {
+            _underlyingDisk = underlyingDisk;
+            PddInfo = new PddInfo(diskImagePath);
+            IsBlockAllocated = new ReadOnlyIndexer<int, bool>(i => PddInfo.Allocated[i]);
+        }
+
+        public void OpenForRead()
+        {
+            _snapshotFileStream = File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+        }
+
+        public void CloseForRead()
+        {
+            _snapshotFileStream?.Close();
+        }
+
+        public void ReadBlock(byte[] buffer, int offset, int count, int blockIndex, int blockOffset)
+        {
+            if (false == PddInfo.Allocated[blockIndex])
+            {
+                //TODO:convert block to sectors
+
+                var rawOffset = (long)blockIndex * PddInfo.BlockSize + blockOffset;
+                var sectorIndex = rawOffset / _underlyingDisk.BytesPerSector;
+
+                while (count > 0)
+                {
+                    var read = _basedFileStream.Read(buffer, offset, count);
+                    offset += read;
+                    count -= read;
+                }
+            }
+            else
+            {
+                _snapshotFileStream.Position = PddInfo.Offset[blockIndex] + blockOffset;
+                while (count > 0)
+                {
+                    var read = _snapshotFileStream.Read(buffer, offset, count);
+                    offset += read;
+                    count -= read;
+                }
+            }
+        }
+
+        private void WriteBlock(byte[] buffer, int offset, int count, int blockIndex, int blockOffset)
+        {
+            if (false == PddInfo.Allocated[blockIndex])
+            {
+                //COW:allocate block
+
+                _snapshotFileStream.Position = _snapshotFileStream.Length;
+                PddInfo.Offset[blockIndex] = _snapshotFileStream.Position;
+
+                //copy leading block from based image
+                if (blockOffset > 0)
+                {
+                    var readCount = blockOffset;
+                    var bufLeading = new byte[readCount];
+
+                    //TODO:convert block to sectors
+
+
+                    _basedFileStream.Position = (long)blockIndex * PddInfo.BlockSize;
+
+                    var readOffset = 0;
+                    while (readCount > 0)
+                    {
+                        var read = _basedFileStream.Read(bufLeading, readOffset, readCount);
+                        readOffset += read;
+                        readCount -= read;
+                    }
+
+                    _snapshotFileStream.Position = PddInfo.Offset[blockIndex];
+                    _snapshotFileStream.Write(bufLeading, 0, bufLeading.Length);
+                }
+
+                //write to snapshot directly
+                _snapshotFileStream.Position = PddInfo.Offset[blockIndex] + blockOffset;
+                _snapshotFileStream.Write(buffer, offset, count);
+
+                //copy suffix block from based image
+                if (blockOffset + count < PddInfo.BlockSize)
+                {
+                    var suffixOffset = blockOffset + count;
+                    var readCount = PddInfo.BlockSize - suffixOffset;
+                    var bufSuffix = new byte[readCount];
+
+                    //TODO:convert block to sectors
+
+
+                    _basedFileStream.Position = (long)blockIndex * PddInfo.BlockSize + suffixOffset;
+                    var readOffset = 0;
+                    while (readCount > 0)
+                    {
+                        var read = _basedFileStream.Read(bufSuffix, readOffset, readCount);
+                        readOffset += read;
+                        readCount -= read;
+                    }
+
+                    _snapshotFileStream.Position = PddInfo.Offset[blockIndex] + suffixOffset;
+                    _snapshotFileStream.Write(bufSuffix, 0, bufSuffix.Length);
+                }
+
+                PddInfo.Allocated[blockIndex] = true;
+                _snapshotFileStream.Position = PddInfo.EntryTableOffset + PddInfo.BlockIndexEntrySize * blockIndex;
+                var bufEntry = BitConverter.GetBytes(PddInfo.Entries[blockIndex]);
+                _snapshotFileStream.Write(bufEntry, 0, bufEntry.Length);
+            }
+            else
+            {
+                _snapshotFileStream.Position = PddInfo.Offset[blockIndex] + blockOffset;
+                _snapshotFileStream.Write(buffer, offset, count);
+            }
+        }
+
+        public override byte[] ReadSectors(long sectorIndex, int sectorCount)
+        {
+            var rawOffset = sectorIndex * SectorSize;
+            var rawSize = sectorCount * SectorSize;
+
+            var buffer = new byte[rawSize];
+
+            for (var i = 0; i < rawSize;)
+            {
+                var blockIndex = (int)(rawOffset / PddInfo.BlockSize);
+                var blockOffset = (int)(rawOffset % PddInfo.BlockSize);
+                var bytesToRead = PddInfo.BlockSize - blockOffset;
+
+                if (i + bytesToRead > rawSize)
+                {
+                    bytesToRead = rawSize - i;
+                }
+
+                ReadBlock(buffer, i, bytesToRead, blockIndex, blockOffset);
+
+                i += bytesToRead;
+                rawOffset += bytesToRead;
+            }
+
+            return buffer;
+        }
+
+        public override void WriteSectors(long sectorIndex, byte[] data)
+        {
+            if (IsReadOnly) throw new AuthenticationException("Read only");
+
+            var rawOffset = sectorIndex * SectorSize;
+
+            for (var i = 0; i < data.Length;)
+            {
+                var blockIndex = (int)(rawOffset / PddInfo.BlockSize);
+                var blockOffset = (int)(rawOffset % PddInfo.BlockSize);
+                var bytesToWrite = PddInfo.BlockSize - blockOffset;
+
+                if (i + bytesToWrite > data.Length)
+                {
+                    bytesToWrite = data.Length - i;
+                }
+
+                WriteBlock(data, i, bytesToWrite, blockIndex, blockOffset);
+
+                i += bytesToWrite;
+                rawOffset += bytesToWrite;
+            }
+        }
+
+        public override bool ExclusiveLock()
+        {
+            if (_isExclusiveLock) return true;
+
+            _snapshotFileStream?.Close();
+            _snapshotFileStream = IsReadOnly
+                ? File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.Read)
+                : File.Open(Path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
+            _isExclusiveLock = true;
+
+            return true;
+        }
+
+        public void Clean()
+        {
+            if (IsReadOnly) return;
+            for (var blockIndex = 0; blockIndex < PddInfo.NumberOfBlocks; blockIndex++)
+            {
+                PddInfo.Allocated[blockIndex] = false;
+            }
+
+            for (var blockIndex = 0; blockIndex < PddInfo.NumberOfBlocks; blockIndex++)
+            {
+                _snapshotFileStream.Position = PddInfo.EntryTableOffset + PddInfo.BlockIndexEntrySize * blockIndex;
+                var bufEntry = BitConverter.GetBytes(PddInfo.Entries[blockIndex]);
+                _snapshotFileStream.Write(bufEntry, 0, bufEntry.Length);
+            }
+            _snapshotFileStream.SetLength(PddInfo.EntryTableOffset + PddInfo.NumberOfBlocks * PddInfo.BlockIndexEntrySize);
+        }
+
+        public override bool ReleaseLock()
+        {
+            _snapshotFileStream?.Close();
+            _isExclusiveLock = false;
+            return true;
+        }
+
+        public static BlockDifferencingDiskImage Create(string snapshot, long size, int blockSize)
+        {
+            var numberOfBlocks = (int)Math.DivRem(size, blockSize, out var rem);
+            if (rem != 0) throw new ArgumentException("Size no align to blockSize");
+
+            using var stream = File.Create(snapshot);
+            using var writer = new BinaryWriter(stream);
+
+            writer.Write((ushort)0);
+            writer.Write(blockSize);
+            writer.Write(numberOfBlocks);
+            writer.Flush();
+            stream.SetLength(stream.Length + numberOfBlocks * PddInfo.BlockIndexEntrySize);
+            writer.Close();
+            stream.Close();
+
+            //return instance
+            return new BlockDifferencingDiskImage(snapshot);
+        }
+
+        public static BlockDifferencingDiskImage Create(string based, string snapshot, int blockSize)
+        {
+            var fileSize = IoUtility.GetRealFileSize(based);
+
+            if (fileSize % blockSize != 0) throw new ArgumentException("Block size no mul to base image size", nameof(blockSize));
+
+            using var stream = File.Create(snapshot);
+            using var writer = new BinaryWriter(stream);
+
+            //create bdd struct
+
+            var bufBasedPath = Encoding.UTF8.GetBytes(System.IO.Path.GetFullPath(based));
+            var numberOfBlocks = (int)(fileSize / blockSize);
+            writer.Write((ushort)bufBasedPath.Length);
+            writer.Write(bufBasedPath);
+            writer.Write(blockSize);
+            writer.Write(numberOfBlocks);
+            writer.Flush();
+            stream.SetLength(stream.Length + numberOfBlocks * PddInfo.BlockIndexEntrySize);
+            writer.Close();
+            stream.Close();
+
+            //return instance
+            return new BlockDifferencingDiskImage(snapshot);
+        }
+
+        public override long Size => PddInfo.Length;
+
+        public override int BytesPerSector => SectorSize;
+
+        ////////////////////////////////////////////////////////////////////////////////////
+
+        public PhysicalDiskDifferencingDiskImage(string diskImagePath, Disk underlyingDisk) : this(diskImagePath, false, underlyingDisk)
+        {
+        }
+
+        public override bool ExclusiveLock(bool useOverlappedIO)
+        {
+            throw new System.NotImplementedException();
+        }
+
+        public override void Extend(long numberOfAdditionalBytes)
+        {
+            throw new System.NotImplementedException();
+        }
+    }
+}

+ 1 - 1
DiskAccessLibrary/Mod/BlockDifferencingDiskImage/BlockEntryAllocateFlagIndexer.cs

@@ -1,4 +1,4 @@
-namespace DiskAccessLibrary
+namespace DiskAccessLibrary.Mod.Utility
 {
     public class BlockEntryAllocateFlagIndexer
     {

+ 1 - 1
DiskAccessLibrary/Mod/BlockDifferencingDiskImage/BlockEntryOffsetIndexer.cs

@@ -1,4 +1,4 @@
-namespace DiskAccessLibrary
+namespace DiskAccessLibrary.Mod.Utility
 {
     public class BlockEntryOffsetIndexer
     {

+ 13 - 0
DiskAccessLibrary/Mod/Utility/ReadOnlyIndexer.cs

@@ -0,0 +1,13 @@
+using System;
+
+namespace DiskAccessLibrary.Mod.Utility
+{
+    public class ReadOnlyIndexer<TI, TV>
+    {
+        private readonly Func<TI, TV> _func;
+
+        public ReadOnlyIndexer(Func<TI, TV> func) => this._func = func;
+
+        public TV this[TI index] => _func(index);
+    }
+}

+ 22 - 6
ISCSIConsole/AddTargetForm.Designer.cs

@@ -41,6 +41,7 @@ namespace ISCSIConsole
             this.btnAddVolume = new System.Windows.Forms.Button();
             this.btnCreateDiskImage = new System.Windows.Forms.Button();
             this.btnRemove = new System.Windows.Forms.Button();
+            this.btnAddPhysicalDiskWithSnap = new System.Windows.Forms.Button();
             this.SuspendLayout();
             // 
             // btnAddDiskImage
@@ -96,7 +97,7 @@ namespace ISCSIConsole
             // btnOK
             // 
             this.btnOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-            this.btnOK.Location = new System.Drawing.Point(324, 194);
+            this.btnOK.Location = new System.Drawing.Point(324, 222);
             this.btnOK.Name = "btnOK";
             this.btnOK.Size = new System.Drawing.Size(75, 23);
             this.btnOK.TabIndex = 8;
@@ -108,7 +109,7 @@ namespace ISCSIConsole
             // 
             this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
             this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
-            this.btnCancel.Location = new System.Drawing.Point(405, 194);
+            this.btnCancel.Location = new System.Drawing.Point(405, 222);
             this.btnCancel.Name = "btnCancel";
             this.btnCancel.Size = new System.Drawing.Size(75, 23);
             this.btnCancel.TabIndex = 9;
@@ -125,10 +126,11 @@ namespace ISCSIConsole
             this.columnDescription,
             this.columnSize});
             this.listDisks.FullRowSelect = true;
+            this.listDisks.HideSelection = false;
             this.listDisks.Location = new System.Drawing.Point(57, 44);
             this.listDisks.MultiSelect = false;
             this.listDisks.Name = "listDisks";
-            this.listDisks.Size = new System.Drawing.Size(275, 139);
+            this.listDisks.Size = new System.Drawing.Size(275, 167);
             this.listDisks.TabIndex = 2;
             this.listDisks.UseCompatibleStateImageBehavior = false;
             this.listDisks.View = System.Windows.Forms.View.Details;
@@ -147,7 +149,7 @@ namespace ISCSIConsole
             // btnAddVolume
             // 
             this.btnAddVolume.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.btnAddVolume.Location = new System.Drawing.Point(340, 131);
+            this.btnAddVolume.Location = new System.Drawing.Point(340, 160);
             this.btnAddVolume.Name = "btnAddVolume";
             this.btnAddVolume.Size = new System.Drawing.Size(140, 23);
             this.btnAddVolume.TabIndex = 6;
@@ -171,7 +173,7 @@ namespace ISCSIConsole
             // 
             this.btnRemove.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
             this.btnRemove.Enabled = false;
-            this.btnRemove.Location = new System.Drawing.Point(340, 160);
+            this.btnRemove.Location = new System.Drawing.Point(340, 189);
             this.btnRemove.Name = "btnRemove";
             this.btnRemove.Size = new System.Drawing.Size(140, 23);
             this.btnRemove.TabIndex = 7;
@@ -179,12 +181,24 @@ namespace ISCSIConsole
             this.btnRemove.UseVisualStyleBackColor = true;
             this.btnRemove.Click += new System.EventHandler(this.btnRemove_Click);
             // 
+            // btnAddPhysicalDiskWithSnap
+            // 
+            this.btnAddPhysicalDiskWithSnap.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.btnAddPhysicalDiskWithSnap.Location = new System.Drawing.Point(338, 131);
+            this.btnAddPhysicalDiskWithSnap.Name = "btnAddPhysicalDiskWithSnap";
+            this.btnAddPhysicalDiskWithSnap.Size = new System.Drawing.Size(140, 23);
+            this.btnAddPhysicalDiskWithSnap.TabIndex = 5;
+            this.btnAddPhysicalDiskWithSnap.Text = "Physical Disk W/Snap";
+            this.btnAddPhysicalDiskWithSnap.UseVisualStyleBackColor = true;
+            this.btnAddPhysicalDiskWithSnap.Visible = false;
+            this.btnAddPhysicalDiskWithSnap.Click += new System.EventHandler(this.btnAddPhysicalDiskWithSnap_Click);
+            // 
             // AddTargetForm
             // 
             this.AcceptButton = this.btnOK;
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
             this.CancelButton = this.btnCancel;
-            this.ClientSize = new System.Drawing.Size(484, 221);
+            this.ClientSize = new System.Drawing.Size(484, 249);
             this.Controls.Add(this.btnRemove);
             this.Controls.Add(this.btnCreateDiskImage);
             this.Controls.Add(this.btnAddVolume);
@@ -194,6 +208,7 @@ namespace ISCSIConsole
             this.Controls.Add(this.label2);
             this.Controls.Add(this.label1);
             this.Controls.Add(this.txtTargetIQN);
+            this.Controls.Add(this.btnAddPhysicalDiskWithSnap);
             this.Controls.Add(this.btnAddPhysicalDisk);
             this.Controls.Add(this.btnAddDiskImage);
             this.KeyPreview = true;
@@ -227,5 +242,6 @@ namespace ISCSIConsole
         private System.Windows.Forms.Button btnAddVolume;
         private System.Windows.Forms.Button btnCreateDiskImage;
         private System.Windows.Forms.Button btnRemove;
+        private System.Windows.Forms.Button btnAddPhysicalDiskWithSnap;
     }
 }

+ 13 - 0
ISCSIConsole/AddTargetForm.cs

@@ -21,6 +21,7 @@ namespace ISCSIConsole
             InitializeComponent();
 #if Win32
             btnAddPhysicalDisk.Visible = true;
+            btnAddPhysicalDiskWithSnap.Visible = true;
             btnAddVolume.Visible = true;
             if (!SecurityHelper.IsAdministrator())
             {
@@ -201,5 +202,17 @@ namespace ISCSIConsole
         {
             btnCreateDiskImage.Text = "Create Virtual Disk";
         }
+
+        private void btnAddPhysicalDiskWithSnap_Click(object sender, EventArgs e)
+        {
+#if Win32
+            SelectPhysicalDiskWithSnapForm selectPhysicalDisk = new SelectPhysicalDiskWithSnapForm();
+            DialogResult result = selectPhysicalDisk.ShowDialog();
+            if (result == DialogResult.OK)
+            {
+                AddDisk(selectPhysicalDisk.SelectedDisk);
+            }
+#endif
+        }
     }
 }

+ 2 - 2
ISCSIConsole/CreateDiskImageForm.Designer.cs

@@ -120,7 +120,7 @@ namespace ISCSIConsole
             // 
             // btnOK
             // 
-            this.btnOK.Location = new System.Drawing.Point(164, 161);
+            this.btnOK.Location = new System.Drawing.Point(164, 176);
             this.btnOK.Name = "btnOK";
             this.btnOK.Size = new System.Drawing.Size(75, 23);
             this.btnOK.TabIndex = 6;
@@ -131,7 +131,7 @@ namespace ISCSIConsole
             // btnCancel
             // 
             this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
-            this.btnCancel.Location = new System.Drawing.Point(245, 161);
+            this.btnCancel.Location = new System.Drawing.Point(245, 176);
             this.btnCancel.Name = "btnCancel";
             this.btnCancel.Size = new System.Drawing.Size(75, 23);
             this.btnCancel.TabIndex = 7;

+ 12 - 9
ISCSIConsole/CreateDiskImageForm.cs

@@ -2,7 +2,6 @@ using DiskAccessLibrary;
 using ISCSIConsole.Mods;
 using System;
 using System.Collections.Generic;
-using System.IO;
 using System.Threading;
 using System.Windows.Forms;
 using static DiskAccessLibrary.Mod.Consts;
@@ -11,7 +10,7 @@ namespace ISCSIConsole
 {
     public partial class CreateDiskImageForm : BaseForm
     {
-        private bool m_isWorking;
+        private bool _isWorking;
 
         private FileDialog _imageFileDialog;
         private FileDialog _snapshotFileDialog;
@@ -128,7 +127,7 @@ namespace ISCSIConsole
                 ImageFileTextBox.Text = _imageFileDialog.FileName;
                 if ((DiskImageFormat)FormatComboBox.SelectedValue == DiskImageFormat.BlockDifferencingDiskImage)
                 {
-                    var length=IoUtility.GetRealFileSize(_imageFileDialog.FileName);
+                    var length = IoUtility.GetRealFileSize(_imageFileDialog.FileName);
 
                     // ReSharper disable once PossibleLossOfFraction
                     ImageSizeUpDown.Value = length / MegaByte;
@@ -201,7 +200,7 @@ namespace ISCSIConsole
                 return;
             }
 
-            m_isWorking = true;
+            _isWorking = true;
             new Thread(delegate ()
             {
                 DiskImage diskImage;
@@ -221,7 +220,7 @@ namespace ISCSIConsole
                         btnOK.Enabled = true;
                         btnCancel.Enabled = true;
                     });
-                    m_isWorking = false;
+                    _isWorking = false;
                     return;
                 }
 
@@ -236,14 +235,14 @@ namespace ISCSIConsole
                         ImageSizeUpDown.Enabled = true;
                         btnOK.Enabled = true;
                         btnCancel.Enabled = true;
-                        m_isWorking = false;
+                        _isWorking = false;
                     });
                     return;
                 }
                 DiskImage = diskImage;
                 Invoke((MethodInvoker)delegate
                 {
-                    m_isWorking = false;
+                    _isWorking = false;
                     DialogResult = DialogResult.OK;
                     Close();
                 });
@@ -264,11 +263,15 @@ namespace ISCSIConsole
 
         private void CreateDiskImageForm_FormClosing(object sender, FormClosingEventArgs e)
         {
-            if (m_isWorking)
+            if (_isWorking)
             {
                 e.Cancel = true;
                 MessageBox.Show("Please wait until the creation of the disk image is completed.", "Error");
             }
         }
+
+        private void PhysicalDiskButton_Click(object sender, EventArgs e)
+        {
+        }
     }
-}
+}

+ 36 - 0
ISCSIConsole/ISCSIConsole.csproj

@@ -20,6 +20,21 @@
     <TargetFrameworkProfile />
     <NuGetPackageImportStamp>
     </NuGetPackageImportStamp>
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -84,6 +99,12 @@
     <Compile Include="Mods\AutoLoadEntry.cs" />
     <Compile Include="Mods\IconExtractor.cs" />
     <Compile Include="Mods\NativeMethods.cs" />
+    <Compile Include="Mods\SelectPhysicalDiskWithSnapForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Mods\SelectPhysicalDiskWithSnapForm.Designer.cs">
+      <DependentUpon>SelectPhysicalDiskWithSnapForm.cs</DependentUpon>
+    </Compile>
     <Compile Include="Program.cs" />
     <Compile Include="Program.Log.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
@@ -111,6 +132,9 @@
       <SubType>Designer</SubType>
       <DependentUpon>MainForm.cs</DependentUpon>
     </EmbeddedResource>
+    <EmbeddedResource Include="Mods\SelectPhysicalDiskWithSnapForm.resx">
+      <DependentUpon>SelectPhysicalDiskWithSnapForm.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="SelectDiskImageForm.resx">
       <DependentUpon>SelectDiskImageForm.cs</DependentUpon>
       <SubType>Designer</SubType>
@@ -164,6 +188,18 @@
     <None Include="app.config" />
     <None Include="packages.config" />
   </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
+      <Visible>False</Visible>
+      <ProductName>Microsoft .NET Framework 4.7.2 %28x86 和 x64%29</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 1 - 1
ISCSIConsole/MainForm.cs

@@ -49,7 +49,7 @@ namespace ISCSIConsole
             comboIPAddress.DataSource = list;
             comboIPAddress.DisplayMember = "Key";
             comboIPAddress.ValueMember = "Value";
-            lblStatus.Text = "Author: Tal Aloni (tal.aloni.il@gmail.com)";
+            lblStatus.Text = "Author: Tal Aloni (tal.aloni.il@gmail.com) and Modded by Coder (@Bug_er)";
 #if Win32
             if (!SecurityHelper.IsAdministrator())
             {

+ 316 - 0
ISCSIConsole/Mods/SelectPhysicalDiskWithSnapForm.Designer.cs

@@ -0,0 +1,316 @@
+
+namespace ISCSIConsole.Mods
+{
+    partial class SelectPhysicalDiskWithSnapForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.SnapInfoValueLabel = new System.Windows.Forms.Label();
+            this.SnapInfoLabel = new System.Windows.Forms.Label();
+            this.ReadOnlyCheckBox = new System.Windows.Forms.CheckBox();
+            this.btnCancel = new System.Windows.Forms.Button();
+            this.btnOK = new System.Windows.Forms.Button();
+            this.SelectSnapButton = new System.Windows.Forms.Button();
+            this.SnapFilePathTextBox = new System.Windows.Forms.TextBox();
+            this.FileLabel = new System.Windows.Forms.Label();
+            this.DiskLabel = new System.Windows.Forms.Label();
+            this.DiskTextBox = new System.Windows.Forms.TextBox();
+            this.SelectDiskButton = new System.Windows.Forms.Button();
+            this.DiskInfoLabel = new System.Windows.Forms.Label();
+            this.DiskInfoValueLabel = new System.Windows.Forms.Label();
+            this.MainSplitContainer = new System.Windows.Forms.SplitContainer();
+            this.BottomPanel = new System.Windows.Forms.Panel();
+            this.BlockSizeUpDown = new System.Windows.Forms.DomainUpDown();
+            this.AheadLabel = new System.Windows.Forms.Label();
+            this.KbLabel = new System.Windows.Forms.Label();
+            this.BlockSizeLabel = new System.Windows.Forms.Label();
+            ((System.ComponentModel.ISupportInitialize)(this.MainSplitContainer)).BeginInit();
+            this.MainSplitContainer.Panel1.SuspendLayout();
+            this.MainSplitContainer.Panel2.SuspendLayout();
+            this.MainSplitContainer.SuspendLayout();
+            this.BottomPanel.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // SnapInfoValueLabel
+            // 
+            this.SnapInfoValueLabel.AutoSize = true;
+            this.SnapInfoValueLabel.Location = new System.Drawing.Point(47, 71);
+            this.SnapInfoValueLabel.Name = "SnapInfoValueLabel";
+            this.SnapInfoValueLabel.Size = new System.Drawing.Size(29, 12);
+            this.SnapInfoValueLabel.TabIndex = 16;
+            this.SnapInfoValueLabel.Text = "____";
+            // 
+            // SnapInfoLabel
+            // 
+            this.SnapInfoLabel.AutoSize = true;
+            this.SnapInfoLabel.Location = new System.Drawing.Point(5, 54);
+            this.SnapInfoLabel.Name = "SnapInfoLabel";
+            this.SnapInfoLabel.Size = new System.Drawing.Size(131, 12);
+            this.SnapInfoLabel.TabIndex = 17;
+            this.SnapInfoLabel.Text = "Snap Disk Image Info:";
+            // 
+            // ReadOnlyCheckBox
+            // 
+            this.ReadOnlyCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+            this.ReadOnlyCheckBox.AutoSize = true;
+            this.ReadOnlyCheckBox.Location = new System.Drawing.Point(3, 8);
+            this.ReadOnlyCheckBox.Name = "ReadOnlyCheckBox";
+            this.ReadOnlyCheckBox.Size = new System.Drawing.Size(78, 16);
+            this.ReadOnlyCheckBox.TabIndex = 15;
+            this.ReadOnlyCheckBox.Text = "Read only";
+            this.ReadOnlyCheckBox.UseVisualStyleBackColor = true;
+            // 
+            // btnCancel
+            // 
+            this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+            this.btnCancel.Location = new System.Drawing.Point(498, 1);
+            this.btnCancel.Name = "btnCancel";
+            this.btnCancel.Size = new System.Drawing.Size(75, 23);
+            this.btnCancel.TabIndex = 14;
+            this.btnCancel.Text = "Cancel";
+            this.btnCancel.UseVisualStyleBackColor = true;
+            // 
+            // btnOK
+            // 
+            this.btnOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.btnOK.Location = new System.Drawing.Point(417, 1);
+            this.btnOK.Name = "btnOK";
+            this.btnOK.Size = new System.Drawing.Size(75, 23);
+            this.btnOK.TabIndex = 13;
+            this.btnOK.Text = "OK";
+            this.btnOK.UseVisualStyleBackColor = true;
+            this.btnOK.Click += new System.EventHandler(this.btnOK_Click);
+            // 
+            // SelectSnapButton
+            // 
+            this.SelectSnapButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.SelectSnapButton.Location = new System.Drawing.Point(496, 3);
+            this.SelectSnapButton.Name = "SelectSnapButton";
+            this.SelectSnapButton.Size = new System.Drawing.Size(75, 23);
+            this.SelectSnapButton.TabIndex = 12;
+            this.SelectSnapButton.Text = "Browse..";
+            this.SelectSnapButton.UseVisualStyleBackColor = true;
+            this.SelectSnapButton.Click += new System.EventHandler(this.SelectSnapButton_Click);
+            // 
+            // SnapFilePathTextBox
+            // 
+            this.SnapFilePathTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.SnapFilePathTextBox.Location = new System.Drawing.Point(49, 3);
+            this.SnapFilePathTextBox.Name = "SnapFilePathTextBox";
+            this.SnapFilePathTextBox.ReadOnly = true;
+            this.SnapFilePathTextBox.Size = new System.Drawing.Size(441, 21);
+            this.SnapFilePathTextBox.TabIndex = 11;
+            // 
+            // FileLabel
+            // 
+            this.FileLabel.AutoSize = true;
+            this.FileLabel.Location = new System.Drawing.Point(5, 6);
+            this.FileLabel.Name = "FileLabel";
+            this.FileLabel.Size = new System.Drawing.Size(35, 12);
+            this.FileLabel.TabIndex = 10;
+            this.FileLabel.Text = "Snap:";
+            // 
+            // DiskLabel
+            // 
+            this.DiskLabel.AutoSize = true;
+            this.DiskLabel.Location = new System.Drawing.Point(5, 6);
+            this.DiskLabel.Name = "DiskLabel";
+            this.DiskLabel.Size = new System.Drawing.Size(35, 12);
+            this.DiskLabel.TabIndex = 10;
+            this.DiskLabel.Text = "Disk:";
+            // 
+            // DiskTextBox
+            // 
+            this.DiskTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.DiskTextBox.Location = new System.Drawing.Point(49, 3);
+            this.DiskTextBox.Name = "DiskTextBox";
+            this.DiskTextBox.ReadOnly = true;
+            this.DiskTextBox.Size = new System.Drawing.Size(441, 21);
+            this.DiskTextBox.TabIndex = 11;
+            // 
+            // SelectDiskButton
+            // 
+            this.SelectDiskButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.SelectDiskButton.Location = new System.Drawing.Point(496, 3);
+            this.SelectDiskButton.Name = "SelectDiskButton";
+            this.SelectDiskButton.Size = new System.Drawing.Size(75, 23);
+            this.SelectDiskButton.TabIndex = 12;
+            this.SelectDiskButton.Text = "Select..";
+            this.SelectDiskButton.UseVisualStyleBackColor = true;
+            this.SelectDiskButton.Click += new System.EventHandler(this.SelectDiskButton_Click);
+            // 
+            // DiskInfoLabel
+            // 
+            this.DiskInfoLabel.AutoSize = true;
+            this.DiskInfoLabel.Location = new System.Drawing.Point(5, 36);
+            this.DiskInfoLabel.Name = "DiskInfoLabel";
+            this.DiskInfoLabel.Size = new System.Drawing.Size(65, 12);
+            this.DiskInfoLabel.TabIndex = 17;
+            this.DiskInfoLabel.Text = "Disk Info:";
+            // 
+            // DiskInfoValueLabel
+            // 
+            this.DiskInfoValueLabel.AutoSize = true;
+            this.DiskInfoValueLabel.Location = new System.Drawing.Point(47, 58);
+            this.DiskInfoValueLabel.Name = "DiskInfoValueLabel";
+            this.DiskInfoValueLabel.Size = new System.Drawing.Size(29, 12);
+            this.DiskInfoValueLabel.TabIndex = 16;
+            this.DiskInfoValueLabel.Text = "____";
+            // 
+            // MainSplitContainer
+            // 
+            this.MainSplitContainer.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+            this.MainSplitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.MainSplitContainer.Location = new System.Drawing.Point(0, 0);
+            this.MainSplitContainer.Name = "MainSplitContainer";
+            this.MainSplitContainer.Orientation = System.Windows.Forms.Orientation.Horizontal;
+            // 
+            // MainSplitContainer.Panel1
+            // 
+            this.MainSplitContainer.Panel1.Controls.Add(this.DiskLabel);
+            this.MainSplitContainer.Panel1.Controls.Add(this.DiskInfoValueLabel);
+            this.MainSplitContainer.Panel1.Controls.Add(this.DiskTextBox);
+            this.MainSplitContainer.Panel1.Controls.Add(this.SelectDiskButton);
+            this.MainSplitContainer.Panel1.Controls.Add(this.DiskInfoLabel);
+            // 
+            // MainSplitContainer.Panel2
+            // 
+            this.MainSplitContainer.Panel2.Controls.Add(this.BlockSizeUpDown);
+            this.MainSplitContainer.Panel2.Controls.Add(this.AheadLabel);
+            this.MainSplitContainer.Panel2.Controls.Add(this.KbLabel);
+            this.MainSplitContainer.Panel2.Controls.Add(this.BlockSizeLabel);
+            this.MainSplitContainer.Panel2.Controls.Add(this.SnapInfoValueLabel);
+            this.MainSplitContainer.Panel2.Controls.Add(this.FileLabel);
+            this.MainSplitContainer.Panel2.Controls.Add(this.SnapInfoLabel);
+            this.MainSplitContainer.Panel2.Controls.Add(this.SnapFilePathTextBox);
+            this.MainSplitContainer.Panel2.Controls.Add(this.SelectSnapButton);
+            this.MainSplitContainer.Size = new System.Drawing.Size(576, 274);
+            this.MainSplitContainer.SplitterDistance = 127;
+            this.MainSplitContainer.TabIndex = 18;
+            // 
+            // BottomPanel
+            // 
+            this.BottomPanel.Controls.Add(this.ReadOnlyCheckBox);
+            this.BottomPanel.Controls.Add(this.btnCancel);
+            this.BottomPanel.Controls.Add(this.btnOK);
+            this.BottomPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
+            this.BottomPanel.Location = new System.Drawing.Point(0, 274);
+            this.BottomPanel.Name = "BottomPanel";
+            this.BottomPanel.Size = new System.Drawing.Size(576, 27);
+            this.BottomPanel.TabIndex = 19;
+            // 
+            // BlockSizeUpDown
+            // 
+            this.BlockSizeUpDown.Items.Add("1024");
+            this.BlockSizeUpDown.Items.Add("512");
+            this.BlockSizeUpDown.Items.Add("256");
+            this.BlockSizeUpDown.Items.Add("128");
+            this.BlockSizeUpDown.Items.Add("64");
+            this.BlockSizeUpDown.Items.Add("32");
+            this.BlockSizeUpDown.Items.Add("16");
+            this.BlockSizeUpDown.Location = new System.Drawing.Point(100, 30);
+            this.BlockSizeUpDown.Name = "BlockSizeUpDown";
+            this.BlockSizeUpDown.ReadOnly = true;
+            this.BlockSizeUpDown.Size = new System.Drawing.Size(55, 21);
+            this.BlockSizeUpDown.TabIndex = 21;
+            this.BlockSizeUpDown.Text = "512";
+            // 
+            // AheadLabel
+            // 
+            this.AheadLabel.AutoSize = true;
+            this.AheadLabel.Location = new System.Drawing.Point(206, 32);
+            this.AheadLabel.Name = "AheadLabel";
+            this.AheadLabel.Size = new System.Drawing.Size(77, 12);
+            this.AheadLabel.TabIndex = 20;
+            this.AheadLabel.Text = "____(____KB)";
+            // 
+            // KbLabel
+            // 
+            this.KbLabel.AutoSize = true;
+            this.KbLabel.Location = new System.Drawing.Point(159, 32);
+            this.KbLabel.Name = "KbLabel";
+            this.KbLabel.Size = new System.Drawing.Size(17, 12);
+            this.KbLabel.TabIndex = 19;
+            this.KbLabel.Text = "KB";
+            // 
+            // BlockSizeLabel
+            // 
+            this.BlockSizeLabel.AutoSize = true;
+            this.BlockSizeLabel.Location = new System.Drawing.Point(6, 32);
+            this.BlockSizeLabel.Name = "BlockSizeLabel";
+            this.BlockSizeLabel.Size = new System.Drawing.Size(71, 12);
+            this.BlockSizeLabel.TabIndex = 18;
+            this.BlockSizeLabel.Text = "Block Size:";
+            // 
+            // SelectPhysicalDiskWithSnapForm
+            // 
+            this.AcceptButton = this.btnOK;
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(576, 301);
+            this.Controls.Add(this.MainSplitContainer);
+            this.Controls.Add(this.BottomPanel);
+            this.Name = "SelectPhysicalDiskWithSnapForm";
+            this.Text = "Physical Disk With Snap";
+            this.MainSplitContainer.Panel1.ResumeLayout(false);
+            this.MainSplitContainer.Panel1.PerformLayout();
+            this.MainSplitContainer.Panel2.ResumeLayout(false);
+            this.MainSplitContainer.Panel2.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.MainSplitContainer)).EndInit();
+            this.MainSplitContainer.ResumeLayout(false);
+            this.BottomPanel.ResumeLayout(false);
+            this.BottomPanel.PerformLayout();
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.Label SnapInfoValueLabel;
+        private System.Windows.Forms.Label SnapInfoLabel;
+        private System.Windows.Forms.CheckBox ReadOnlyCheckBox;
+        private System.Windows.Forms.Button btnCancel;
+        private System.Windows.Forms.Button btnOK;
+        private System.Windows.Forms.Button SelectSnapButton;
+        private System.Windows.Forms.TextBox SnapFilePathTextBox;
+        private System.Windows.Forms.Label FileLabel;
+        private System.Windows.Forms.Label DiskLabel;
+        private System.Windows.Forms.TextBox DiskTextBox;
+        private System.Windows.Forms.Button SelectDiskButton;
+        private System.Windows.Forms.Label DiskInfoLabel;
+        private System.Windows.Forms.Label DiskInfoValueLabel;
+        private System.Windows.Forms.SplitContainer MainSplitContainer;
+        private System.Windows.Forms.Panel BottomPanel;
+        private System.Windows.Forms.DomainUpDown BlockSizeUpDown;
+        private System.Windows.Forms.Label AheadLabel;
+        private System.Windows.Forms.Label KbLabel;
+        private System.Windows.Forms.Label BlockSizeLabel;
+    }
+}

+ 81 - 0
ISCSIConsole/Mods/SelectPhysicalDiskWithSnapForm.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using DiskAccessLibrary;
+using static DiskAccessLibrary.Mod.Consts;
+
+namespace ISCSIConsole.Mods
+{
+    public partial class SelectPhysicalDiskWithSnapForm : BaseForm
+    {
+        private PhysicalDisk _underlyingDisk;
+
+        public SelectPhysicalDiskWithSnapForm()
+        {
+            InitializeComponent();
+        }
+
+        public Disk SelectedDisk { get; private set; }
+
+        private void SelectDiskButton_Click(object sender, EventArgs e)
+        {
+            SelectPhysicalDiskForm selectPhysicalDisk = new SelectPhysicalDiskForm();
+            DialogResult result = selectPhysicalDisk.ShowDialog();
+            if (result == DialogResult.OK)
+            {
+                _underlyingDisk = selectPhysicalDisk.SelectedDisk;
+
+                DiskTextBox.Text = $"{_underlyingDisk.Description} - {_underlyingDisk.SerialNumber}";
+
+                var sb = new StringBuilder();
+
+                sb.AppendLine($"Size: {_underlyingDisk.Size / MegaByte:N0} MB");
+                sb.AppendLine($"Disk Index: {_underlyingDisk.PhysicalDiskIndex}");
+                sb.AppendLine($"Bytes Per Sector: {_underlyingDisk.BytesPerSector}");
+
+                DiskInfoValueLabel.Text = sb.ToString();
+            }
+        }
+
+        private void SelectSnapButton_Click(object sender, EventArgs e)
+        {
+            if (_underlyingDisk == null)
+            {
+                MessageBox.Show("Select disk first.");
+                return;
+            }
+
+            var dlg = new OpenFileDialog
+            {
+                Filter = "Physical Disk Differencing Disk Image (*.pdd)|*.pdd",
+                CheckFileExists = false,
+            };
+
+            if (dlg.ShowDialog() == DialogResult.OK)
+            {
+                SnapFilePathTextBox.Text = dlg.FileName;
+
+                if (File.Exists(SnapFilePathTextBox.Text))
+                {
+
+                }
+                else
+                {
+                    
+                }
+            }
+        }
+
+        private void btnOK_Click(object sender, EventArgs e)
+        {
+
+        }
+    }
+}

+ 120 - 0
ISCSIConsole/Mods/SelectPhysicalDiskWithSnapForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>