diff --git a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj
index 67b6463e..41f056ed 100644
--- a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj
+++ b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj
@@ -105,6 +105,7 @@
+
diff --git a/DiscImageChef.Filesystems/PFS.cs b/DiscImageChef.Filesystems/PFS.cs
new file mode 100644
index 00000000..7c929746
--- /dev/null
+++ b/DiscImageChef.Filesystems/PFS.cs
@@ -0,0 +1,300 @@
+// /***************************************************************************
+// The Disc Image Chef
+// ----------------------------------------------------------------------------
+//
+// Filename : AmigaDOS.cs
+// Author(s) : Natalia Portillo
+//
+// Component : Amiga Fast File System plugin.
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// Identifies the Professional File System and shows information.
+//
+// --[ License ] --------------------------------------------------------------
+//
+// This library is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, see .
+//
+// ----------------------------------------------------------------------------
+// Copyright © 2011-2016 Natalia Portillo
+// ****************************************************************************/
+
+using System;
+using System.Text;
+using System.Collections.Generic;
+using DiscImageChef.Console;
+using System.Runtime.InteropServices;
+
+namespace DiscImageChef.Filesystems
+{
+ class PFS : Filesystem
+ {
+ public PFS()
+ {
+ Name = "Professional File System";
+ PluginUUID = new Guid("68DE769E-D957-406A-8AE4-3781CA8CDA77");
+ }
+
+ public PFS(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd)
+ {
+ Name = "Professional File System";
+ PluginUUID = new Guid("68DE769E-D957-406A-8AE4-3781CA8CDA77");
+ }
+
+ ///
+ /// Boot block, first 2 sectors
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct BootBlock
+ {
+ ///
+ /// "PFS\1" disk type
+ ///
+ public uint diskType;
+ ///
+ /// Boot code, til completion
+ ///
+ public byte[] bootCode;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct RootBlock
+ {
+ ///
+ /// Disk type
+ ///
+ public uint diskType;
+ ///
+ /// Options
+ ///
+ public uint options;
+ ///
+ /// Current datestamp
+ ///
+ public uint datestamp;
+ ///
+ /// Volume creation day
+ ///
+ public ushort creationday;
+ ///
+ /// Volume creation minute
+ ///
+ public ushort creationminute;
+ ///
+ /// Volume creation tick
+ ///
+ public ushort creationtick;
+ ///
+ /// AmigaDOS protection bits
+ ///
+ public ushort protection;
+ ///
+ /// Volume label (Pascal string)
+ ///
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ public byte[] diskname;
+ ///
+ /// Last reserved block
+ ///
+ public uint lastreserved;
+ ///
+ /// First reserved block
+ ///
+ public uint firstreserved;
+ ///
+ /// Free reserved blocks
+ ///
+ public uint reservedfree;
+ ///
+ /// Size of reserved blocks in bytes
+ ///
+ public ushort reservedblocksize;
+ ///
+ /// Blocks in rootblock, including bitmap
+ ///
+ public ushort rootblockclusters;
+ ///
+ /// Free blocks
+ ///
+ public uint blocksfree;
+ ///
+ /// Blocks that must be always free
+ ///
+ public uint alwaysfree;
+ ///
+ /// Current bitmapfield number for allocation
+ ///
+ public uint rovingPointer;
+ ///
+ /// Pointer to deldir
+ ///
+ public uint delDirPtr;
+ ///
+ /// Disk size in sectors
+ ///
+ public uint diskSize;
+ ///
+ /// Rootblock extension
+ ///
+ public uint extension;
+ ///
+ /// Unused
+ ///
+ public uint unused;
+ }
+
+ ///
+ /// Identifier for AFS (PFS v1)
+ ///
+ const uint AFS_DISK = 0x41465301;
+ ///
+ /// Identifier for PFS v2
+ ///
+ const uint PFS2_DISK = 0x50465302;
+ ///
+ /// Identifier for PFS v3
+ ///
+ const uint PFS_DISK = 0x50465301;
+ ///
+ /// Identifier for multi-user AFS
+ ///
+ const uint MUAF_DISK = 0x6D754146;
+ ///
+ /// Identifier for multi-user PFS
+ ///
+ const uint MUPFS_DISK = 0x6D755046;
+
+ public override bool Identify(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd)
+ {
+ if(partitionStart >= partitionEnd)
+ return false;
+
+ BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian;
+
+ byte[] sector = imagePlugin.ReadSector(2 + partitionStart);
+
+ uint magic = BigEndianBitConverter.ToUInt32(sector, 0x00);
+
+ return magic == AFS_DISK || magic == PFS2_DISK || magic == PFS_DISK || magic == MUAF_DISK || magic == MUPFS_DISK;
+ }
+
+ public override void GetInformation(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd, out string information)
+ {
+ byte[] RootBlockSector = imagePlugin.ReadSector(2 + partitionStart);
+ RootBlock rootBlock = new RootBlock();
+ rootBlock = BigEndianMarshal.ByteArrayToStructureBigEndian(RootBlockSector);
+
+ StringBuilder sbInformation = new StringBuilder();
+ xmlFSType = new Schemas.FileSystemType();
+
+ switch(rootBlock.diskType)
+ {
+ case AFS_DISK:
+ case MUAF_DISK:
+ sbInformation.Append("Professional File System v1");
+ xmlFSType.Type = "PFS v1";
+ break;
+ case PFS2_DISK:
+ sbInformation.Append("Professional File System v2");
+ xmlFSType.Type = "PFS v2";
+ break;
+ case PFS_DISK:
+ case MUPFS_DISK:
+ sbInformation.Append("Professional File System v3");
+ xmlFSType.Type = "PFS v3";
+ break;
+ }
+
+ if(rootBlock.diskType == MUAF_DISK || rootBlock.diskType == MUPFS_DISK)
+ sbInformation.Append(", with multi-user support");
+
+ sbInformation.AppendLine();
+
+ sbInformation.AppendFormat("Volume name: {0}", StringHandlers.PascalToString(rootBlock.diskname)).AppendLine();
+ sbInformation.AppendFormat("Volume has {0} free sectors of {1}", rootBlock.blocksfree, rootBlock.diskSize).AppendLine();
+ sbInformation.AppendFormat("Volme created on {0}", DateHandlers.AmigaToDateTime(rootBlock.creationday, rootBlock.creationminute, rootBlock.creationtick)).AppendLine();
+ if(rootBlock.extension > 0)
+ sbInformation.AppendFormat("Root block extension resides at block {0}", rootBlock.extension).AppendLine();
+
+ information = sbInformation.ToString();
+
+ xmlFSType.CreationDate = DateHandlers.AmigaToDateTime(rootBlock.creationday, rootBlock.creationminute, rootBlock.creationtick);
+ xmlFSType.CreationDateSpecified = true;
+ xmlFSType.FreeClusters = rootBlock.blocksfree;
+ xmlFSType.FreeClustersSpecified = true;
+ xmlFSType.Clusters = rootBlock.diskSize / imagePlugin.GetSectorSize();
+ xmlFSType.ClusterSize = (int)imagePlugin.GetSectorSize();
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ public override Errno ReadLink(string path, ref string dest)
+ {
+ return Errno.NotImplemented;
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscImageChef.Partitions/RDB.cs b/DiscImageChef.Partitions/RDB.cs
index bd038d44..f2fd97ce 100644
--- a/DiscImageChef.Partitions/RDB.cs
+++ b/DiscImageChef.Partitions/RDB.cs
@@ -113,6 +113,10 @@ namespace DiscImageChef.PartPlugins
///
const uint TypeIDPFS = 0x50465301;
///
+ /// Type ID for ProfessionalFileSystem, "PFS\2"
+ ///
+ const uint TypeIDPFS2 = 0x50465302;
+ ///
/// Type ID for ProfessionalFileSystem, "muAF"
///
const uint TypeIDPFSm = 0x6D754146;
@@ -1379,6 +1383,7 @@ namespace DiscImageChef.PartPlugins
case TypeIDAMIXReserved:
return "Amiga UNIX Reserved partition (swap)";
case TypeIDPFS:
+ case TypeIDPFS2:
case TypeIDPFSm:
case TypeIDAFS:
return "ProfessionalFileSystem";
diff --git a/README.md b/README.md
index 58e350f9..e5309670 100644
--- a/README.md
+++ b/README.md
@@ -131,6 +131,7 @@ Supported file systems for identification and information only
* NILFS2
* Reiser file systems
* SGI XFS
+* Professional File System
Supported checksums
===================
diff --git a/TODO b/TODO
index 03e011f4..059c5a6b 100644
--- a/TODO
+++ b/TODO
@@ -10,7 +10,6 @@
Filesystem plugins:
--- Add support for SFS filesystem
---- Add support for PFS3 filesystem
--- Add support for Apple DOS filesystems
--- Add support for CBM filesystem
--- Add support for ZFS