Skip to content

Commit 2695551

Browse files
committed
Initial support for fixed-size VHDs and a basic MBR partition table
1 parent ac09b20 commit 2695551

File tree

7 files changed

+314
-12
lines changed

7 files changed

+314
-12
lines changed

TotalImage.IO/Containers/Container.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using System.Text;
34
using TotalImage.Partitions;
45

56
namespace TotalImage.Containers
@@ -67,7 +68,28 @@ protected Container(Stream stream)
6768
/// Load the file system from the container image
6869
/// </summary>
6970
/// <returns>The file system found on the image</returns>
70-
protected abstract PartitionTable LoadPartitionTable();
71+
protected virtual PartitionTable LoadPartitionTable()
72+
{
73+
// TODO: introduce a factory system that tries and then fails as required, like file systems have
74+
75+
using BinaryReader br = new BinaryReader(Content, Encoding.ASCII, true);
76+
br.BaseStream.Seek(0x1FE, SeekOrigin.Begin);
77+
var signature = br.ReadUInt16();
78+
79+
if (signature != 0xaa55)
80+
{
81+
return new NoPartitionTable(this);
82+
}
83+
84+
var mbrPartition = new MbrPartitionTable(this);
85+
// TODO: very naive check, assumse first partition will always start in third sector
86+
if (mbrPartition.Partitions.Count >= 1 && mbrPartition.Partitions[0].Offset == 0x10000)
87+
{
88+
return mbrPartition;
89+
}
90+
91+
return new NoPartitionTable(this);
92+
}
7193

7294
/// <summary>
7395
/// Get raw bytes from the container image

TotalImage.IO/Containers/RawContainer.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.IO;
3-
using TotalImage.Containers;
43
using TotalImage.FileSystems;
54
using TotalImage.FileSystems.FAT;
65
using TotalImage.Partitions;
@@ -82,12 +81,6 @@ public void ExtractFile(DirectoryEntry entry, string path)
8281
}
8382
}
8483

85-
/// <inheritdoc />
86-
protected override PartitionTable LoadPartitionTable()
87-
{
88-
return new NoPartitionTable(this);
89-
}
90-
9184
/// <inheritdoc />
9285
public override byte[] GetRawBytes(int offset, int length)
9386
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace TotalImage.Containers
5+
{
6+
public class VhdContainer : Container
7+
{
8+
private readonly Stream _contentStream;
9+
10+
/// <inheritdoc />
11+
public VhdContainer(string path) : base(path)
12+
{
13+
_contentStream = new PartialStream(containerStream, 0, containerStream.Length - 512);
14+
}
15+
16+
/// <inheritdoc />
17+
public VhdContainer(Stream stream) : base(stream)
18+
{
19+
_contentStream = new PartialStream(containerStream, 0, containerStream.Length - 512);
20+
}
21+
22+
/// <inheritdoc />
23+
public override Stream Content => _contentStream;
24+
25+
/// <inheritdoc />
26+
public override byte[] GetRawBytes(int offset, int length)
27+
{
28+
throw new NotImplementedException();
29+
}
30+
}
31+
}

