diff --git a/DiscImageChef.Filesystems/ChangeLog b/DiscImageChef.Filesystems/ChangeLog index 22574a927..ded919ec7 100644 --- a/DiscImageChef.Filesystems/ChangeLog +++ b/DiscImageChef.Filesystems/ChangeLog @@ -1,3 +1,15 @@ +2016-07-31 Natalia Portillo + + * Dir.cs: + * File.cs: + * Info.cs: + * Super.cs: + * Consts.cs: + * Structs.cs: + * UCSDPascal.cs: + * DiscImageChef.Filesystems.csproj: Added support for U.C.S.D. + Pascal filesystem, closes #31 + 2016-07-31 Natalia Portillo * Super.cs: Reports correct filesystem version on Statfs(); diff --git a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj index 02dabced0..539cc44c5 100644 --- a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj +++ b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj @@ -71,6 +71,13 @@ + + + + + + + @@ -105,6 +112,7 @@ + diff --git a/DiscImageChef.Filesystems/UCSDPascal/Consts.cs b/DiscImageChef.Filesystems/UCSDPascal/Consts.cs index e13259e8c..5216ea220 100644 --- a/DiscImageChef.Filesystems/UCSDPascal/Consts.cs +++ b/DiscImageChef.Filesystems/UCSDPascal/Consts.cs @@ -5,11 +5,11 @@ // Filename : Consts.cs // Author(s) : Natalia Portillo // -// Component : Component +// Component : U.C.S.D. Pascal filesystem plugin. // // --[ Description ] ---------------------------------------------------------- // -// Description +// U.C.S.D. Pascal filesystem constants. // // --[ License ] -------------------------------------------------------------- // @@ -29,13 +29,32 @@ // ---------------------------------------------------------------------------- // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ -using System; + namespace DiscImageChef.Filesystems.UCSDPascal { - public class Consts + // Information from Call-A.P.P.L.E. Pascal Disk Directory Structure + public partial class PascalPlugin : Filesystem { - public Consts() + enum PascalFileKind : short { + /// Disk volume entry + Volume = 0, + /// File containing bad blocks + Bad, + /// Code file, machine executable + Code, + /// Text file, human readable + Text, + /// Information file for debugger + Info, + /// Data file + Data, + /// Graphics vectors + Graf, + /// Graphics screen image + Foto, + /// Security, not used + Secure } } } diff --git a/DiscImageChef.Filesystems/UCSDPascal/Dir.cs b/DiscImageChef.Filesystems/UCSDPascal/Dir.cs index dfad19262..58beb5b2d 100644 --- a/DiscImageChef.Filesystems/UCSDPascal/Dir.cs +++ b/DiscImageChef.Filesystems/UCSDPascal/Dir.cs @@ -5,11 +5,11 @@ // Filename : Dir.cs // Author(s) : Natalia Portillo // -// Component : Component +// Component : U.C.S.D. Pascal filesystem plugin. // // --[ Description ] ---------------------------------------------------------- // -// Description +// Methods to handle show the U.C.S.D. Pascal catalog as a directory. // // --[ License ] -------------------------------------------------------------- // @@ -29,13 +29,35 @@ // ---------------------------------------------------------------------------- // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ + using System; +using System.Collections.Generic; + namespace DiscImageChef.Filesystems.UCSDPascal { - public class Dir + // Information from Call-A.P.P.L.E. Pascal Disk Directory Structure + public partial class PascalPlugin : Filesystem { - public Dir() + public override Errno ReadDir(string path, ref List contents) { + if(!mounted) + return Errno.AccessDenied; + + if(!string.IsNullOrEmpty(path) && string.Compare(path, "/", StringComparison.OrdinalIgnoreCase) != 0) + return Errno.NotSupported; + + contents = new List(); + foreach(PascalFileEntry ent in fileEntries) + contents.Add(StringHandlers.PascalToString(ent.filename)); + + if(debug) + { + contents.Add("$"); + contents.Add("$Boot"); + } + + contents.Sort(); + return Errno.NoError; } } } diff --git a/DiscImageChef.Filesystems/UCSDPascal/File.cs b/DiscImageChef.Filesystems/UCSDPascal/File.cs index 37b962583..1411968b3 100644 --- a/DiscImageChef.Filesystems/UCSDPascal/File.cs +++ b/DiscImageChef.Filesystems/UCSDPascal/File.cs @@ -5,11 +5,11 @@ // Filename : File.cs // Author(s) : Natalia Portillo // -// Component : Component +// Component : U.C.S.D. Pascal filesystem plugin. // // --[ Description ] ---------------------------------------------------------- // -// Description +// Methods to handle files. // // --[ License ] -------------------------------------------------------------- // @@ -29,13 +29,142 @@ // ---------------------------------------------------------------------------- // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ + using System; + namespace DiscImageChef.Filesystems.UCSDPascal { - public class File + // Information from Call-A.P.P.L.E. Pascal Disk Directory Structure + public partial class PascalPlugin : Filesystem { - public File() + public override Errno MapBlock(string path, long fileBlock, ref long deviceBlock) { + if(!mounted) + return Errno.AccessDenied; + + return Errno.NotImplemented; + } + + public override Errno GetAttributes(string path, ref FileAttributes attributes) + { + if(!mounted) + return Errno.AccessDenied; + + string[] pathElements = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if(pathElements.Length != 1) + return Errno.NotSupported; + + PascalFileEntry entry; + Errno error = GetFileEntry(path, out entry); + + if(error == Errno.NoError) + { + attributes = new FileAttributes(); + attributes = FileAttributes.File; + } + + return error; + } + + public override Errno Read(string path, long offset, long size, ref byte[] buf) + { + if(!mounted) + return Errno.AccessDenied; + + string[] pathElements = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if(pathElements.Length != 1) + return Errno.NotSupported; + + byte[] file; + Errno error; + + if(debug && + (string.Compare(path, "$", StringComparison.InvariantCulture) == 0 + || string.Compare(path, "$Boot", StringComparison.InvariantCulture) == 0)) + { + if(string.Compare(path, "$", StringComparison.InvariantCulture) == 0) + file = catalogBlocks; + else + file = bootBlocks; + } + else + { + PascalFileEntry entry; + error = GetFileEntry(path, out entry); + + if(error != Errno.NoError) + return error; + + byte[] tmp = device.ReadSectors((ulong)entry.firstBlock, (uint)(entry.lastBlock - entry.firstBlock + 1)); + file = new byte[(entry.lastBlock - entry.firstBlock) * device.GetSectorSize() + entry.lastBytes]; + Array.Copy(tmp, 0, file, 0, file.Length); + } + + if(offset >= file.Length || size == 0 || offset + size == file.Length) + { + buf = new byte[0]; + return Errno.NoError; + } + + if(offset + size > file.Length) + size = file.Length - offset; + + buf = new byte[size]; + + Array.Copy(file, offset, buf, 0, size); + + return Errno.NoError; + } + + public override Errno Stat(string path, ref FileEntryInfo stat) + { + if(!mounted) + return Errno.AccessDenied; + + if(!mounted) + return Errno.AccessDenied; + + string[] pathElements = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if(pathElements.Length != 1) + return Errno.NotSupported; + + PascalFileEntry entry; + Errno error = GetFileEntry(path, out entry); + + if(error != Errno.NoError) + return error; + + stat = new FileEntryInfo(); + stat.Attributes = new FileAttributes(); + stat.Attributes = FileAttributes.File; + stat.Blocks = entry.lastBlock - entry.firstBlock + 1; + stat.BlockSize = device.GetSectorSize(); + stat.DeviceNo = 0; + stat.GID = 0; + stat.Inode = 0; + stat.LastWriteTimeUtc = DateHandlers.UCSDPascalToDateTime(entry.mtime); + stat.Length = (entry.lastBlock - entry.firstBlock) * device.GetSectorSize() + entry.lastBytes; + stat.Links = 1; + stat.Mode = 0x124; + stat.UID = 0; + + return Errno.NoError; + } + + Errno GetFileEntry(string path, out PascalFileEntry entry) + { + entry = new PascalFileEntry(); + + foreach(PascalFileEntry ent in fileEntries) + { + if(string.Compare(path, StringHandlers.PascalToString(ent.filename), StringComparison.InvariantCultureIgnoreCase) == 0) + { + entry = ent; + return Errno.NoError; + } + } + + return Errno.NoSuchFile; } } } diff --git a/DiscImageChef.Filesystems/UCSDPascal/Info.cs b/DiscImageChef.Filesystems/UCSDPascal/Info.cs index f25abc0f7..61af656ee 100644 --- a/DiscImageChef.Filesystems/UCSDPascal/Info.cs +++ b/DiscImageChef.Filesystems/UCSDPascal/Info.cs @@ -5,11 +5,11 @@ // Filename : Info.cs // Author(s) : Natalia Portillo // -// Component : Component +// Component : U.C.S.D. Pascal filesystem plugin. // // --[ Description ] ---------------------------------------------------------- // -// Description +// Identifies the U.C.S.D. Pascal filesystem and shows information. // // --[ License ] -------------------------------------------------------------- // @@ -29,13 +29,133 @@ // ---------------------------------------------------------------------------- // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ + using System; +using System.Text; + namespace DiscImageChef.Filesystems.UCSDPascal { - public class Info + // Information from Call-A.P.P.L.E. Pascal Disk Directory Structure + public partial class PascalPlugin : Filesystem { - public Info() + public override bool Identify(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd) { + if(imagePlugin.GetSectors() < 3) + return false; + + // Blocks 0 and 1 are boot code + byte[] volBlock = imagePlugin.ReadSector(2 + partitionStart); + + PascalVolumeEntry volEntry = new PascalVolumeEntry(); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + volEntry.firstBlock = BigEndianBitConverter.ToInt16(volBlock, 0x00); + volEntry.lastBlock = BigEndianBitConverter.ToInt16(volBlock, 0x02); + volEntry.entryType = (PascalFileKind)BigEndianBitConverter.ToInt16(volBlock, 0x04); + volEntry.volumeName = new byte[8]; + Array.Copy(volBlock, 0x06, volEntry.volumeName, 0, 8); + volEntry.blocks = BigEndianBitConverter.ToInt16(volBlock, 0x0E); + volEntry.files = BigEndianBitConverter.ToInt16(volBlock, 0x10); + volEntry.dummy = BigEndianBitConverter.ToInt16(volBlock, 0x12); + volEntry.lastBoot = BigEndianBitConverter.ToInt16(volBlock, 0x14); + volEntry.tail = BigEndianBitConverter.ToInt32(volBlock, 0x16); + + // First block is always 0 (even is it's sector 2) + if(volEntry.firstBlock != 0) + return false; + + // Last volume record block must be after first block, and before end of device + if(volEntry.lastBlock <= volEntry.firstBlock || (ulong)volEntry.lastBlock > imagePlugin.GetSectors() - 2) + return false; + + // Volume record entry type must be volume or secure + if(volEntry.entryType != PascalFileKind.Volume && volEntry.entryType != PascalFileKind.Secure) + return false; + + // Volume name is max 7 characters + if(volEntry.volumeName[0] > 7) + return false; + + // Volume blocks is equal to volume sectors + if(volEntry.blocks < 0 || (ulong)volEntry.blocks != imagePlugin.GetSectors()) + return false; + + // There can be not less than zero files + if(volEntry.files < 0) + return false; + + return true; + } + + public override void GetInformation(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd, out string information) + { + StringBuilder sbInformation = new StringBuilder(); + information = ""; + + if(imagePlugin.GetSectors() < 3) + return; + + // Blocks 0 and 1 are boot code + byte[] volBlock = imagePlugin.ReadSector(2 + partitionStart); + + PascalVolumeEntry volEntry = new PascalVolumeEntry(); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + volEntry.firstBlock = BigEndianBitConverter.ToInt16(volBlock, 0x00); + volEntry.lastBlock = BigEndianBitConverter.ToInt16(volBlock, 0x02); + volEntry.entryType = (PascalFileKind)BigEndianBitConverter.ToInt16(volBlock, 0x04); + volEntry.volumeName = new byte[8]; + Array.Copy(volBlock, 0x06, volEntry.volumeName, 0, 8); + volEntry.blocks = BigEndianBitConverter.ToInt16(volBlock, 0x0E); + volEntry.files = BigEndianBitConverter.ToInt16(volBlock, 0x10); + volEntry.dummy = BigEndianBitConverter.ToInt16(volBlock, 0x12); + volEntry.lastBoot = BigEndianBitConverter.ToInt16(volBlock, 0x14); + volEntry.tail = BigEndianBitConverter.ToInt32(volBlock, 0x16); + + // First block is always 0 (even is it's sector 2) + if(volEntry.firstBlock != 0) + return; + + // Last volume record block must be after first block, and before end of device + if(volEntry.lastBlock <= volEntry.firstBlock || (ulong)volEntry.lastBlock > imagePlugin.GetSectors() - 2) + return; + + // Volume record entry type must be volume or secure + if(volEntry.entryType != PascalFileKind.Volume && volEntry.entryType != PascalFileKind.Secure) + return; + + // Volume name is max 7 characters + if(volEntry.volumeName[0] > 7) + return; + + // Volume blocks is equal to volume sectors + if(volEntry.blocks < 0 || (ulong)volEntry.blocks != imagePlugin.GetSectors()) + return; + + // There can be not less than zero files + if(volEntry.files < 0) + return; + + sbInformation.AppendFormat("Volume record spans from block {0} to block {1}", volEntry.firstBlock, volEntry.lastBlock).AppendLine(); + sbInformation.AppendFormat("Volume name: {0}", StringHandlers.PascalToString(volEntry.volumeName)).AppendLine(); + sbInformation.AppendFormat("Volume has {0} blocks", volEntry.blocks).AppendLine(); + sbInformation.AppendFormat("Volume has {0} files", volEntry.files).AppendLine(); + sbInformation.AppendFormat("Volume last booted at {0}", DateHandlers.UCSDPascalToDateTime(volEntry.lastBoot)).AppendLine(); + + information = sbInformation.ToString(); + + xmlFSType = new Schemas.FileSystemType(); + xmlFSType.Bootable = !ArrayHelpers.ArrayIsNullOrEmpty(imagePlugin.ReadSectors(partitionStart, 2)); + xmlFSType.Clusters = volEntry.blocks; + xmlFSType.ClusterSize = (int)imagePlugin.GetSectorSize(); + xmlFSType.Files = volEntry.files; + xmlFSType.FilesSpecified = true; + xmlFSType.Type = "UCSD Pascal"; + xmlFSType.VolumeName = StringHandlers.PascalToString(volEntry.volumeName); + + return; } } } diff --git a/DiscImageChef.Filesystems/UCSDPascal/Structs.cs b/DiscImageChef.Filesystems/UCSDPascal/Structs.cs index 8afb1ddcf..d92dadea2 100644 --- a/DiscImageChef.Filesystems/UCSDPascal/Structs.cs +++ b/DiscImageChef.Filesystems/UCSDPascal/Structs.cs @@ -5,11 +5,11 @@ // Filename : Structs.cs // Author(s) : Natalia Portillo // -// Component : Component +// Component : U.C.S.D. Pascal filesystem plugin. // // --[ Description ] ---------------------------------------------------------- // -// Description +// U.C.S.D. Pascal filesystem structures. // // --[ License ] -------------------------------------------------------------- // @@ -29,13 +29,48 @@ // ---------------------------------------------------------------------------- // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ -using System; + namespace DiscImageChef.Filesystems.UCSDPascal { - public class Structs + // Information from Call-A.P.P.L.E. Pascal Disk Directory Structure + public partial class PascalPlugin : Filesystem { - public Structs() + struct PascalVolumeEntry { + /// 0x00, first block of volume entry + public short firstBlock; + /// 0x02, last block of volume entry + public short lastBlock; + /// 0x04, entry type + public PascalFileKind entryType; + /// 0x06, volume name + public byte[] volumeName; + /// 0x0E, block in volume + public short blocks; + /// 0x10, files in volume + public short files; + /// 0x12, dummy + public short dummy; + /// 0x14, last booted + public short lastBoot; + /// 0x16, tail to make record same size as + public int tail; + } + + struct PascalFileEntry + { + /// 0x00, first block of file + public short firstBlock; + /// 0x02, last block of file + public short lastBlock; + /// 0x04, entry type + public PascalFileKind entryType; + /// 0x06, file name + public byte[] filename; + /// 0x16, bytes used in last block + public short lastBytes; + /// 0x18, modification time + public short mtime; } } } diff --git a/DiscImageChef.Filesystems/UCSDPascal/Super.cs b/DiscImageChef.Filesystems/UCSDPascal/Super.cs index 2dcce263a..04832c2fb 100644 --- a/DiscImageChef.Filesystems/UCSDPascal/Super.cs +++ b/DiscImageChef.Filesystems/UCSDPascal/Super.cs @@ -5,11 +5,11 @@ // Filename : Super.cs // Author(s) : Natalia Portillo // -// Component : Component +// Component : U.C.S.D. Pascal filesystem plugin. // // --[ Description ] ---------------------------------------------------------- // -// Description +// Handles mounting and umounting the U.C.S.D. Pascal filesystem. // // --[ License ] -------------------------------------------------------------- // @@ -29,13 +29,108 @@ // ---------------------------------------------------------------------------- // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ + using System; +using System.Collections.Generic; + namespace DiscImageChef.Filesystems.UCSDPascal { - public class Super + // Information from Call-A.P.P.L.E. Pascal Disk Directory Structure + public partial class PascalPlugin : Filesystem { - public Super() + public override Errno Mount() { + return Mount(false); + } + + public override Errno Mount(bool debug) + { + this.debug = debug; + if(device.GetSectors() < 3) + return Errno.InvalidArgument; + + // Blocks 0 and 1 are boot code + catalogBlocks = device.ReadSector(2); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + mountedVolEntry.firstBlock = BigEndianBitConverter.ToInt16(catalogBlocks, 0x00); + mountedVolEntry.lastBlock = BigEndianBitConverter.ToInt16(catalogBlocks, 0x02); + mountedVolEntry.entryType = (PascalFileKind)BigEndianBitConverter.ToInt16(catalogBlocks, 0x04); + mountedVolEntry.volumeName = new byte[8]; + Array.Copy(catalogBlocks, 0x06, mountedVolEntry.volumeName, 0, 8); + mountedVolEntry.blocks = BigEndianBitConverter.ToInt16(catalogBlocks, 0x0E); + mountedVolEntry.files = BigEndianBitConverter.ToInt16(catalogBlocks, 0x10); + mountedVolEntry.dummy = BigEndianBitConverter.ToInt16(catalogBlocks, 0x12); + mountedVolEntry.lastBoot = BigEndianBitConverter.ToInt16(catalogBlocks, 0x14); + mountedVolEntry.tail = BigEndianBitConverter.ToInt32(catalogBlocks, 0x16); + + if(mountedVolEntry.firstBlock != 0 || mountedVolEntry.lastBlock <= mountedVolEntry.firstBlock || + (ulong)mountedVolEntry.lastBlock > device.GetSectors() - 2 || + (mountedVolEntry.entryType != PascalFileKind.Volume && mountedVolEntry.entryType != PascalFileKind.Secure) || + mountedVolEntry.volumeName[0] > 7 || mountedVolEntry.blocks < 0 || (ulong)mountedVolEntry.blocks != device.GetSectors() || + mountedVolEntry.files < 0) + return Errno.InvalidArgument; + + catalogBlocks = device.ReadSectors(2, (uint)(mountedVolEntry.lastBlock - mountedVolEntry.firstBlock - 2)); + int offset = 26; + + fileEntries = new List(); + while(offset + 26 < catalogBlocks.Length) + { + PascalFileEntry entry = new PascalFileEntry(); + entry.filename = new byte[16]; + entry.firstBlock = BigEndianBitConverter.ToInt16(catalogBlocks, offset + 0x00); + entry.lastBlock = BigEndianBitConverter.ToInt16(catalogBlocks, offset + 0x02); + entry.entryType = (PascalFileKind)BigEndianBitConverter.ToInt16(catalogBlocks, offset + 0x04); + Array.Copy(catalogBlocks, offset + 0x06, entry.filename, 0, 16); + entry.lastBytes = BigEndianBitConverter.ToInt16(catalogBlocks, offset + 0x16); + entry.mtime = BigEndianBitConverter.ToInt16(catalogBlocks, offset + 0x18); + + if(entry.filename[0] <= 15 && entry.filename[0] > 0) + fileEntries.Add(entry); + + offset += 26; + } + + bootBlocks = device.ReadSectors(0, 2); + + xmlFSType = new Schemas.FileSystemType(); + xmlFSType.Bootable = !ArrayHelpers.ArrayIsNullOrEmpty(bootBlocks); + xmlFSType.Clusters = mountedVolEntry.blocks; + xmlFSType.ClusterSize = (int)device.GetSectorSize(); + xmlFSType.Files = mountedVolEntry.files; + xmlFSType.FilesSpecified = true; + xmlFSType.Type = "UCSD Pascal"; + xmlFSType.VolumeName = StringHandlers.PascalToString(mountedVolEntry.volumeName); + + mounted = true; + + return Errno.NoError; + } + + public override Errno Unmount() + { + mounted = false; + fileEntries = null; + return Errno.NoError; + } + + public override Errno StatFs(ref FileSystemInfo stat) + { + stat = new FileSystemInfo(); + stat.Blocks = mountedVolEntry.blocks; + stat.FilenameLength = 16; + stat.Files = (ulong)mountedVolEntry.files; + stat.FreeBlocks = 0; + stat.PluginId = PluginUUID; + stat.Type = "UCSD Pascal"; + + stat.FreeBlocks = mountedVolEntry.blocks - (mountedVolEntry.lastBlock - mountedVolEntry.firstBlock + 1); + foreach(PascalFileEntry entry in fileEntries) + stat.FreeBlocks -= (entry.lastBlock - entry.firstBlock + 1); + + return Errno.NotImplemented; } } } diff --git a/DiscImageChef.Filesystems/UCSDPascal/UCSDPascal.cs b/DiscImageChef.Filesystems/UCSDPascal/UCSDPascal.cs index 674a7769d..a11193055 100644 --- a/DiscImageChef.Filesystems/UCSDPascal/UCSDPascal.cs +++ b/DiscImageChef.Filesystems/UCSDPascal/UCSDPascal.cs @@ -9,7 +9,7 @@ // // --[ Description ] ---------------------------------------------------------- // -// Identifies the U.C.S.D. Pascal filesystem and shows information. +// Constructors and common variables for the U.C.S.D. Pascal filesystem plugin. // // --[ License ] -------------------------------------------------------------- // @@ -32,266 +32,48 @@ using System; using System.Collections.Generic; -using System.Text; using DiscImageChef.ImagePlugins; -namespace DiscImageChef.Filesystems +namespace DiscImageChef.Filesystems.UCSDPascal { // Information from Call-A.P.P.L.E. Pascal Disk Directory Structure - public class PascalPlugin : Filesystem + public partial class PascalPlugin : Filesystem { + bool mounted; + bool debug; + readonly ImagePlugin device; + + PascalVolumeEntry mountedVolEntry; + List fileEntries; + byte[] bootBlocks; + byte[] catalogBlocks; + public PascalPlugin() { Name = "U.C.S.D. Pascal filesystem"; PluginUUID = new Guid("B0AC2CB5-72AA-473A-9200-270B5A2C2D53"); } - public PascalPlugin(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd) + public PascalPlugin(ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd) { + device = imagePlugin; Name = "U.C.S.D. Pascal filesystem"; PluginUUID = new Guid("B0AC2CB5-72AA-473A-9200-270B5A2C2D53"); } - public override bool Identify(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd) - { - if(imagePlugin.GetSectors() < 3) - return false; - - // Blocks 0 and 1 are boot code - byte[] volBlock = imagePlugin.ReadSector(2 + partitionStart); - - PascalVolumeEntry volEntry = new PascalVolumeEntry(); - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - - volEntry.firstBlock = BigEndianBitConverter.ToInt16(volBlock, 0x00); - volEntry.lastBlock = BigEndianBitConverter.ToInt16(volBlock, 0x02); - volEntry.entryType = (PascalFileKind)BigEndianBitConverter.ToInt16(volBlock, 0x04); - volEntry.volumeName = new byte[8]; - Array.Copy(volBlock, 0x06, volEntry.volumeName, 0, 8); - volEntry.blocks = BigEndianBitConverter.ToInt16(volBlock, 0x0E); - volEntry.files = BigEndianBitConverter.ToInt16(volBlock, 0x10); - volEntry.dummy = BigEndianBitConverter.ToInt16(volBlock, 0x12); - volEntry.lastBoot = BigEndianBitConverter.ToInt16(volBlock, 0x14); - volEntry.tail = BigEndianBitConverter.ToInt32(volBlock, 0x16); - - // First block is always 0 (even is it's sector 2) - if(volEntry.firstBlock != 0) - return false; - - // Last volume record block must be after first block, and before end of device - if(volEntry.lastBlock <= volEntry.firstBlock || (ulong)volEntry.lastBlock > imagePlugin.GetSectors() - 2) - return false; - - // Volume record entry type must be volume or secure - if(volEntry.entryType != PascalFileKind.Volume && volEntry.entryType != PascalFileKind.Secure) - return false; - - // Volume name is max 7 characters - if(volEntry.volumeName[0] > 7) - return false; - - // Volume blocks is equal to volume sectors - if(volEntry.blocks < 0 || (ulong)volEntry.blocks != imagePlugin.GetSectors()) - return false; - - // There can be not less than zero files - if(volEntry.files < 0) - return false; - - return true; - } - - public override void GetInformation(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd, out string information) - { - StringBuilder sbInformation = new StringBuilder(); - information = ""; - - if(imagePlugin.GetSectors() < 3) - return; - - // Blocks 0 and 1 are boot code - byte[] volBlock = imagePlugin.ReadSector(2 + partitionStart); - - PascalVolumeEntry volEntry = new PascalVolumeEntry(); - - BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; - - volEntry.firstBlock = BigEndianBitConverter.ToInt16(volBlock, 0x00); - volEntry.lastBlock = BigEndianBitConverter.ToInt16(volBlock, 0x02); - volEntry.entryType = (PascalFileKind)BigEndianBitConverter.ToInt16(volBlock, 0x04); - volEntry.volumeName = new byte[8]; - Array.Copy(volBlock, 0x06, volEntry.volumeName, 0, 8); - volEntry.blocks = BigEndianBitConverter.ToInt16(volBlock, 0x0E); - volEntry.files = BigEndianBitConverter.ToInt16(volBlock, 0x10); - volEntry.dummy = BigEndianBitConverter.ToInt16(volBlock, 0x12); - volEntry.lastBoot = BigEndianBitConverter.ToInt16(volBlock, 0x14); - volEntry.tail = BigEndianBitConverter.ToInt32(volBlock, 0x16); - - // First block is always 0 (even is it's sector 2) - if(volEntry.firstBlock != 0) - return; - - // Last volume record block must be after first block, and before end of device - if(volEntry.lastBlock <= volEntry.firstBlock || (ulong)volEntry.lastBlock > imagePlugin.GetSectors() - 2) - return; - - // Volume record entry type must be volume or secure - if(volEntry.entryType != PascalFileKind.Volume && volEntry.entryType != PascalFileKind.Secure) - return; - - // Volume name is max 7 characters - if(volEntry.volumeName[0] > 7) - return; - - // Volume blocks is equal to volume sectors - if(volEntry.blocks < 0 || (ulong)volEntry.blocks != imagePlugin.GetSectors()) - return; - - // There can be not less than zero files - if(volEntry.files < 0) - return; - - sbInformation.AppendFormat("Volume record spans from block {0} to block {1}", volEntry.firstBlock, volEntry.lastBlock).AppendLine(); - sbInformation.AppendFormat("Volume name: {0}", StringHandlers.PascalToString(volEntry.volumeName)).AppendLine(); - sbInformation.AppendFormat("Volume has {0} blocks", volEntry.blocks).AppendLine(); - sbInformation.AppendFormat("Volume has {0} files", volEntry.files).AppendLine(); - sbInformation.AppendFormat("Volume last booted at {0}", DateHandlers.UCSDPascalToDateTime(volEntry.lastBoot)).AppendLine(); - - information = sbInformation.ToString(); - - byte[] bootBlocks = imagePlugin.ReadSectors(partitionStart, 2); - - xmlFSType = new Schemas.FileSystemType(); - xmlFSType.Bootable = !ArrayHelpers.ArrayIsNullOrEmpty(bootBlocks); - xmlFSType.Clusters = volEntry.blocks; - xmlFSType.ClusterSize = (int)imagePlugin.GetSectorSize(); - xmlFSType.Files = volEntry.files; - xmlFSType.FilesSpecified = true; - xmlFSType.Type = "UCSD Pascal"; - xmlFSType.VolumeName = StringHandlers.PascalToString(volEntry.volumeName); - - return; - } - - enum PascalFileKind : short - { - /// Disk volume entry - Volume = 0, - /// File containing bad blocks - Bad, - /// Code file, machine executable - Code, - /// Text file, human readable - Text, - /// Information file for debugger - Info, - /// Data file - Data, - /// Graphics vectors - Graf, - /// Graphics screen image - Foto, - /// Security, not used - Secure - } - - struct PascalVolumeEntry - { - /// 0x00, first block of volume entry - public short firstBlock; - /// 0x02, last block of volume entry - public short lastBlock; - /// 0x04, entry type - public PascalFileKind entryType; - /// 0x06, volume name - public byte[] volumeName; - /// 0x0E, block in volume - public short blocks; - /// 0x10, files in volume - public short files; - /// 0x12, dummy - public short dummy; - /// 0x14, last booted - public short lastBoot; - /// 0x16, tail to make record same size as - public int tail; - } - - struct PascalFileEntry - { - /// 0x00, first block of file - public short firstBlock; - /// 0x02, last block of file - public short lastBlock; - /// 0x04, entry type - public PascalFileKind entryType; - /// 0x06, file name - public byte[] filename; - /// 0x16, bytes used in last block - public short lastBytes; - /// 0x18, modification time - public short mtime; - } - - public override Errno Mount() - { - return Errno.NotImplemented; - } - - public override Errno Mount(bool debug) - { - return Errno.NotImplemented; - } - - public override Errno Unmount() - { - return Errno.NotImplemented; - } - - public override Errno MapBlock(string path, long fileBlock, ref long deviceBlock) - { - return Errno.NotImplemented; - } - - public override Errno GetAttributes(string path, ref FileAttributes attributes) - { - return Errno.NotImplemented; - } - public override Errno ListXAttr(string path, ref List xattrs) { - return Errno.NotImplemented; + return Errno.NotSupported; } public override Errno GetXattr(string path, string xattr, ref byte[] buf) { - return Errno.NotImplemented; - } - - public override Errno Read(string path, long offset, long size, ref byte[] buf) - { - return Errno.NotImplemented; - } - - public override Errno ReadDir(string path, ref List contents) - { - return Errno.NotImplemented; - } - - public override Errno StatFs(ref FileSystemInfo stat) - { - return Errno.NotImplemented; - } - - public override Errno Stat(string path, ref FileEntryInfo stat) - { - return Errno.NotImplemented; + return Errno.NotSupported; } public override Errno ReadLink(string path, ref string dest) { - return Errno.NotImplemented; + return Errno.NotSupported; } } } diff --git a/DiscImageChef.Helpers/ChangeLog b/DiscImageChef.Helpers/ChangeLog index 2dcf524ba..a4aa678cf 100644 --- a/DiscImageChef.Helpers/ChangeLog +++ b/DiscImageChef.Helpers/ChangeLog @@ -1,3 +1,8 @@ +2016-07-31 Natalia Portillo + + * DateHandlers.cs: Added support for U.C.S.D. Pascal + filesystem, closes #31 + 2016-07-29 Natalia Portillo * DiscImageChef.Helpers.csproj: Bump to version 3.1.0. diff --git a/DiscImageChef.Helpers/DateHandlers.cs b/DiscImageChef.Helpers/DateHandlers.cs index b80804f80..fef562475 100644 --- a/DiscImageChef.Helpers/DateHandlers.cs +++ b/DiscImageChef.Helpers/DateHandlers.cs @@ -140,6 +140,16 @@ namespace DiscImageChef temp = temp.AddMinutes(minutes); return temp.AddMilliseconds(ticks * 20); } + + public static DateTime UCSDPascalToDateTime(short dateRecord) + { + int year = ((dateRecord & 0xFE00) >> 9) + 1900; + int day = (dateRecord & 0x01F0) >> 4; + int month = (dateRecord & 0x000F); + + DicConsole.DebugWriteLine("UCSDPascalToDateTime handler", "dateRecord = 0x{0:X4}, year = {1}, month = {2}, day = {3}", dateRecord, year, month, day); + return new DateTime(year, month, day); + } } } diff --git a/README.md b/README.md index 6b57d038e..950bc6cdb 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Supported partitioning schemes Supported file systems for read-only operations =============================================== * Apple Lisa file system +* U.C.S.D Pascal file system Supported file systems for identification and information only ============================================================== diff --git a/TODO b/TODO index 1e2bdb2b5..9f67a446d 100644 --- a/TODO +++ b/TODO @@ -21,7 +21,6 @@ Filesystem plugins: --- Add support for SFS filesystem --- Add support for PFS3 filesystem --- Add support for Apple DOS filesystems ---- Add support for UCSD/Pascal filesystem --- Add support for AMSDOS filesystem --- Add support for CP/M filesystem --- Add support for CBM filesystem