diff --git a/DiscImageChef.Filesystems/AppleMFS/AppleMFS.cs b/DiscImageChef.Filesystems/AppleMFS/AppleMFS.cs index fec2994f3..0a27ae3a4 100644 --- a/DiscImageChef.Filesystems/AppleMFS/AppleMFS.cs +++ b/DiscImageChef.Filesystems/AppleMFS/AppleMFS.cs @@ -31,22 +31,47 @@ // ****************************************************************************/ using System; +using System.Collections.Generic; +using DiscImageChef.ImagePlugins; namespace DiscImageChef.Filesystems.AppleMFS { // Information from Inside Macintosh Volume II partial class AppleMFS : Filesystem { + bool mounted; + bool debug; + ImagePlugin device; + ulong partitionStart; + + Dictionary idToFilename; + Dictionary idToEntry; + Dictionary filenameToId; + + MFS_MasterDirectoryBlock volMDB; + byte[] bootBlocks; + byte[] mdbBlocks; + byte[] directoryBlocks; + byte[] blockMapBytes; + uint[] blockMap; + int sectorsPerBlock; + byte[] bootTags; + byte[] mdbTags; + byte[] directoryTags; + byte[] bitmapTags; + public AppleMFS() { Name = "Apple Macintosh File System"; PluginUUID = new Guid("36405F8D-0D26-4066-6538-5DBF5D065C3A"); } - public AppleMFS(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd) + public AppleMFS(ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd) { Name = "Apple Macintosh File System"; PluginUUID = new Guid("36405F8D-0D26-4066-6538-5DBF5D065C3A"); + device = imagePlugin; + this.partitionStart = partitionStart; } } } diff --git a/DiscImageChef.Filesystems/AppleMFS/Consts.cs b/DiscImageChef.Filesystems/AppleMFS/Consts.cs index bca72ac8f..d097a08b8 100644 --- a/DiscImageChef.Filesystems/AppleMFS/Consts.cs +++ b/DiscImageChef.Filesystems/AppleMFS/Consts.cs @@ -41,7 +41,12 @@ namespace DiscImageChef.Filesystems.AppleMFS const short DIRID_TRASH = -3; const short DIRID_DESKTOP = -2; + const short DIRID_TEMPLATE = -1; const short DIRID_ROOT = 0; + + const int BMAP_FREE = 0; + const int BMAP_LAST = 1; + const int BMAP_DIR = 0xFFF; } } diff --git a/DiscImageChef.Filesystems/AppleMFS/Dir.cs b/DiscImageChef.Filesystems/AppleMFS/Dir.cs index 8c90dddbc..3a03dfb29 100644 --- a/DiscImageChef.Filesystems/AppleMFS/Dir.cs +++ b/DiscImageChef.Filesystems/AppleMFS/Dir.cs @@ -31,6 +31,8 @@ // ****************************************************************************/ using System.Collections.Generic; +using System; +using DiscImageChef.Console; namespace DiscImageChef.Filesystems.AppleMFS { @@ -39,7 +41,88 @@ namespace DiscImageChef.Filesystems.AppleMFS { public override Errno ReadDir(string path, ref List contents) { - return Errno.NotImplemented; + if(!mounted) + return Errno.AccessDenied; + + if(!string.IsNullOrEmpty(path) && string.Compare(path, "/", StringComparison.OrdinalIgnoreCase) != 0) + return Errno.NotSupported; + + contents = new List(); + foreach(KeyValuePair kvp in idToFilename) + contents.Add(kvp.Value); + + if(debug) + { + contents.Add("$"); + contents.Add("$Bitmap"); + contents.Add("$MDB"); + if(bootBlocks != null) + contents.Add("$Boot"); + } + + contents.Sort(); + return Errno.NoError; + } + + public bool FillDirectory() + { + idToFilename = new Dictionary(); + idToEntry = new Dictionary(); + filenameToId = new Dictionary(); + + int offset = 0; + while(offset + 51 < directoryBlocks.Length) + { + MFS_FileEntry entry = new MFS_FileEntry(); + string lowerFilename; + entry.flUsrWds = new byte[16]; + + entry.flFlags = (MFS_FileFlags)directoryBlocks[offset + 0]; + if(!entry.flFlags.HasFlag(MFS_FileFlags.Used)) + break; + entry.flTyp = directoryBlocks[offset + 1]; + Array.Copy(directoryBlocks, offset + 2, entry.flUsrWds, 0, 16); + entry.flFlNum = BigEndianBitConverter.ToUInt32(directoryBlocks, offset + 18); + entry.flStBlk = BigEndianBitConverter.ToUInt16(directoryBlocks, offset + 22); + entry.flLgLen = BigEndianBitConverter.ToUInt32(directoryBlocks, offset + 24); + entry.flPyLen = BigEndianBitConverter.ToUInt32(directoryBlocks, offset + 28); + entry.flRStBlk = BigEndianBitConverter.ToUInt16(directoryBlocks, offset + 32); + entry.flRLgLen = BigEndianBitConverter.ToUInt32(directoryBlocks, offset + 34); + entry.flRPyLen = BigEndianBitConverter.ToUInt32(directoryBlocks, offset + 38); + entry.flCrDat = BigEndianBitConverter.ToUInt32(directoryBlocks, offset + 42); + entry.flMdDat = BigEndianBitConverter.ToUInt32(directoryBlocks, offset + 46); + entry.flNam = new byte[directoryBlocks[offset + 50] + 1]; + Array.Copy(directoryBlocks, offset + 50, entry.flNam, 0, entry.flNam.Length); + lowerFilename = GetStringFromPascal(entry.flNam).ToLowerInvariant().Replace('/', ':'); + + + if(entry.flFlags.HasFlag(MFS_FileFlags.Used) && !idToFilename.ContainsKey(entry.flFlNum) && + !idToEntry.ContainsKey(entry.flFlNum) && !filenameToId.ContainsKey(lowerFilename) && + entry.flFlNum > 0) + { + idToEntry.Add(entry.flFlNum, entry); + idToFilename.Add(entry.flFlNum, GetStringFromPascal(entry.flNam).Replace('/', ':')); + filenameToId.Add(lowerFilename, entry.flFlNum); + + System.Console.WriteLine("{0}", offset); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flFlags = {0}", entry.flFlags); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flTyp = {0}", entry.flTyp); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flFlNum = {0}", entry.flFlNum); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flStBlk = {0}", entry.flStBlk); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flLgLen = {0}", entry.flLgLen); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flPyLen = {0}", entry.flPyLen); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flRStBlk = {0}", entry.flRStBlk); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flRLgLen = {0}", entry.flRLgLen); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flRPyLen = {0}", entry.flRPyLen); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flCrDat = {0}", DateHandlers.MacToDateTime(entry.flCrDat)); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flMdDat = {0}", DateHandlers.MacToDateTime(entry.flMdDat)); + DicConsole.DebugWriteLine("DEBUG (AppleMFS plugin)", "entry.flNam0 = {0}", GetStringFromPascal(entry.flNam)); + } + + offset += (50 + entry.flNam.Length); + } + + return true; } } } diff --git a/DiscImageChef.Filesystems/AppleMFS/File.cs b/DiscImageChef.Filesystems/AppleMFS/File.cs index 365ec452f..c59cfc57e 100644 --- a/DiscImageChef.Filesystems/AppleMFS/File.cs +++ b/DiscImageChef.Filesystems/AppleMFS/File.cs @@ -30,6 +30,10 @@ // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ +using System; +using System.IO; +using DiscImageChef.Console; + namespace DiscImageChef.Filesystems.AppleMFS { // Information from Inside Macintosh Volume II @@ -37,28 +41,323 @@ namespace DiscImageChef.Filesystems.AppleMFS { public override Errno MapBlock(string path, long fileBlock, ref long deviceBlock) { - return Errno.NotImplemented; + deviceBlock = new long(); + + if(!mounted) + return Errno.AccessDenied; + + string[] pathElements = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if(pathElements.Length != 1) + return Errno.NotSupported; + + uint fileID; + MFS_FileEntry entry; + + if(!filenameToId.TryGetValue(path.ToLowerInvariant(), out fileID)) + return Errno.NoSuchFile; + + if(!idToEntry.TryGetValue(fileID, out entry)) + return Errno.NoSuchFile; + + if(fileBlock > (entry.flPyLen / volMDB.drAlBlkSiz)) + return Errno.InvalidArgument; + + uint nextBlock = entry.flStBlk; + long relBlock = 0; + + while(true) + { + if(relBlock == fileBlock) + { + deviceBlock = ((nextBlock - 2) * sectorsPerBlock) + volMDB.drAlBlSt + (long)partitionStart; + return Errno.NoError; + } + + if(blockMap[nextBlock] == BMAP_FREE || blockMap[nextBlock] == BMAP_LAST) + break; + + nextBlock = blockMap[nextBlock]; + relBlock++; + } + + return Errno.InOutError; } public override Errno GetAttributes(string path, ref FileAttributes attributes) { - return Errno.NotImplemented; + if(!mounted) + return Errno.AccessDenied; + + string[] pathElements = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if(pathElements.Length != 1) + return Errno.NotSupported; + + uint fileID; + MFS_FileEntry entry; + + if(!filenameToId.TryGetValue(path.ToLowerInvariant(), out fileID)) + return Errno.NoSuchFile; + + if(!idToEntry.TryGetValue(fileID, out entry)) + return Errno.NoSuchFile; + + attributes = new FileAttributes(); + MFS_FinderFlags fdFlags = (MFS_FinderFlags)BigEndianBitConverter.ToUInt16(entry.flUsrWds, 0x08); + + if(fdFlags.HasFlag(MFS_FinderFlags.kIsAlias)) + attributes |= FileAttributes.Alias; + if(fdFlags.HasFlag(MFS_FinderFlags.kHasBundle)) + attributes |= FileAttributes.Bundle; + if(fdFlags.HasFlag(MFS_FinderFlags.kHasBeenInited)) + attributes |= FileAttributes.HasBeenInited; + if(fdFlags.HasFlag(MFS_FinderFlags.kHasCustomIcon)) + attributes |= FileAttributes.HasCustomIcon; + if(fdFlags.HasFlag(MFS_FinderFlags.kHasNoINITs)) + attributes |= FileAttributes.HasNoINITs; + if(fdFlags.HasFlag(MFS_FinderFlags.kIsInvisible)) + attributes |= FileAttributes.Hidden; + if(entry.flFlags.HasFlag(MFS_FileFlags.Locked)) + attributes |= FileAttributes.Immutable; + if(fdFlags.HasFlag(MFS_FinderFlags.kIsOnDesk)) + attributes |= FileAttributes.IsOnDesk; + if(fdFlags.HasFlag(MFS_FinderFlags.kIsShared)) + attributes |= FileAttributes.Shared; + if(fdFlags.HasFlag(MFS_FinderFlags.kIsStationery)) + attributes |= FileAttributes.Stationery; + + if(!attributes.HasFlag(FileAttributes.Alias) && + !attributes.HasFlag(FileAttributes.Bundle) && + !attributes.HasFlag(FileAttributes.Stationery)) + attributes |= FileAttributes.File; + + attributes |= FileAttributes.BlockUnits; + + return Errno.NoError; } public override Errno Read(string path, long offset, long size, ref byte[] buf) { - return Errno.NotImplemented; + if(!mounted) + return Errno.AccessDenied; + + byte[] file; + Errno error = Errno.NoError; + + if(debug && string.Compare(path, "$", StringComparison.InvariantCulture) == 0) + file = directoryBlocks; + else if(debug && string.Compare(path, "$Boot", StringComparison.InvariantCulture) == 0 && bootBlocks != null) + file = bootBlocks; + else if(debug && string.Compare(path, "$Bitmap", StringComparison.InvariantCulture) == 0) + file = blockMapBytes; + else if(debug && string.Compare(path, "$MDB", StringComparison.InvariantCulture) == 0) + file = mdbBlocks; + else + error = ReadFile(path, out file, false, false); + + if(error != Errno.NoError) + return error; + + if(size == 0) + { + buf = new byte[0]; + return Errno.NoError; + } + + if(offset >= file.Length) + return Errno.InvalidArgument; + + if(size + offset >= 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) { - return Errno.NotImplemented; + if(!mounted) + return Errno.AccessDenied; + + string[] pathElements = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if(pathElements.Length != 1) + return Errno.NotSupported; + + if(debug) + { + if(string.Compare(path, "$", StringComparison.InvariantCulture) == 0 || + string.Compare(path, "$Boot", StringComparison.InvariantCulture) == 0 || + string.Compare(path, "$Bitmap", StringComparison.InvariantCulture) == 0 || + string.Compare(path, "$MDB", StringComparison.InvariantCulture) == 0) + { + stat = new FileEntryInfo(); + stat.Attributes = new FileAttributes(); + stat.Attributes = FileAttributes.System; + stat.BlockSize = device.GetSectorSize(); + stat.DeviceNo = 0; + stat.GID = 0; + stat.Inode = 0; + stat.Links = 1; + stat.Mode = 0x124; + stat.UID = 0; + + if(string.Compare(path, "$", StringComparison.InvariantCulture) == 0) + { + stat.Blocks = (directoryBlocks.Length / stat.BlockSize) + (directoryBlocks.Length % stat.BlockSize); + stat.Length = directoryBlocks.Length; + } + else if(string.Compare(path, "$Bitmap", StringComparison.InvariantCulture) == 0) + { + stat.Blocks = (blockMapBytes.Length / stat.BlockSize) + (blockMapBytes.Length % stat.BlockSize); + stat.Length = blockMapBytes.Length; + } + else if(string.Compare(path, "$Boot", StringComparison.InvariantCulture) == 0 && bootBlocks != null) + { + stat.Blocks = (bootBlocks.Length / stat.BlockSize) + (bootBlocks.Length % stat.BlockSize); + stat.Length = bootBlocks.Length; + } + else if(string.Compare(path, "$MDB", StringComparison.InvariantCulture) == 0) + { + stat.Blocks = (mdbBlocks.Length / stat.BlockSize) + (mdbBlocks.Length % stat.BlockSize); + stat.Length = mdbBlocks.Length; + } + else + return Errno.InvalidArgument; + + return Errno.NoError; + } + } + + uint fileID; + MFS_FileEntry entry; + + if(!filenameToId.TryGetValue(path.ToLowerInvariant(), out fileID)) + return Errno.NoSuchFile; + + if(!idToEntry.TryGetValue(fileID, out entry)) + return Errno.NoSuchFile; + + FileAttributes attr = new FileAttributes(); + Errno error = GetAttributes(path, ref attr); + if(error != Errno.NoError) + return error; + + stat = new FileEntryInfo(); + stat.Attributes = attr; + stat.Blocks = entry.flLgLen / volMDB.drAlBlkSiz; + stat.BlockSize = volMDB.drAlBlkSiz; + stat.CreationTime = DateHandlers.MacToDateTime(entry.flCrDat); + stat.DeviceNo = 0; + stat.GID = 0; + stat.Inode = entry.flFlNum; + stat.LastWriteTime = DateHandlers.MacToDateTime(entry.flMdDat); + stat.Length = entry.flPyLen; + stat.Links = 1; + stat.Mode = 0x124; + stat.UID = 0; + + return Errno.NoError; } public override Errno ReadLink(string path, ref string dest) { return Errno.NotImplemented; } + + Errno ReadFile(string path, out byte[] buf, bool resourceFork, bool tags) + { + buf = null; + + if(!mounted) + return Errno.AccessDenied; + + string[] pathElements = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if(pathElements.Length != 1) + return Errno.NotSupported; + + uint fileID; + MFS_FileEntry entry; + + if(!filenameToId.TryGetValue(path.ToLowerInvariant(), out fileID)) + return Errno.NoSuchFile; + + if(!idToEntry.TryGetValue(fileID, out entry)) + return Errno.NoSuchFile; + + uint nextBlock; + + if(resourceFork) + { + if(entry.flRPyLen == 0) + { + buf = new byte[0]; + return Errno.NoError; + } + nextBlock = entry.flRStBlk; + } + else + { + if(entry.flPyLen == 0) + { + buf = new byte[0]; + return Errno.NoError; + } + + nextBlock = entry.flStBlk; + } + + MemoryStream ms = new MemoryStream(); + byte[] sectors; + + do + { + if(tags) + sectors = device.ReadSectorsTag((ulong)((nextBlock - 2) * sectorsPerBlock) + volMDB.drAlBlSt + partitionStart, (uint)sectorsPerBlock, ImagePlugins.SectorTagType.AppleSectorTag); + else + sectors = device.ReadSectors((ulong)((nextBlock - 2) * sectorsPerBlock) + volMDB.drAlBlSt + partitionStart, (uint)sectorsPerBlock); + + ms.Write(sectors, 0, sectors.Length); + + if(blockMap[nextBlock] == BMAP_FREE) + { + DicConsole.ErrorWriteLine("File truncated at block {0}", nextBlock); + break; + } + + nextBlock = blockMap[nextBlock]; + } + while(nextBlock > BMAP_LAST); + + if(tags) + buf = ms.ToArray(); + else + { + if(resourceFork) + { + if(ms.Length < entry.flRLgLen) + buf = ms.ToArray(); + else + { + buf = new byte[entry.flRLgLen]; + Array.Copy(ms.ToArray(), 0, buf, 0, buf.Length); + } + } + else + { + if(ms.Length < entry.flLgLen) + buf = ms.ToArray(); + else + { + buf = new byte[entry.flLgLen]; + Array.Copy(ms.ToArray(), 0, buf, 0, buf.Length); + } + } + } + + return Errno.NoError; + } } } diff --git a/DiscImageChef.Filesystems/AppleMFS/Info.cs b/DiscImageChef.Filesystems/AppleMFS/Info.cs index 66dfe38a6..dcc03e253 100644 --- a/DiscImageChef.Filesystems/AppleMFS/Info.cs +++ b/DiscImageChef.Filesystems/AppleMFS/Info.cs @@ -47,6 +47,8 @@ namespace DiscImageChef.Filesystems.AppleMFS byte[] mdb_sector = imagePlugin.ReadSector(2 + partitionStart); + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + drSigWord = BigEndianBitConverter.ToUInt16(mdb_sector, 0x000); return drSigWord == MFS_MAGIC; @@ -67,6 +69,8 @@ namespace DiscImageChef.Filesystems.AppleMFS byte[] mdb_sector = imagePlugin.ReadSector(2 + partitionStart); byte[] bb_sector = imagePlugin.ReadSector(0 + partitionStart); + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + MDB.drSigWord = BigEndianBitConverter.ToUInt16(mdb_sector, 0x000); if(MDB.drSigWord != MFS_MAGIC) return; @@ -84,9 +88,9 @@ namespace DiscImageChef.Filesystems.AppleMFS MDB.drNxtFNum = BigEndianBitConverter.ToUInt32(mdb_sector, 0x01E); MDB.drFreeBks = BigEndianBitConverter.ToUInt16(mdb_sector, 0x022); MDB.drVNSiz = mdb_sector[0x024]; - variable_size = new byte[MDB.drVNSiz]; - Array.Copy(mdb_sector, 0x025, variable_size, 0, MDB.drVNSiz); - MDB.drVN = Encoding.ASCII.GetString(variable_size); + variable_size = new byte[MDB.drVNSiz+1]; + Array.Copy(mdb_sector, 0x024, variable_size, 0, MDB.drVNSiz+1); + MDB.drVN = GetStringFromPascal(variable_size); BB.signature = BigEndianBitConverter.ToUInt16(bb_sector, 0x000); @@ -132,12 +136,12 @@ namespace DiscImageChef.Filesystems.AppleMFS if((MDB.drAtrb & 0x8000) == 0x8000) sb.AppendLine("Volume is locked by software."); sb.AppendFormat("{0} files on volume", MDB.drNmFls).AppendLine(); - sb.AppendFormat("First directory block: {0}", MDB.drDirSt).AppendLine(); - sb.AppendFormat("{0} blocks in directory.", MDB.drBlLen).AppendLine(); - sb.AppendFormat("{0} volume allocation blocks.", MDB.drNmAlBlks).AppendLine(); - sb.AppendFormat("Size of allocation blocks: {0}", MDB.drAlBlkSiz).AppendLine(); + sb.AppendFormat("First directory sector: {0}", MDB.drDirSt).AppendLine(); + sb.AppendFormat("{0} sectors in directory.", MDB.drBlLen).AppendLine(); + sb.AppendFormat("{0} volume allocation blocks.", MDB.drNmAlBlks + 1).AppendLine(); + sb.AppendFormat("Size of allocation blocks: {0} bytes", MDB.drAlBlkSiz).AppendLine(); sb.AppendFormat("{0} bytes to allocate.", MDB.drClpSiz).AppendLine(); - sb.AppendFormat("{0} first allocation block.", MDB.drAlBlSt).AppendLine(); + sb.AppendFormat("First allocation block (#2) starts in sector {0}.", MDB.drAlBlSt).AppendLine(); sb.AppendFormat("Next unused file number: {0}", MDB.drNxtFNum).AppendLine(); sb.AppendFormat("{0} unused allocation blocks.", MDB.drFreeBks).AppendLine(); sb.AppendFormat("Volume name: {0}", MDB.drVN).AppendLine(); diff --git a/DiscImageChef.Filesystems/AppleMFS/Structs.cs b/DiscImageChef.Filesystems/AppleMFS/Structs.cs index a1064a5e1..1b77f35ad 100644 --- a/DiscImageChef.Filesystems/AppleMFS/Structs.cs +++ b/DiscImageChef.Filesystems/AppleMFS/Structs.cs @@ -52,9 +52,9 @@ namespace DiscImageChef.Filesystems.AppleMFS public ushort drAtrb; /// 0x00C, Volume number of files public ushort drNmFls; - /// 0x00E, First directory block + /// 0x00E, First directory sector public ushort drDirSt; - /// 0x010, Length of directory in blocks + /// 0x010, Length of directory in sectors public ushort drBlLen; /// 0x012, Volume allocation blocks public ushort drNmAlBlks; diff --git a/DiscImageChef.Filesystems/AppleMFS/Super.cs b/DiscImageChef.Filesystems/AppleMFS/Super.cs index 0a219d4d1..d60d80855 100644 --- a/DiscImageChef.Filesystems/AppleMFS/Super.cs +++ b/DiscImageChef.Filesystems/AppleMFS/Super.cs @@ -30,29 +30,156 @@ // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ +using System; +using System.Collections.Generic; + namespace DiscImageChef.Filesystems.AppleMFS { // Information from Inside Macintosh Volume II partial class AppleMFS : Filesystem { - public override Errno Mount() - { - return Errno.NotImplemented; - } - public override Errno Mount(bool debug) { - return Errno.NotImplemented; + this.debug = debug; + volMDB = new MFS_MasterDirectoryBlock(); + + byte[] variable_size; + + mdbBlocks = device.ReadSector(2 + partitionStart); + bootBlocks = device.ReadSector(0 + partitionStart); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + volMDB.drSigWord = BigEndianBitConverter.ToUInt16(mdbBlocks, 0x000); + if(volMDB.drSigWord != MFS_MAGIC) + return Errno.InvalidArgument; + + volMDB.drCrDate = BigEndianBitConverter.ToUInt32(mdbBlocks, 0x002); + volMDB.drLsBkUp = BigEndianBitConverter.ToUInt32(mdbBlocks, 0x006); + volMDB.drAtrb = BigEndianBitConverter.ToUInt16(mdbBlocks, 0x00A); + volMDB.drNmFls = BigEndianBitConverter.ToUInt16(mdbBlocks, 0x00C); + volMDB.drDirSt = BigEndianBitConverter.ToUInt16(mdbBlocks, 0x00E); + volMDB.drBlLen = BigEndianBitConverter.ToUInt16(mdbBlocks, 0x010); + volMDB.drNmAlBlks = BigEndianBitConverter.ToUInt16(mdbBlocks, 0x012); + volMDB.drAlBlkSiz = BigEndianBitConverter.ToUInt32(mdbBlocks, 0x014); + volMDB.drClpSiz = BigEndianBitConverter.ToUInt32(mdbBlocks, 0x018); + volMDB.drAlBlSt = BigEndianBitConverter.ToUInt16(mdbBlocks, 0x01C); + volMDB.drNxtFNum = BigEndianBitConverter.ToUInt32(mdbBlocks, 0x01E); + volMDB.drFreeBks = BigEndianBitConverter.ToUInt16(mdbBlocks, 0x022); + volMDB.drVNSiz = mdbBlocks[0x024]; + variable_size = new byte[volMDB.drVNSiz + 1]; + Array.Copy(mdbBlocks, 0x024, variable_size, 0, volMDB.drVNSiz + 1); + volMDB.drVN = GetStringFromPascal(variable_size); + + directoryBlocks = device.ReadSectors(volMDB.drDirSt + partitionStart, volMDB.drBlLen); + int bytesInBlockMap = ((volMDB.drNmAlBlks * 12) / 8) + ((volMDB.drNmAlBlks * 12) % 8); + int bytesBeforeBlockMap = 64; + int bytesInWholeMDB = bytesInBlockMap + bytesBeforeBlockMap; + int sectorsInWholeMDB = (bytesInWholeMDB / (int)device.ImageInfo.sectorSize) + (bytesInWholeMDB % (int)device.ImageInfo.sectorSize); + byte[] wholeMDB = device.ReadSectors(partitionStart + 2, (uint)sectorsInWholeMDB); + blockMapBytes = new byte[bytesInBlockMap]; + Array.Copy(wholeMDB, bytesBeforeBlockMap, blockMapBytes, 0, blockMapBytes.Length); + + int offset = 0; + blockMap = new uint[volMDB.drNmAlBlks + 2 + 1]; + for(int i = 2; i < volMDB.drNmAlBlks + 2; i+=8) + { + uint tmp1 = BigEndianBitConverter.ToUInt32(blockMapBytes, offset); + uint tmp2 = BigEndianBitConverter.ToUInt32(blockMapBytes, offset + 4); + uint tmp3 = BigEndianBitConverter.ToUInt32(blockMapBytes, offset + 8); + + if(i < blockMap.Length) + blockMap[i] = (tmp1 & 0xFFF00000) >> 20; + if(i + 2 < blockMap.Length) + blockMap[i + 1] = (tmp1 & 0xFFF00) >> 8; + if(i + 3 < blockMap.Length) + blockMap[i + 2] = ((tmp1 & 0xFF) << 4) + ((tmp2 & 0xF0000000) >> 28); + if(i + 4 < blockMap.Length) + blockMap[i + 3] = (tmp2 & 0xFFF0000) >> 16; + if(i + 5 < blockMap.Length) + blockMap[i + 4] = (tmp2 & 0xFFF0) >> 4; + if(i + 6 < blockMap.Length) + blockMap[i + 5] = ((tmp2 & 0xF) << 8) + ((tmp3 & 0xFF000000) >> 24); + if(i + 7 < blockMap.Length) + blockMap[i + 6] = (tmp3 & 0xFFF000) >> 12; + if(i + 8 < blockMap.Length) + blockMap[i + 7] = (tmp3 & 0xFFF); + + offset += 12; + } + + if(device.ImageInfo.readableSectorTags.Contains(ImagePlugins.SectorTagType.AppleSectorTag)) + { + mdbTags = device.ReadSectorTag(2 + partitionStart, ImagePlugins.SectorTagType.AppleSectorTag); + bootTags = device.ReadSectorTag(0 + partitionStart, ImagePlugins.SectorTagType.AppleSectorTag); + directoryTags = device.ReadSectorsTag(volMDB.drDirSt + partitionStart, volMDB.drBlLen, ImagePlugins.SectorTagType.AppleSectorTag); + bitmapTags = device.ReadSectorsTag(partitionStart + 2, (uint)sectorsInWholeMDB, ImagePlugins.SectorTagType.AppleSectorTag); + } + + sectorsPerBlock = (int)(volMDB.drAlBlkSiz / device.ImageInfo.sectorSize); + + if(!FillDirectory()) + return Errno.InvalidArgument; + + mounted = true; + + ushort bbSig = BigEndianBitConverter.ToUInt16(bootBlocks, 0x000); + + if(bbSig != MFSBB_MAGIC) + bootBlocks = null; + + xmlFSType = new Schemas.FileSystemType(); + if(volMDB.drLsBkUp > 0) + { + xmlFSType.BackupDate = DateHandlers.MacToDateTime(volMDB.drLsBkUp); + xmlFSType.BackupDateSpecified = true; + } + xmlFSType.Bootable = bbSig == MFSBB_MAGIC; + xmlFSType.Clusters = volMDB.drNmAlBlks; + xmlFSType.ClusterSize = (int)volMDB.drAlBlkSiz; + if(volMDB.drCrDate > 0) + { + xmlFSType.CreationDate = DateHandlers.MacToDateTime(volMDB.drCrDate); + xmlFSType.CreationDateSpecified = true; + } + xmlFSType.Files = volMDB.drNmFls; + xmlFSType.FilesSpecified = true; + xmlFSType.FreeClusters = volMDB.drFreeBks; + xmlFSType.FreeClustersSpecified = true; + xmlFSType.Type = "MFS"; + xmlFSType.VolumeName = volMDB.drVN; + + return Errno.NoError; + } + + public override Errno Mount() + { + return Mount(false); } public override Errno Unmount() { - return Errno.NotImplemented; + mounted = false; + idToFilename = null; + idToEntry = null; + filenameToId = null; + bootBlocks = null; + + return Errno.NoError; } public override Errno StatFs(ref FileSystemInfo stat) { - return Errno.NotImplemented; + stat = new FileSystemInfo(); + stat.Blocks = volMDB.drNmAlBlks; + stat.FilenameLength = 255; + stat.Files = volMDB.drNmFls; + stat.FreeBlocks = volMDB.drFreeBks; + stat.FreeFiles = uint.MaxValue - stat.Files; + stat.PluginId = PluginUUID; + stat.Type = "Apple MFS"; + + return Errno.NoError; } } } diff --git a/DiscImageChef.Filesystems/AppleMFS/Xattr.cs b/DiscImageChef.Filesystems/AppleMFS/Xattr.cs index 13895b204..609d1b708 100644 --- a/DiscImageChef.Filesystems/AppleMFS/Xattr.cs +++ b/DiscImageChef.Filesystems/AppleMFS/Xattr.cs @@ -32,6 +32,7 @@ // ****************************************************************************/ using System.Collections.Generic; +using System; namespace DiscImageChef.Filesystems.AppleMFS { @@ -40,12 +41,145 @@ namespace DiscImageChef.Filesystems.AppleMFS { public override Errno ListXAttr(string path, ref List xattrs) { - return Errno.NotImplemented; + if(!mounted) + return Errno.AccessDenied; + + string[] pathElements = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if(pathElements.Length != 1) + return Errno.NotSupported; + + xattrs = new List(); + + if(debug) + { + if(string.Compare(path, "$", StringComparison.InvariantCulture) == 0 || + string.Compare(path, "$Bitmap", StringComparison.InvariantCulture) == 0 || + string.Compare(path, "$Boot", StringComparison.InvariantCulture) == 0 || + string.Compare(path, "$MDB", StringComparison.InvariantCulture) == 0) + { + if(device.ImageInfo.readableSectorTags.Contains(ImagePlugins.SectorTagType.AppleSectorTag)) + xattrs.Add("com.apple.macintosh.tags"); + + return Errno.NoError; + } + } + + uint fileID; + MFS_FileEntry entry; + + if(!filenameToId.TryGetValue(path.ToLowerInvariant(), out fileID)) + return Errno.NoSuchFile; + + if(!idToEntry.TryGetValue(fileID, out entry)) + return Errno.NoSuchFile; + + if(entry.flRLgLen > 0) + { + xattrs.Add("com.apple.ResourceFork"); + if(debug && device.ImageInfo.readableSectorTags.Contains(ImagePlugins.SectorTagType.AppleSectorTag)) + xattrs.Add("com.apple.ResourceFork.tags"); + } + + if(!ArrayHelpers.ArrayIsNullOrEmpty(entry.flUsrWds)) + xattrs.Add("com.apple.FinderInfo"); + + if(debug && device.ImageInfo.readableSectorTags.Contains(ImagePlugins.SectorTagType.AppleSectorTag) && entry.flLgLen > 0) + xattrs.Add("com.apple.macintosh.tags"); + + xattrs.Sort(); + + return Errno.NoError; } public override Errno GetXattr(string path, string xattr, ref byte[] buf) { - return Errno.NotImplemented; + if(!mounted) + return Errno.AccessDenied; + + string[] pathElements = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if(pathElements.Length != 1) + return Errno.NotSupported; + + if(debug) + { + if(string.Compare(path, "$", StringComparison.InvariantCulture) == 0 || + string.Compare(path, "$Bitmap", StringComparison.InvariantCulture) == 0 || + string.Compare(path, "$Boot", StringComparison.InvariantCulture) == 0 || + string.Compare(path, "$MDB", StringComparison.InvariantCulture) == 0) + { + if(device.ImageInfo.readableSectorTags.Contains(ImagePlugins.SectorTagType.AppleSectorTag) && + string.Compare(xattr, "com.apple.macintosh.tags", StringComparison.InvariantCulture) == 0) + { + if(string.Compare(path, "$", StringComparison.InvariantCulture) == 0) + { + buf = new byte[directoryTags.Length]; + Array.Copy(directoryTags, 0, buf, 0, buf.Length); + return Errno.NoError; + } + + if(string.Compare(path, "$Bitmap", StringComparison.InvariantCulture) == 0) + { + buf = new byte[bitmapTags.Length]; + Array.Copy(bitmapTags, 0, buf, 0, buf.Length); + return Errno.NoError; + } + + if(string.Compare(path, "$Boot", StringComparison.InvariantCulture) == 0) + { + buf = new byte[bootTags.Length]; + Array.Copy(bootTags, 0, buf, 0, buf.Length); + return Errno.NoError; + } + + if(string.Compare(path, "$MDB", StringComparison.InvariantCulture) == 0) + { + buf = new byte[mdbTags.Length]; + Array.Copy(mdbTags, 0, buf, 0, buf.Length); + return Errno.NoError; + } + } + else + return Errno.NoSuchExtendedAttribute; + } + } + + uint fileID; + MFS_FileEntry entry; + Errno error; + + if(!filenameToId.TryGetValue(path.ToLowerInvariant(), out fileID)) + return Errno.NoSuchFile; + + if(!idToEntry.TryGetValue(fileID, out entry)) + return Errno.NoSuchFile; + + if(entry.flRLgLen > 0 && string.Compare(xattr, "com.apple.ResourceFork", StringComparison.InvariantCulture) == 0) + { + error = ReadFile(path, out buf, true, false); + return error; + } + + if(entry.flRLgLen > 0 && string.Compare(xattr, "com.apple.ResourceFork.tags", StringComparison.InvariantCulture) == 0) + { + error = ReadFile(path, out buf, true, true); + return error; + } + + if(!ArrayHelpers.ArrayIsNullOrEmpty(entry.flUsrWds) && string.Compare(xattr, "com.apple.FinderInfo", StringComparison.InvariantCulture) == 0) + { + buf = new byte[16]; + Array.Copy(entry.flUsrWds, 0, buf, 0, 16); + return Errno.NoError; + } + + if(debug && device.ImageInfo.readableSectorTags.Contains(ImagePlugins.SectorTagType.AppleSectorTag) && + string.Compare(xattr, "com.apple.macintosh.tags", StringComparison.InvariantCulture) == 0) + { + error = ReadFile(path, out buf, false, true); + return error; + } + + return Errno.NoSuchExtendedAttribute; } } } diff --git a/DiscImageChef.Filesystems/ChangeLog b/DiscImageChef.Filesystems/ChangeLog index 77504904f..2557b10fa 100644 --- a/DiscImageChef.Filesystems/ChangeLog +++ b/DiscImageChef.Filesystems/ChangeLog @@ -1,3 +1,15 @@ +2016-08-01 Natalia Portillo + + * Dir.cs: + * File.cs: + * Info.cs: + * Super.cs: + * Xattr.cs: + * Consts.cs: + * Structs.cs: + * AppleMFS.cs: Implemented missing methods, for full MFS + read-only support. + 2016-08-01 Natalia Portillo * File.cs: Correct block size arithmetic. diff --git a/README.md b/README.md index 950bc6cdb..8bea7e960 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 +* Apple Macintosh File System (MFS) * U.C.S.D Pascal file system Supported file systems for identification and information only