TotalImage.IO/PartialStream.cs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace TotalImage
5+
{
6+
/// <summary>
7+
/// A stream that represents a part of an underlying stream
8+
/// </summary>
9+
internal class PartialStream : Stream
10+
{
11+
private readonly Stream _base;
12+
private readonly long _length;
13+
private readonly long _offsetStart;
14+
private readonly long _offsetEnd;
15+
16+
/// <summary>
17+
/// Creates a partial stream from
18+
/// </summary>
19+
/// <param name="stream">The underlying stream</param>
20+
/// <param name="offset">The start of the partial stream</param>
21+
/// <param name="length">The length of the partial stream</param>
22+
public PartialStream(Stream stream, long offset, long length)
23+
{
24+
_base = stream;
25+
_length = length;
26+
_offsetStart = offset;
27+
_offsetEnd = offset + length;
28+
}
29+
30+
/// <inheritdoc />
31+
public override bool CanRead => _base.CanRead;
32+
33+
/// <inheritdoc />
34+
public override bool CanSeek => _base.CanSeek;
35+
36+
/// <inheritdoc />
37+
public override bool CanWrite => _base.CanWrite;
38+
39+
/// <inheritdoc />
40+
public override long Length => _length;
41+
42+
/// <inheritdoc />
43+
public override long Position
44+
{
45+
get => _base.Position + _offsetStart;
46+
set
47+
{
48+
if (value >= _length)
49+
{
50+
throw new IOException();
51+
}
52+
53+
_base.Position = value + _offsetStart;
54+
}
55+
}
56+
57+
/// <inheritdoc />
58+
public override void Flush() => _base.Flush();
59+
60+
/// <inheritdoc />
61+
public override int Read(byte[] buffer, int offset, int count)
62+
{
63+
if (_base.Position >= _offsetEnd)
64+
{
65+
return 0; // already at end of partial stream
66+
}
67+
68+
long targetEnd = _base.Position + count;
69+
if (targetEnd < _offsetEnd)
70+
{
71+
// falls entirely within partial stream
72+
return _base.Read(buffer, offset, count);
73+
}
74+
75+
int revisedCount = (int)(_offsetEnd - _base.Position);
76+
int actualRead = _base.Read(buffer, offset, revisedCount);
77+
78+
return Math.Min(revisedCount, actualRead);
79+
}
80+
81+
/// <inheritdoc />
82+
public override long Seek(long offset, SeekOrigin origin)
83+
{
84+
long target;
85+
switch (origin)
86+
{
87+
case SeekOrigin.Begin:
88+
target = offset + _offsetStart;
89+
break;
90+
case SeekOrigin.End:
91+
target = offset + _offsetEnd;
92+
break;
93+
case SeekOrigin.Current:
94+
target = offset + _base.Position;
95+
break;
96+
default:
97+
throw new NotImplementedException();
98+
}
99+
100+
if (target < _offsetStart || target >= _offsetEnd)
101+
{
102+
throw new ArgumentOutOfRangeException();
103+
}
104+
105+
long result = _base.Seek(target, SeekOrigin.Begin);
106+
return result - _offsetStart;
107+
}
108+
109+
/// <inheritdoc />
110+
public override void SetLength(long value)
111+
{
112+
throw new NotImplementedException();
113+
}
114+
115+
/// <inheritdoc />
116+
public override void Write(byte[] buffer, int offset, int count)
117+
{
118+
if ((_base.Position + count) >= _offsetEnd)
119+
{
120+
throw new ArgumentOutOfRangeException();
121+
}
122+
123+
_base.Write(buffer, offset, count);
124+
}
125+
}
126+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel.DataAnnotations;
4+
using System.IO;
5+
using System.Text;
6+
using TotalImage.Containers;
7+
8+
namespace TotalImage.Partitions
9+
{
10+
/// <summary>
11+
///
12+
/// </summary>
13+
public class MbrPartitionTable : PartitionTable
14+
{
15+
// this is a hardcoded assumption that holds true for currently supported formats but should probably be reconsidered in the future
16+
private const int SECTOR_SIZE = 512;
17+
18+
/// <inheritdoc />
19+
public MbrPartitionTable(Container container) : base(container)
20+
{
21+
}
22+
23+
/// <inheritdoc />
24+
protected override IEnumerable<PartitionEntry> LoadPartitions()
25+
{
26+
using BinaryReader br = new BinaryReader(_container.Content, Encoding.ASCII, true);
27+
br.BaseStream.Seek(0x1FE, SeekOrigin.Begin);
28+
29+
var signature = br.ReadUInt16();
30+
if (signature != 0xaa55)
31+
{
32+
throw new InvalidDataException();
33+
}
34+
35+
br.BaseStream.Seek(0x1BE, SeekOrigin.Begin);
36+
37+
List<PartitionEntry> entries = new List<PartitionEntry>();
38+
for (int i = 0; i < 4; i++)
39+
{
40+
byte status = br.ReadByte();
41+
byte[] chsStart = br.ReadBytes(3);
42+
MbrPartitionType type = (MbrPartitionType)br.ReadByte();
43+
byte[] chsEnd = br.ReadBytes(3);
44+
uint lbaStart = br.ReadUInt32();
45+
uint lbaLength = br.ReadUInt32();
46+
47+
if (type == MbrPartitionType.Empty)
48+
{
49+
continue;
50+
}
51+
52+
uint offset = lbaStart * SECTOR_SIZE;
53+
uint length = lbaLength * SECTOR_SIZE;
54+
var entry = new MbrPartitionEntry((status & 0x80) != 0, type, offset, length, new PartialStream(_container.Content, offset, length));
55+
entries.Add(entry);
56+
}
57+
58+
return entries;
59+
}
60+
61+
/// <summary>
62+
/// A partition entry within an MBR Partition Table
63+
/// </summary>
64+
public class MbrPartitionEntry : PartitionEntry
65+
{
66+
private bool _active;
67+
private MbrPartitionType _type;
68+
69+
/// <summary>
70+
/// Indicates whether this partition is marked as active
71+
/// </summary>
72+
public bool Active
73+
{
74+
get => _active;
75+
set => _active = value;
76+
}
77+
78+
/// <summary>
79+
/// Indicates the type of partition
80+
/// </summary>
81+
public MbrPartitionType Type
82+
{
83+
get => _type;
84+
set => _type = value;
85+
}
86+
87+
/// <summary>
88+
/// Initialises an MBR partition entry
89+
/// </summary>
90+
/// <param name="active">Whether the partition is marked as active</param>
91+
/// <param name="type">The type of the partition</param>
92+
/// <param name="offset">The offset of the partition in it's container file</param>
93+
/// <param name="length">The length of the partition</param>
94+
/// <param name="stream">The stream containing the partition data</param>
95+
public MbrPartitionEntry(bool active, MbrPartitionType type, uint offset, uint length, Stream stream)
96+
: base(offset, length, stream)
97+
{
98+
_active = active;
99+
_type = type;
100+
}
101+
}
102+
103+
/// <summary>
104+
/// The type of partition
105+
/// </summary>
106+
public enum MbrPartitionType : byte
107+
{
108+
/// <summary>
109+
/// An empty partition entry
110+
/// </summary>
111+
[Display(Name = "Empty")]
112+
Empty = 0,
113+
114+
/// <summary>
115+
/// A FAT-12 partition for use with DOS
116+
/// </summary>
117+
[Display(Name = "DOS (FAT-12)")]
118+
DosFat12 = 0x01
119+
}
120+
}
121+
}

TotalImage.IO/Partitions/PartitionEntry.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.IO;
22
using TotalImage.FileSystems;
3-
using TotalImage.FileSystems.FAT;
43

54
namespace TotalImage.Partitions
65
{

TotalImage/frmMain.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -495,9 +495,10 @@ private void openImage_Click(object sender, EventArgs e)
495495
ofd.CheckPathExists = true;
496496
ofd.Multiselect = false;
497497
//We probably want this, but it degrades the dialog appearance to XP dialog... Some workaround for this would be nice.
498-
//ofd.ShowReadOnly = true;
498+
//ofd.ShowReadOnly = true;
499499
ofd.Filter =
500500
"Raw sector image (*.img, *.ima, *.vfd, *.flp, *.dsk, *.xdf, *.hdm)|*.img;*.ima;*.vfd;*.flp;*.dsk;*.xdf;*.hdm|" +
501+
"Microsoft VHD (*.vhd)|*.vhd|" +
501502
"All files (*.*)|*.*";
502503

503504
if (ofd.ShowDialog() == DialogResult.OK)
@@ -1275,7 +1276,7 @@ private void PopulateTreeView(TreeNode node, FileSystems.Directory dir)
12751276
subnode.ImageIndex = imgFilesSmall.Images.IndexOfKey("folder (Hidden)");
12761277
subnode.ForeColor = Color.Gray;
12771278
}
1278-
else {
1279+
else {
12791280
subnode.ImageIndex = imgFilesSmall.Images.IndexOfKey("folder");
12801281
}
12811282

@@ -1409,7 +1410,16 @@ private void OpenImage(string path)
14091410
filename = Path.GetFileName(path);
14101411
Text = filename + " - TotalImage";
14111412

1412-
image = new RawContainer(path);
1413+
var ext = Path.GetExtension(filename).ToLowerInvariant();
1414+
switch (ext)
1415+
{
1416+
case "vhd":
1417+
image = new VhdContainer(path);
1418+
break;
1419+
default:
1420+
image = new RawContainer(path);
1421+
break;
1422+
}
14131423

14141424
var root = new TreeNode(@"\");
14151425
root.ImageIndex = imgFilesSmall.Images.IndexOfKey("folder");

0 commit comments

Comments
 (0)