Esempio n. 1
0
        public void GetInformation(IMediaImage imagePlugin, Partition partition, out string information,
                                   Encoding encoding)
        {
            Encoding    = encoding ?? Encoding.GetEncoding("IBM437");
            information = "";

            var sb = new StringBuilder();

            XmlFsType = new FileSystemType();

            uint sectorsPerBpb = imagePlugin.Info.SectorSize < 512 ? 512 / imagePlugin.Info.SectorSize : 1;

            byte[] bpbSector = imagePlugin.ReadSectors(0 + partition.Start, sectorsPerBpb);

            BpbKind bpbKind = DetectBpbKind(bpbSector, imagePlugin, partition, out BiosParameterBlockEbpb fakeBpb,
                                            out HumanParameterBlock humanBpb, out AtariParameterBlock atariBpb,
                                            out byte minBootNearJump, out bool andosOemCorrect, out bool bootable);

            bool   isFat12             = false;
            bool   isFat16             = false;
            bool   isFat32             = false;
            ulong  rootDirectorySector = 0;
            string extraInfo           = null;
            string bootChk             = null;

            XmlFsType.Bootable = bootable;

            // This is needed because for FAT16, GEMDOS increases bytes per sector count instead of using big_sectors field.
            uint sectorsPerRealSector;

            // This is needed because some OSes don't put volume label as first entry in the root directory
            uint sectorsForRootDirectory = 0;

            switch (bpbKind)
            {
            case BpbKind.DecRainbow:
            case BpbKind.Hardcoded:
            case BpbKind.Msx:
            case BpbKind.Apricot:
                isFat12 = true;

                break;

            case BpbKind.ShortFat32:
            case BpbKind.LongFat32:
            {
                isFat32 = true;

                Fat32ParameterBlock fat32Bpb =
                    Marshal.ByteArrayToStructureLittleEndian <Fat32ParameterBlock>(bpbSector);

                Fat32ParameterBlockShort shortFat32Bpb =
                    Marshal.ByteArrayToStructureLittleEndian <Fat32ParameterBlockShort>(bpbSector);

                // This is to support FAT partitions on hybrid ISO/USB images
                if (imagePlugin.Info.XmlMediaType == XmlMediaType.OpticalDisc)
                {
                    fat32Bpb.bps       *= 4;
                    fat32Bpb.spc       /= 4;
                    fat32Bpb.big_spfat /= 4;
                    fat32Bpb.hsectors  /= 4;
                    fat32Bpb.sptrk     /= 4;
                }

                if (fat32Bpb.version != 0)
                {
                    sb.AppendLine("FAT+");
                    XmlFsType.Type = "FAT+";
                }
                else
                {
                    sb.AppendLine("Microsoft FAT32");
                    XmlFsType.Type = "FAT32";
                }

                if (fat32Bpb.oem_name != null)
                {
                    if (fat32Bpb.oem_name[5] == 0x49 &&
                        fat32Bpb.oem_name[6] == 0x48 &&
                        fat32Bpb.oem_name[7] == 0x43)
                    {
                        sb.AppendLine("Volume has been modified by Windows 9x/Me Volume Tracker.");
                    }
                    else
                    {
                        XmlFsType.SystemIdentifier = StringHandlers.CToString(fat32Bpb.oem_name);
                    }
                }

                if (!string.IsNullOrEmpty(XmlFsType.SystemIdentifier))
                {
                    sb.AppendFormat("OEM Name: {0}", XmlFsType.SystemIdentifier.Trim()).AppendLine();
                }

                sb.AppendFormat("{0} bytes per sector.", fat32Bpb.bps).AppendLine();
                sb.AppendFormat("{0} sectors per cluster.", fat32Bpb.spc).AppendLine();
                XmlFsType.ClusterSize = (uint)(fat32Bpb.bps * fat32Bpb.spc);
                sb.AppendFormat("{0} sectors reserved between BPB and FAT.", fat32Bpb.rsectors).AppendLine();

                if (fat32Bpb.big_sectors == 0 &&
                    fat32Bpb.signature == 0x28)
                {
                    sb.AppendFormat("{0} sectors on volume ({1} bytes).", shortFat32Bpb.huge_sectors,
                                    shortFat32Bpb.huge_sectors * shortFat32Bpb.bps).AppendLine();

                    XmlFsType.Clusters = shortFat32Bpb.huge_sectors / shortFat32Bpb.spc;
                }
                else
                {
                    sb.AppendFormat("{0} sectors on volume ({1} bytes).", fat32Bpb.big_sectors,
                                    fat32Bpb.big_sectors * fat32Bpb.bps).AppendLine();

                    XmlFsType.Clusters = fat32Bpb.big_sectors / fat32Bpb.spc;
                }

                sb.AppendFormat("{0} clusters on volume.", XmlFsType.Clusters).AppendLine();
                sb.AppendFormat("Media descriptor: 0x{0:X2}", fat32Bpb.media).AppendLine();
                sb.AppendFormat("{0} sectors per FAT.", fat32Bpb.big_spfat).AppendLine();
                sb.AppendFormat("{0} sectors per track.", fat32Bpb.sptrk).AppendLine();
                sb.AppendFormat("{0} heads.", fat32Bpb.heads).AppendLine();
                sb.AppendFormat("{0} hidden sectors before BPB.", fat32Bpb.hsectors).AppendLine();
                sb.AppendFormat("Cluster of root directory: {0}", fat32Bpb.root_cluster).AppendLine();
                sb.AppendFormat("Sector of FSINFO structure: {0}", fat32Bpb.fsinfo_sector).AppendLine();
                sb.AppendFormat("Sector of backup FAT32 parameter block: {0}", fat32Bpb.backup_sector).AppendLine();
                sb.AppendFormat("Drive number: 0x{0:X2}", fat32Bpb.drive_no).AppendLine();
                sb.AppendFormat("Volume Serial Number: 0x{0:X8}", fat32Bpb.serial_no).AppendLine();
                XmlFsType.VolumeSerial = $"{fat32Bpb.serial_no:X8}";

                if ((fat32Bpb.flags & 0xF8) == 0x00)
                {
                    if ((fat32Bpb.flags & 0x01) == 0x01)
                    {
                        sb.AppendLine("Volume should be checked on next mount.");
                        XmlFsType.Dirty = true;
                    }

                    if ((fat32Bpb.flags & 0x02) == 0x02)
                    {
                        sb.AppendLine("Disk surface should be on next mount.");
                    }
                }

                if ((fat32Bpb.mirror_flags & 0x80) == 0x80)
                {
                    sb.AppendFormat("FATs are out of sync. FAT #{0} is in use.", fat32Bpb.mirror_flags & 0xF).
                    AppendLine();
                }
                else
                {
                    sb.AppendLine("All copies of FAT are the same.");
                }

                if ((fat32Bpb.mirror_flags & 0x6F20) == 0x6F20)
                {
                    sb.AppendLine("DR-DOS will boot this FAT32 using CHS.");
                }
                else if ((fat32Bpb.mirror_flags & 0x4F20) == 0x4F20)
                {
                    sb.AppendLine("DR-DOS will boot this FAT32 using LBA.");
                }

                if (fat32Bpb.signature == 0x29)
                {
                    XmlFsType.VolumeName = Encoding.ASCII.GetString(fat32Bpb.volume_label);

                    sb.AppendFormat("Filesystem type: {0}", Encoding.ASCII.GetString(fat32Bpb.fs_type)).
                    AppendLine();

                    bootChk = Sha1Context.Data(fat32Bpb.boot_code, out _);
                }
                else
                {
                    bootChk = Sha1Context.Data(shortFat32Bpb.boot_code, out _);
                }

                // Check that jumps to a correct boot code position and has boot signature set.
                // This will mean that the volume will boot, even if just to say "this is not bootable change disk"......
                XmlFsType.Bootable =
                    (fat32Bpb.jump[0] == 0xEB && fat32Bpb.jump[1] >= minBootNearJump && fat32Bpb.jump[1] < 0x80) ||
                    (fat32Bpb.jump[0] == 0xE9 && fat32Bpb.jump.Length >= 3 &&
                     BitConverter.ToUInt16(fat32Bpb.jump, 1) >= minBootNearJump &&
                     BitConverter.ToUInt16(fat32Bpb.jump, 1) <= 0x1FC);

                sectorsPerRealSector = fat32Bpb.bps / imagePlugin.Info.SectorSize;

                // First root directory sector
                rootDirectorySector =
                    (ulong)(((fat32Bpb.root_cluster - 2) * fat32Bpb.spc) + (fat32Bpb.big_spfat * fat32Bpb.fats_no) +
                            fat32Bpb.rsectors) * sectorsPerRealSector;

                sectorsForRootDirectory = 1;

                if (fat32Bpb.fsinfo_sector + partition.Start <= partition.End)
                {
                    byte[] fsinfoSector = imagePlugin.ReadSector(fat32Bpb.fsinfo_sector + partition.Start);

                    FsInfoSector fsInfo = Marshal.ByteArrayToStructureLittleEndian <FsInfoSector>(fsinfoSector);

                    if (fsInfo.signature1 == FSINFO_SIGNATURE1 &&
                        fsInfo.signature2 == FSINFO_SIGNATURE2 &&
                        fsInfo.signature3 == FSINFO_SIGNATURE3)
                    {
                        if (fsInfo.free_clusters < 0xFFFFFFFF)
                        {
                            sb.AppendFormat("{0} free clusters", fsInfo.free_clusters).AppendLine();
                            XmlFsType.FreeClusters          = fsInfo.free_clusters;
                            XmlFsType.FreeClustersSpecified = true;
                        }

                        if (fsInfo.last_cluster > 2 &&
                            fsInfo.last_cluster < 0xFFFFFFFF)
                        {
                            sb.AppendFormat("Last allocated cluster {0}", fsInfo.last_cluster).AppendLine();
                        }
                    }
                }

                break;
            }

            // Some fields could overflow fake BPB, those will be handled below
            case BpbKind.Atari:
            {
                ushort sum = 0;

                for (int i = 0; i < bpbSector.Length; i += 2)
                {
                    sum += BigEndianBitConverter.ToUInt16(bpbSector, i);
                }

                // TODO: Check this
                if (sum == 0x1234)
                {
                    XmlFsType.Bootable = true;
                    var atariSb = new StringBuilder();

                    atariSb.AppendFormat("cmdload will be loaded with value {0:X4}h",
                                         BigEndianBitConverter.ToUInt16(bpbSector, 0x01E)).AppendLine();

                    atariSb.AppendFormat("Boot program will be loaded at address {0:X4}h", atariBpb.ldaaddr).
                    AppendLine();

                    atariSb.AppendFormat("FAT and directory will be cached at address {0:X4}h", atariBpb.fatbuf).
                    AppendLine();

                    if (atariBpb.ldmode == 0)
                    {
                        byte[] tmp = new byte[8];
                        Array.Copy(atariBpb.fname, 0, tmp, 0, 8);
                        string fname = Encoding.ASCII.GetString(tmp).Trim();
                        tmp = new byte[3];
                        Array.Copy(atariBpb.fname, 8, tmp, 0, 3);
                        string extension = Encoding.ASCII.GetString(tmp).Trim();
                        string filename;

                        if (string.IsNullOrEmpty(extension))
                        {
                            filename = fname;
                        }
                        else
                        {
                            filename = fname + "." + extension;
                        }

                        atariSb.AppendFormat("Boot program resides in file \"{0}\"", filename).AppendLine();
                    }
                    else
                    {
                        atariSb.
                        AppendFormat("Boot program starts in sector {0} and is {1} sectors long ({2} bytes)",
                                     atariBpb.ssect, atariBpb.sectcnt, atariBpb.sectcnt * atariBpb.bps).
                        AppendLine();
                    }

                    extraInfo = atariSb.ToString();
                }

                break;
            }

            case BpbKind.Human:
                XmlFsType.Bootable = true;

                break;
            }

            if (!isFat32)
            {
                // This is to support FAT partitions on hybrid ISO/USB images
                if (imagePlugin.Info.XmlMediaType == XmlMediaType.OpticalDisc)
                {
                    fakeBpb.bps      *= 4;
                    fakeBpb.spc      /= 4;
                    fakeBpb.spfat    /= 4;
                    fakeBpb.hsectors /= 4;
                    fakeBpb.sptrk    /= 4;
                    fakeBpb.rsectors /= 4;

                    if (fakeBpb.spc == 0)
                    {
                        fakeBpb.spc = 1;
                    }
                }

                // This assumes no sane implementation will violate cluster size rules
                // However nothing prevents this to happen
                // If first file on disk uses only one cluster there is absolutely no way to differentiate between FAT12 and FAT16,
                // so let's hope implementations use common sense?
                if (!isFat12 &&
                    !isFat16)
                {
                    ulong clusters;

                    if (fakeBpb.sectors == 0)
                    {
                        clusters = fakeBpb.spc == 0 ? fakeBpb.big_sectors : fakeBpb.big_sectors / fakeBpb.spc;
                    }
                    else
                    {
                        clusters = fakeBpb.spc == 0 ? fakeBpb.sectors : (ulong)fakeBpb.sectors / fakeBpb.spc;
                    }

                    if (clusters < 4089)
                    {
                        isFat12 = true;
                    }
                    else
                    {
                        isFat16 = true;
                    }
                }

                if (isFat12)
                {
                    switch (bpbKind)
                    {
                    case BpbKind.Atari:
                        sb.AppendLine("Atari FAT12");

                        break;

                    case BpbKind.Apricot:
                        sb.AppendLine("Apricot FAT12");

                        break;

                    case BpbKind.Human:
                        sb.AppendLine("Human68k FAT12");

                        break;

                    default:
                        sb.AppendLine("Microsoft FAT12");

                        break;
                    }

                    XmlFsType.Type = "FAT12";
                }
                else if (isFat16)
                {
                    sb.AppendLine(bpbKind == BpbKind.Atari
                                      ? "Atari FAT16"
                                      : bpbKind == BpbKind.Human
                                          ? "Human68k FAT16"
                                          : "Microsoft FAT16");

                    XmlFsType.Type = "FAT16";
                }

                if (bpbKind == BpbKind.Atari)
                {
                    if (atariBpb.serial_no[0] == 0x49 &&
                        atariBpb.serial_no[1] == 0x48 &&
                        atariBpb.serial_no[2] == 0x43)
                    {
                        sb.AppendLine("Volume has been modified by Windows 9x/Me Volume Tracker.");
                    }
                    else
                    {
                        XmlFsType.VolumeSerial =
                            $"{atariBpb.serial_no[0]:X2}{atariBpb.serial_no[1]:X2}{atariBpb.serial_no[2]:X2}";
                    }

                    XmlFsType.SystemIdentifier = StringHandlers.CToString(atariBpb.oem_name);

                    if (string.IsNullOrEmpty(XmlFsType.SystemIdentifier))
                    {
                        XmlFsType.SystemIdentifier = null;
                    }
                }
                else if (fakeBpb.oem_name != null)
                {
                    if (fakeBpb.oem_name[5] == 0x49 &&
                        fakeBpb.oem_name[6] == 0x48 &&
                        fakeBpb.oem_name[7] == 0x43)
                    {
                        sb.AppendLine("Volume has been modified by Windows 9x/Me Volume Tracker.");
                    }
                    else
                    {
                        // Later versions of Windows create a DOS 3 BPB without OEM name on 8 sectors/track floppies
                        // OEM ID should be ASCII, otherwise ignore it
                        if (fakeBpb.oem_name[0] >= 0x20 &&
                            fakeBpb.oem_name[0] <= 0x7F &&
                            fakeBpb.oem_name[1] >= 0x20 &&
                            fakeBpb.oem_name[1] <= 0x7F &&
                            fakeBpb.oem_name[2] >= 0x20 &&
                            fakeBpb.oem_name[2] <= 0x7F &&
                            fakeBpb.oem_name[3] >= 0x20 &&
                            fakeBpb.oem_name[3] <= 0x7F &&
                            fakeBpb.oem_name[4] >= 0x20 &&
                            fakeBpb.oem_name[4] <= 0x7F &&
                            fakeBpb.oem_name[5] >= 0x20 &&
                            fakeBpb.oem_name[5] <= 0x7F &&
                            fakeBpb.oem_name[6] >= 0x20 &&
                            fakeBpb.oem_name[6] <= 0x7F &&
                            fakeBpb.oem_name[7] >= 0x20 &&
                            fakeBpb.oem_name[7] <= 0x7F)
                        {
                            XmlFsType.SystemIdentifier = StringHandlers.CToString(fakeBpb.oem_name);
                        }
                        else if (fakeBpb.oem_name[0] < 0x20 &&
                                 fakeBpb.oem_name[1] >= 0x20 &&
                                 fakeBpb.oem_name[1] <= 0x7F &&
                                 fakeBpb.oem_name[2] >= 0x20 &&
                                 fakeBpb.oem_name[2] <= 0x7F &&
                                 fakeBpb.oem_name[3] >= 0x20 &&
                                 fakeBpb.oem_name[3] <= 0x7F &&
                                 fakeBpb.oem_name[4] >= 0x20 &&
                                 fakeBpb.oem_name[4] <= 0x7F &&
                                 fakeBpb.oem_name[5] >= 0x20 &&
                                 fakeBpb.oem_name[5] <= 0x7F &&
                                 fakeBpb.oem_name[6] >= 0x20 &&
                                 fakeBpb.oem_name[6] <= 0x7F &&
                                 fakeBpb.oem_name[7] >= 0x20 &&
                                 fakeBpb.oem_name[7] <= 0x7F)
                        {
                            XmlFsType.SystemIdentifier = StringHandlers.CToString(fakeBpb.oem_name, Encoding, start: 1);
                        }
                    }

                    if (fakeBpb.signature == 0x28 ||
                        fakeBpb.signature == 0x29)
                    {
                        XmlFsType.VolumeSerial = $"{fakeBpb.serial_no:X8}";
                    }
                }

                if (XmlFsType.SystemIdentifier != null)
                {
                    sb.AppendFormat("OEM Name: {0}", XmlFsType.SystemIdentifier.Trim()).AppendLine();
                }

                sb.AppendFormat("{0} bytes per sector.", fakeBpb.bps).AppendLine();

                if (bpbKind != BpbKind.Human)
                {
                    if (fakeBpb.sectors == 0)
                    {
                        sb.AppendFormat("{0} sectors on volume ({1} bytes).", fakeBpb.big_sectors,
                                        fakeBpb.big_sectors * fakeBpb.bps).AppendLine();

                        XmlFsType.Clusters = fakeBpb.spc == 0 ? fakeBpb.big_sectors : fakeBpb.big_sectors / fakeBpb.spc;
                    }
                    else
                    {
                        sb.AppendFormat("{0} sectors on volume ({1} bytes).", fakeBpb.sectors,
                                        fakeBpb.sectors * fakeBpb.bps).AppendLine();

                        XmlFsType.Clusters =
                            (ulong)(fakeBpb.spc == 0 ? fakeBpb.sectors : fakeBpb.sectors / fakeBpb.spc);
                    }
                }
                else
                {
                    XmlFsType.Clusters = humanBpb.clusters == 0 ? humanBpb.big_clusters : humanBpb.clusters;

                    sb.AppendFormat("{0} sectors on volume ({1} bytes).",
                                    (XmlFsType.Clusters * humanBpb.bpc) / imagePlugin.Info.SectorSize,
                                    XmlFsType.Clusters * humanBpb.bpc).AppendLine();
                }

                sb.AppendFormat("{0} sectors per cluster.", fakeBpb.spc).AppendLine();
                sb.AppendFormat("{0} clusters on volume.", XmlFsType.Clusters).AppendLine();
                XmlFsType.ClusterSize = (uint)(fakeBpb.bps * fakeBpb.spc);
                sb.AppendFormat("{0} sectors reserved between BPB and FAT.", fakeBpb.rsectors).AppendLine();
                sb.AppendFormat("{0} FATs.", fakeBpb.fats_no).AppendLine();
                sb.AppendFormat("{0} entries on root directory.", fakeBpb.root_ent).AppendLine();

                if (fakeBpb.media > 0)
                {
                    sb.AppendFormat("Media descriptor: 0x{0:X2}", fakeBpb.media).AppendLine();
                }

                sb.AppendFormat("{0} sectors per FAT.", fakeBpb.spfat).AppendLine();

                if (fakeBpb.sptrk > 0 &&
                    fakeBpb.sptrk < 64 &&
                    fakeBpb.heads > 0 &&
                    fakeBpb.heads < 256)
                {
                    sb.AppendFormat("{0} sectors per track.", fakeBpb.sptrk).AppendLine();
                    sb.AppendFormat("{0} heads.", fakeBpb.heads).AppendLine();
                }

                if (fakeBpb.hsectors <= partition.Start)
                {
                    sb.AppendFormat("{0} hidden sectors before BPB.", fakeBpb.hsectors).AppendLine();
                }

                if (fakeBpb.signature == 0x28 ||
                    fakeBpb.signature == 0x29 ||
                    andosOemCorrect)
                {
                    sb.AppendFormat("Drive number: 0x{0:X2}", fakeBpb.drive_no).AppendLine();

                    if (XmlFsType.VolumeSerial != null)
                    {
                        sb.AppendFormat("Volume Serial Number: {0}", XmlFsType.VolumeSerial).AppendLine();
                    }

                    if ((fakeBpb.flags & 0xF8) == 0x00)
                    {
                        if ((fakeBpb.flags & 0x01) == 0x01)
                        {
                            sb.AppendLine("Volume should be checked on next mount.");
                            XmlFsType.Dirty = true;
                        }

                        if ((fakeBpb.flags & 0x02) == 0x02)
                        {
                            sb.AppendLine("Disk surface should be on next mount.");
                        }
                    }

                    if (fakeBpb.signature == 0x29 || andosOemCorrect)
                    {
                        XmlFsType.VolumeName = Encoding.ASCII.GetString(fakeBpb.volume_label);
                        sb.AppendFormat("Filesystem type: {0}", Encoding.ASCII.GetString(fakeBpb.fs_type)).AppendLine();
                    }
                }
                else if (bpbKind == BpbKind.Atari &&
                         XmlFsType.VolumeSerial != null)
                {
                    sb.AppendFormat("Volume Serial Number: {0}", XmlFsType.VolumeSerial).AppendLine();
                }

                bootChk = Sha1Context.Data(fakeBpb.boot_code, out _);

                // Workaround that PCExchange jumps into "FAT16   "...
                if (XmlFsType.SystemIdentifier == "PCX 2.0 ")
                {
                    fakeBpb.jump[1] += 8;
                }

                // Check that jumps to a correct boot code position and has boot signature set.
                // This will mean that the volume will boot, even if just to say "this is not bootable change disk"......
                if (XmlFsType.Bootable == false &&
                    fakeBpb.jump != null)
                {
                    XmlFsType.Bootable |=
                        (fakeBpb.jump[0] == 0xEB && fakeBpb.jump[1] >= minBootNearJump && fakeBpb.jump[1] < 0x80) ||
                        (fakeBpb.jump[0] == 0xE9 && fakeBpb.jump.Length >= 3 &&
                         BitConverter.ToUInt16(fakeBpb.jump, 1) >= minBootNearJump &&
                         BitConverter.ToUInt16(fakeBpb.jump, 1) <= 0x1FC);
                }

                sectorsPerRealSector = fakeBpb.bps / imagePlugin.Info.SectorSize;

                // First root directory sector
                rootDirectorySector =
                    (ulong)((fakeBpb.spfat * fakeBpb.fats_no) + fakeBpb.rsectors) * sectorsPerRealSector;

                sectorsForRootDirectory = (uint)((fakeBpb.root_ent * 32) / imagePlugin.Info.SectorSize);
            }

            if (extraInfo != null)
            {
                sb.Append(extraInfo);
            }

            if (rootDirectorySector + partition.Start < partition.End &&
                imagePlugin.Info.XmlMediaType != XmlMediaType.OpticalDisc)
            {
                byte[] rootDirectory =
                    imagePlugin.ReadSectors(rootDirectorySector + partition.Start, sectorsForRootDirectory);

                if (bpbKind == BpbKind.DecRainbow)
                {
                    var rootMs = new MemoryStream();

                    foreach (byte[] tmp in from ulong rootSector in new[]
                    {
                        0x17, 0x19, 0x1B, 0x1D, 0x1E, 0x20
                    } select imagePlugin.ReadSector(rootSector))
                    {
                        rootMs.Write(tmp, 0, tmp.Length);
                    }

                    rootDirectory = rootMs.ToArray();
                }

                for (int i = 0; i < rootDirectory.Length; i += 32)
                {
                    // Not a correct entry
                    if (rootDirectory[i] < DIRENT_MIN &&
                        rootDirectory[i] != DIRENT_E5)
                    {
                        continue;
                    }

                    // Deleted or subdirectory entry
                    if (rootDirectory[i] == DIRENT_SUBDIR ||
                        rootDirectory[i] == DIRENT_DELETED)
                    {
                        continue;
                    }

                    // Not a volume label
                    if (rootDirectory[i + 0x0B] != 0x08 &&
                        rootDirectory[i + 0x0B] != 0x28)
                    {
                        continue;
                    }

                    DirectoryEntry entry =
                        Marshal.ByteArrayToStructureLittleEndian <DirectoryEntry>(rootDirectory, i, 32);

                    byte[] fullname = new byte[11];
                    Array.Copy(entry.filename, 0, fullname, 0, 8);
                    Array.Copy(entry.extension, 0, fullname, 8, 3);
                    string volname = Encoding.GetString(fullname).Trim();

                    if (!string.IsNullOrEmpty(volname))
                    {
                        XmlFsType.VolumeName =
                            entry.caseinfo.HasFlag(CaseInfo.AllLowerCase) ? volname.ToLower() : volname;
                    }

                    if (entry.ctime > 0 &&
                        entry.cdate > 0)
                    {
                        XmlFsType.CreationDate = DateHandlers.DosToDateTime(entry.cdate, entry.ctime);

                        if (entry.ctime_ms > 0)
                        {
                            XmlFsType.CreationDate = XmlFsType.CreationDate.AddMilliseconds(entry.ctime_ms * 10);
                        }

                        XmlFsType.CreationDateSpecified = true;
                        sb.AppendFormat("Volume created on {0}", XmlFsType.CreationDate).AppendLine();
                    }

                    if (entry.mtime > 0 &&
                        entry.mdate > 0)
                    {
                        XmlFsType.ModificationDate          = DateHandlers.DosToDateTime(entry.mdate, entry.mtime);
                        XmlFsType.ModificationDateSpecified = true;
                        sb.AppendFormat("Volume last modified on {0}", XmlFsType.ModificationDate).AppendLine();
                    }

                    if (entry.adate > 0)
                    {
                        sb.AppendFormat("Volume last accessed on {0:d}", DateHandlers.DosToDateTime(entry.adate, 0)).
                        AppendLine();
                    }

                    break;
                }
            }

            if (!string.IsNullOrEmpty(XmlFsType.VolumeName))
            {
                sb.AppendFormat("Volume label: {0}", XmlFsType.VolumeName).AppendLine();
            }

            if (XmlFsType.Bootable)
            {
                // Intel short jump
                if (bpbSector[0] == 0xEB &&
                    bpbSector[1] < 0x80)
                {
                    int    sigSize  = bpbSector[510] == 0x55 && bpbSector[511] == 0xAA ? 2 : 0;
                    byte[] bootCode = new byte[512 - sigSize - bpbSector[1] - 2];
                    Array.Copy(bpbSector, bpbSector[1] + 2, bootCode, 0, bootCode.Length);
                    Sha1Context.Data(bootCode, out _);
                }

                // Intel big jump
                else if (bpbSector[0] == 0xE9 &&
                         BitConverter.ToUInt16(bpbSector, 1) < 0x1FC)
                {
                    int    sigSize  = bpbSector[510] == 0x55 && bpbSector[511] == 0xAA ? 2 : 0;
                    byte[] bootCode = new byte[512 - sigSize - BitConverter.ToUInt16(bpbSector, 1) - 3];
                    Array.Copy(bpbSector, BitConverter.ToUInt16(bpbSector, 1) + 3, bootCode, 0, bootCode.Length);
                    Sha1Context.Data(bootCode, out _);
                }

                sb.AppendLine("Volume is bootable");
                sb.AppendFormat("Boot code's SHA1: {0}", bootChk).AppendLine();
                string bootName = knownBootHashes.FirstOrDefault(t => t.hash == bootChk).name;

                if (string.IsNullOrWhiteSpace(bootName))
                {
                    sb.AppendLine("Unknown boot code.");
                }
                else
                {
                    sb.AppendFormat("Boot code corresponds to {0}", bootName).AppendLine();
                }
            }

            information = sb.ToString();
        }
Esempio n. 2
0
        public Errno Stat(string path, out FileEntryInfo stat)
        {
            stat = null;
            if (!mounted)
            {
                return(Errno.AccessDenied);
            }

            Errno err = GetFileEntry(path, out CompleteDirectoryEntry completeEntry);

            if (err != Errno.NoError)
            {
                return(err);
            }

            DirectoryEntry entry = completeEntry.Dirent;

            stat = new FileEntryInfo
            {
                Attributes   = new FileAttributes(),
                Blocks       = entry.size / bytesPerCluster,
                BlockSize    = bytesPerCluster,
                Length       = entry.size,
                Inode        = (ulong)(fat32 ? (entry.ea_handle << 16) + entry.start_cluster : entry.start_cluster),
                Links        = 1,
                CreationTime = DateHandlers.DosToDateTime(entry.cdate, entry.ctime)
            };

            if (@namespace != Namespace.Human)
            {
                stat.LastWriteTime = DateHandlers.DosToDateTime(entry.mdate, entry.mtime);
                stat.CreationTime  = stat.CreationTime?.AddMilliseconds(entry.ctime_ms * 10);
            }

            if (entry.size % bytesPerCluster > 0)
            {
                stat.Blocks++;
            }

            if (entry.attributes.HasFlag(FatAttributes.Subdirectory))
            {
                stat.Attributes |= FileAttributes.Directory;
                stat.Blocks      = fat32
                                  ? GetClusters((uint)((entry.ea_handle << 16) + entry.start_cluster)).Length
                                  : GetClusters(entry.start_cluster).Length;
                stat.Length = stat.Blocks * stat.BlockSize;
            }

            if (entry.attributes.HasFlag(FatAttributes.ReadOnly))
            {
                stat.Attributes |= FileAttributes.ReadOnly;
            }
            if (entry.attributes.HasFlag(FatAttributes.Hidden))
            {
                stat.Attributes |= FileAttributes.Hidden;
            }
            if (entry.attributes.HasFlag(FatAttributes.System))
            {
                stat.Attributes |= FileAttributes.System;
            }
            if (entry.attributes.HasFlag(FatAttributes.Archive))
            {
                stat.Attributes |= FileAttributes.Archive;
            }
            if (entry.attributes.HasFlag(FatAttributes.Device))
            {
                stat.Attributes |= FileAttributes.Device;
            }

            return(Errno.NoError);
        }
Esempio n. 3
0
        public bool Open(IFilter imageFilter)
        {
            Stream stream = imageFilter.GetDataForkStream();

            stream.Seek(0, SeekOrigin.Begin);

            byte[] hdr = new byte[133];

            stream.Read(hdr, 0, 133);
            header = new CopyQmHeader();
            IntPtr hdrPtr = Marshal.AllocHGlobal(133);

            Marshal.Copy(hdr, 0, hdrPtr, 133);
            header = (CopyQmHeader)Marshal.PtrToStructure(hdrPtr, typeof(CopyQmHeader));
            Marshal.FreeHGlobal(hdrPtr);

            DicConsole.DebugWriteLine("CopyQM plugin", "header.magic = 0x{0:X4}", header.magic);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.mark = 0x{0:X2}", header.mark);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorSize = {0}", header.sectorSize);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorPerCluster = {0}", header.sectorPerCluster);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.reservedSectors = {0}", header.reservedSectors);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.fatCopy = {0}", header.fatCopy);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.rootEntries = {0}", header.rootEntries);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.sectors = {0}", header.sectors);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.mediaType = 0x{0:X2}", header.mediaType);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsPerFat = {0}", header.sectorsPerFat);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsPerTrack = {0}", header.sectorsPerTrack);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.heads = {0}", header.heads);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.hidden = {0}", header.hidden);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsBig = {0}", header.sectorsBig);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.description = {0}", header.description);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.blind = {0}", header.blind);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.density = {0}", header.density);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.imageCylinders = {0}", header.imageCylinders);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.totalCylinders = {0}", header.totalCylinders);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.crc = 0x{0:X8}", header.crc);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.volumeLabel = {0}", header.volumeLabel);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.time = 0x{0:X4}", header.time);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.date = 0x{0:X4}", header.date);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.commentLength = {0}", header.commentLength);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.secbs = {0}", header.secbs);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.unknown = 0x{0:X4}", header.unknown);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.interleave = {0}", header.interleave);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.skew = {0}", header.skew);
            DicConsole.DebugWriteLine("CopyQM plugin", "header.drive = {0}", header.drive);

            byte[] cmt = new byte[header.commentLength];
            stream.Read(cmt, 0, header.commentLength);
            imageInfo.Comments = StringHandlers.CToString(cmt);
            decodedImage       = new MemoryStream();

            calculatedDataCrc = 0;

            while (stream.Position + 2 < stream.Length)
            {
                byte[] runLengthBytes = new byte[2];
                if (stream.Read(runLengthBytes, 0, 2) != 2)
                {
                    break;
                }

                short runLength = BitConverter.ToInt16(runLengthBytes, 0);

                if (runLength < 0)
                {
                    byte   repeatedByte  = (byte)stream.ReadByte();
                    byte[] repeatedArray = new byte[runLength * -1];
                    ArrayHelpers.ArrayFill(repeatedArray, repeatedByte);

                    for (int i = 0; i < runLength * -1; i++)
                    {
                        decodedImage.WriteByte(repeatedByte);
                        calculatedDataCrc = copyQmCrcTable[(repeatedByte ^ calculatedDataCrc) & 0x3F] ^
                                            (calculatedDataCrc >> 8);
                    }
                }
                else if (runLength > 0)
                {
                    byte[] nonRepeated = new byte[runLength];
                    stream.Read(nonRepeated, 0, runLength);
                    decodedImage.Write(nonRepeated, 0, runLength);

                    foreach (byte c in nonRepeated)
                    {
                        calculatedDataCrc = copyQmCrcTable[(c ^ calculatedDataCrc) & 0x3F] ^ (calculatedDataCrc >> 8);
                    }
                }
            }

            // In case there is omitted data
            long sectors = header.sectorsPerTrack * header.heads * header.totalCylinders;

            long fillingLen = sectors * header.sectorSize - decodedImage.Length;

            if (fillingLen > 0)
            {
                byte[] filling = new byte[fillingLen];
                ArrayHelpers.ArrayFill(filling, (byte)0xF6);
                decodedImage.Write(filling, 0, filling.Length);
            }

            int sum = 0;

            for (int i = 0; i < hdr.Length - 1; i++)
            {
                sum += hdr[i];
            }

            headerChecksumOk = ((-1 * sum) & 0xFF) == header.headerChecksum;

            DicConsole.DebugWriteLine("CopyQM plugin", "Calculated header checksum = 0x{0:X2}, {1}", (-1 * sum) & 0xFF,
                                      headerChecksumOk);
            DicConsole.DebugWriteLine("CopyQM plugin", "Calculated data CRC = 0x{0:X8}, {1}", calculatedDataCrc,
                                      calculatedDataCrc == header.crc);

            imageInfo.Application          = "CopyQM";
            imageInfo.CreationTime         = DateHandlers.DosToDateTime(header.date, header.time);
            imageInfo.LastModificationTime = imageInfo.CreationTime;
            imageInfo.MediaTitle           = header.volumeLabel;
            imageInfo.ImageSize            = (ulong)(stream.Length - 133 - header.commentLength);
            imageInfo.Sectors    = (ulong)sectors;
            imageInfo.SectorSize = header.sectorSize;

            imageInfo.MediaType =
                Geometry.GetMediaType(((ushort)header.totalCylinders, (byte)header.heads, header.sectorsPerTrack,
                                       (uint)header.sectorSize, MediaEncoding.MFM, false));

            switch (imageInfo.MediaType)
            {
            case MediaType.NEC_525_HD when header.drive == COPYQM_35_HD || header.drive == COPYQM_35_ED:
                imageInfo.MediaType = MediaType.NEC_35_HD_8;
                break;

            case MediaType.DOS_525_HD when header.drive == COPYQM_35_HD || header.drive == COPYQM_35_ED:
                imageInfo.MediaType = MediaType.NEC_35_HD_15;
                break;

            case MediaType.RX50 when header.drive == COPYQM_525_DD || header.drive == COPYQM_525_HD:
                imageInfo.MediaType = MediaType.ATARI_35_SS_DD;
                break;
            }

            imageInfo.XmlMediaType = XmlMediaType.BlockMedia;
            decodedDisk            = decodedImage.ToArray();

            decodedImage.Close();

            DicConsole.VerboseWriteLine("CopyQM image contains a disk of type {0}", imageInfo.MediaType);
            if (!string.IsNullOrEmpty(imageInfo.Comments))
            {
                DicConsole.VerboseWriteLine("CopyQM comments: {0}", imageInfo.Comments);
            }

            imageInfo.Heads           = header.heads;
            imageInfo.Cylinders       = header.totalCylinders;
            imageInfo.SectorsPerTrack = header.sectorsPerTrack;

            return(true);
        }
Esempio n. 4
0
        public void Open(string path)
        {
            var fs = new FileStream(path, FileMode.Open, FileAccess.Read);

            fs.Seek(0, SeekOrigin.Begin);

            byte[] hdrB = new byte[26];
            fs.Read(hdrB, 0, 26);
            _header = Marshal.ByteArrayToStructureBigEndian <Header>(hdrB);

            Entry[] entries = new Entry[_header.entries];

            for (int i = 0; i < _header.entries; i++)
            {
                byte[] entry = new byte[12];
                fs.Read(entry, 0, 12);
                entries[i] = Marshal.ByteArrayToStructureBigEndian <Entry>(entry);
            }

            _creationTime  = DateTime.UtcNow;
            _lastWriteTime = _creationTime;

            foreach (Entry entry in entries)
            {
                switch ((AppleSingleEntryID)entry.id)
                {
                case AppleSingleEntryID.DataFork:
                    _dataFork = entry;

                    break;

                case AppleSingleEntryID.FileDates:
                    fs.Seek(entry.offset, SeekOrigin.Begin);
                    byte[] datesB = new byte[16];
                    fs.Read(datesB, 0, 16);

                    FileDates dates = Marshal.ByteArrayToStructureBigEndian <FileDates>(datesB);

                    _creationTime  = DateHandlers.MacToDateTime(dates.creationDate);
                    _lastWriteTime = DateHandlers.MacToDateTime(dates.modificationDate);

                    break;

                case AppleSingleEntryID.FileInfo:
                    fs.Seek(entry.offset, SeekOrigin.Begin);
                    byte[] finfo = new byte[entry.length];
                    fs.Read(finfo, 0, finfo.Length);

                    if (_macintoshHome.SequenceEqual(_header.homeFilesystem))
                    {
                        MacFileInfo macinfo = Marshal.ByteArrayToStructureBigEndian <MacFileInfo>(finfo);

                        _creationTime  = DateHandlers.MacToDateTime(macinfo.creationDate);
                        _lastWriteTime = DateHandlers.MacToDateTime(macinfo.modificationDate);
                    }
                    else if (_proDosHome.SequenceEqual(_header.homeFilesystem))
                    {
                        ProDOSFileInfo prodosinfo = Marshal.ByteArrayToStructureBigEndian <ProDOSFileInfo>(finfo);

                        _creationTime  = DateHandlers.MacToDateTime(prodosinfo.creationDate);
                        _lastWriteTime = DateHandlers.MacToDateTime(prodosinfo.modificationDate);
                    }
                    else if (_unixHome.SequenceEqual(_header.homeFilesystem))
                    {
                        UnixFileInfo unixinfo = Marshal.ByteArrayToStructureBigEndian <UnixFileInfo>(finfo);

                        _creationTime  = DateHandlers.UnixUnsignedToDateTime(unixinfo.creationDate);
                        _lastWriteTime = DateHandlers.UnixUnsignedToDateTime(unixinfo.modificationDate);
                    }
                    else if (_dosHome.SequenceEqual(_header.homeFilesystem))
                    {
                        DOSFileInfo dosinfo = Marshal.ByteArrayToStructureBigEndian <DOSFileInfo>(finfo);

                        _lastWriteTime =
                            DateHandlers.DosToDateTime(dosinfo.modificationDate, dosinfo.modificationTime);
                    }

                    break;

                case AppleSingleEntryID.ResourceFork:
                    _rsrcFork = entry;

                    break;
                }
            }

            fs.Close();
            _opened   = true;
            _isPath   = true;
            _basePath = path;
        }
Esempio n. 5
0
        public void Open(string path)
        {
            FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

            fs.Seek(0, SeekOrigin.Begin);

            byte[] hdr_b = new byte[26];
            fs.Read(hdr_b, 0, 26);
            header = Marshal.ByteArrayToStructureBigEndian <AppleSingleHeader>(hdr_b);

            AppleSingleEntry[] entries = new AppleSingleEntry[header.entries];
            for (int i = 0; i < header.entries; i++)
            {
                byte[] entry = new byte[12];
                fs.Read(entry, 0, 12);
                entries[i] = Marshal.ByteArrayToStructureBigEndian <AppleSingleEntry>(entry);
            }

            creationTime  = DateTime.UtcNow;
            lastWriteTime = creationTime;
            foreach (AppleSingleEntry entry in entries)
            {
                switch ((AppleSingleEntryID)entry.id)
                {
                case AppleSingleEntryID.DataFork:
                    dataFork = entry;
                    break;

                case AppleSingleEntryID.FileDates:
                    fs.Seek(entry.offset, SeekOrigin.Begin);
                    byte[] dates_b = new byte[16];
                    fs.Read(dates_b, 0, 16);
                    AppleSingleFileDates dates =
                        Marshal.ByteArrayToStructureBigEndian <AppleSingleFileDates>(dates_b);
                    creationTime  = DateHandlers.MacToDateTime(dates.creationDate);
                    lastWriteTime = DateHandlers.MacToDateTime(dates.modificationDate);
                    break;

                case AppleSingleEntryID.FileInfo:
                    fs.Seek(entry.offset, SeekOrigin.Begin);
                    byte[] finfo = new byte[entry.length];
                    fs.Read(finfo, 0, finfo.Length);
                    if (MacintoshHome.SequenceEqual(header.homeFilesystem))
                    {
                        AppleSingleMacFileInfo macinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleSingleMacFileInfo>(finfo);
                        creationTime  = DateHandlers.MacToDateTime(macinfo.creationDate);
                        lastWriteTime = DateHandlers.MacToDateTime(macinfo.modificationDate);
                    }
                    else if (ProDOSHome.SequenceEqual(header.homeFilesystem))
                    {
                        AppleSingleProDOSFileInfo prodosinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleSingleProDOSFileInfo>(finfo);
                        creationTime  = DateHandlers.MacToDateTime(prodosinfo.creationDate);
                        lastWriteTime = DateHandlers.MacToDateTime(prodosinfo.modificationDate);
                    }
                    else if (UNIXHome.SequenceEqual(header.homeFilesystem))
                    {
                        AppleSingleUNIXFileInfo unixinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleSingleUNIXFileInfo>(finfo);
                        creationTime  = DateHandlers.UnixUnsignedToDateTime(unixinfo.creationDate);
                        lastWriteTime = DateHandlers.UnixUnsignedToDateTime(unixinfo.modificationDate);
                    }
                    else if (DOSHome.SequenceEqual(header.homeFilesystem))
                    {
                        AppleSingleDOSFileInfo dosinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleSingleDOSFileInfo>(finfo);
                        lastWriteTime =
                            DateHandlers.DosToDateTime(dosinfo.modificationDate, dosinfo.modificationTime);
                    }

                    break;

                case AppleSingleEntryID.ResourceFork:
                    rsrcFork = entry;
                    break;
                }
            }

            fs.Close();
            opened   = true;
            isPath   = true;
            basePath = path;
        }
Esempio n. 6
0
File: Info.cs Progetto: paulyc/Aaru
        public void GetInformation(IMediaImage imagePlugin, Partition partition, out string information,
                                   Encoding encoding)
        {
            Encoding    = encoding ?? Encoding.GetEncoding("ibm850");
            information = "";

            var sb = new StringBuilder();

            byte[] hpofsBpbSector =
                imagePlugin.ReadSector(0 + partition.Start); // Seek to BIOS parameter block, on logical sector 0

            byte[] medInfoSector =
                imagePlugin.ReadSector(13 + partition.Start); // Seek to media information block, on logical sector 13

            byte[] volInfoSector =
                imagePlugin.ReadSector(14 + partition.Start); // Seek to volume information block, on logical sector 14

            BiosParameterBlock     bpb = Marshal.ByteArrayToStructureLittleEndian <BiosParameterBlock>(hpofsBpbSector);
            MediaInformationBlock  mib = Marshal.ByteArrayToStructureBigEndian <MediaInformationBlock>(medInfoSector);
            VolumeInformationBlock vib = Marshal.ByteArrayToStructureBigEndian <VolumeInformationBlock>(volInfoSector);

            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.oem_name = \"{0}\"",
                                       StringHandlers.CToString(bpb.oem_name));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.bps = {0}", bpb.bps);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.spc = {0}", bpb.spc);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.rsectors = {0}", bpb.rsectors);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.fats_no = {0}", bpb.fats_no);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.root_ent = {0}", bpb.root_ent);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.sectors = {0}", bpb.sectors);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.media = 0x{0:X2}", bpb.media);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.spfat = {0}", bpb.spfat);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.sptrk = {0}", bpb.sptrk);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.heads = {0}", bpb.heads);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.hsectors = {0}", bpb.hsectors);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.big_sectors = {0}", bpb.big_sectors);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.drive_no = 0x{0:X2}", bpb.drive_no);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.nt_flags = {0}", bpb.nt_flags);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.signature = 0x{0:X2}", bpb.signature);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.serial_no = 0x{0:X8}", bpb.serial_no);

            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.volume_label = \"{0}\"",
                                       StringHandlers.SpacePaddedToString(bpb.volume_label));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.fs_type = \"{0}\"", StringHandlers.CToString(bpb.fs_type));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.boot_code is empty? = {0}",
                                       ArrayHelpers.ArrayIsNullOrEmpty(bpb.boot_code));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.unknown = {0}", bpb.unknown);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.unknown2 = {0}", bpb.unknown2);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "bpb.signature2 = {0}", bpb.signature2);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.blockId = \"{0}\"", StringHandlers.CToString(mib.blockId));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.volumeLabel = \"{0}\"",
                                       StringHandlers.SpacePaddedToString(mib.volumeLabel));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.comment = \"{0}\"",
                                       StringHandlers.SpacePaddedToString(mib.comment));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.serial = 0x{0:X8}", mib.serial);

            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.creationTimestamp = {0}",
                                       DateHandlers.DosToDateTime(mib.creationDate, mib.creationTime));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.codepageType = {0}", mib.codepageType);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.codepage = {0}", mib.codepage);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.rps = {0}", mib.rps);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.bps = {0}", mib.bps);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.bpc = {0}", mib.bpc);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.unknown2 = {0}", mib.unknown2);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.sectors = {0}", mib.sectors);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.unknown3 = {0}", mib.unknown3);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.unknown4 = {0}", mib.unknown4);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.major = {0}", mib.major);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.minor = {0}", mib.minor);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.unknown5 = {0}", mib.unknown5);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.unknown6 = {0}", mib.unknown6);

            AaruConsole.DebugWriteLine("HPOFS Plugin", "mib.filler is empty? = {0}",
                                       ArrayHelpers.ArrayIsNullOrEmpty(mib.filler));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "vib.blockId = \"{0}\"", StringHandlers.CToString(vib.blockId));
            AaruConsole.DebugWriteLine("HPOFS Plugin", "vib.unknown = {0}", vib.unknown);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "vib.unknown2 = {0}", vib.unknown2);

            AaruConsole.DebugWriteLine("HPOFS Plugin", "vib.unknown3 is empty? = {0}",
                                       ArrayHelpers.ArrayIsNullOrEmpty(vib.unknown3));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "vib.unknown4 = \"{0}\"",
                                       StringHandlers.SpacePaddedToString(vib.unknown4));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "vib.owner = \"{0}\"",
                                       StringHandlers.SpacePaddedToString(vib.owner));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "vib.unknown5 = \"{0}\"",
                                       StringHandlers.SpacePaddedToString(vib.unknown5));

            AaruConsole.DebugWriteLine("HPOFS Plugin", "vib.unknown6 = {0}", vib.unknown6);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "vib.percentFull = {0}", vib.percentFull);
            AaruConsole.DebugWriteLine("HPOFS Plugin", "vib.unknown7 = {0}", vib.unknown7);

            AaruConsole.DebugWriteLine("HPOFS Plugin", "vib.filler is empty? = {0}",
                                       ArrayHelpers.ArrayIsNullOrEmpty(vib.filler));

            sb.AppendLine("High Performance Optical File System");
            sb.AppendFormat("OEM name: {0}", StringHandlers.SpacePaddedToString(bpb.oem_name)).AppendLine();
            sb.AppendFormat("{0} bytes per sector", bpb.bps).AppendLine();
            sb.AppendFormat("{0} sectors per cluster", bpb.spc).AppendLine();
            sb.AppendFormat("Media descriptor: 0x{0:X2}", bpb.media).AppendLine();
            sb.AppendFormat("{0} sectors per track", bpb.sptrk).AppendLine();
            sb.AppendFormat("{0} heads", bpb.heads).AppendLine();
            sb.AppendFormat("{0} sectors hidden before BPB", bpb.hsectors).AppendLine();
            sb.AppendFormat("{0} sectors on volume ({1} bytes)", mib.sectors, mib.sectors * bpb.bps).AppendLine();
            sb.AppendFormat("BIOS Drive Number: 0x{0:X2}", bpb.drive_no).AppendLine();
            sb.AppendFormat("Serial number: 0x{0:X8}", mib.serial).AppendLine();

            sb.AppendFormat("Volume label: {0}", StringHandlers.SpacePaddedToString(mib.volumeLabel, Encoding)).
            AppendLine();

            sb.AppendFormat("Volume comment: {0}", StringHandlers.SpacePaddedToString(mib.comment, Encoding)).
            AppendLine();

            sb.AppendFormat("Volume owner: {0}", StringHandlers.SpacePaddedToString(vib.owner, Encoding)).AppendLine();

            sb.AppendFormat("Volume created on {0}", DateHandlers.DosToDateTime(mib.creationDate, mib.creationTime)).
            AppendLine();

            sb.AppendFormat("Volume uses {0} codepage {1}", mib.codepageType > 0 && mib.codepageType < 3
                                                                ? mib.codepageType == 2
                                                                      ? "EBCDIC"
                                                                      : "ASCII" : "Unknown", mib.codepage).AppendLine();

            sb.AppendFormat("RPS level: {0}", mib.rps).AppendLine();
            sb.AppendFormat("Filesystem version: {0}.{1}", mib.major, mib.minor).AppendLine();
            sb.AppendFormat("Volume can be filled up to {0}%", vib.percentFull).AppendLine();

            XmlFsType = new FileSystemType
            {
                Clusters               = mib.sectors / bpb.spc,
                ClusterSize            = (uint)(bpb.bps * bpb.spc),
                CreationDate           = DateHandlers.DosToDateTime(mib.creationDate, mib.creationTime),
                CreationDateSpecified  = true,
                DataPreparerIdentifier = StringHandlers.SpacePaddedToString(vib.owner, Encoding),
                Type             = "HPOFS",
                VolumeName       = StringHandlers.SpacePaddedToString(mib.volumeLabel, Encoding),
                VolumeSerial     = $"{mib.serial:X8}",
                SystemIdentifier = StringHandlers.SpacePaddedToString(bpb.oem_name)
            };

            information = sb.ToString();
        }
Esempio n. 7
0
        public void Open(string path)
        {
            string filename      = Path.GetFileName(path);
            string filenameNoExt = Path.GetFileNameWithoutExtension(path);
            string parentFolder  = Path.GetDirectoryName(path);

            parentFolder ??= "";

            if (filename is null ||
                filenameNoExt is null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            // Prepend data fork name with "R."
            string proDosAppleDouble = Path.Combine(parentFolder, "R." + filename);

            // Prepend data fork name with '%'
            string unixAppleDouble = Path.Combine(parentFolder, "%" + filename);

            // Change file extension to ADF
            string dosAppleDouble = Path.Combine(parentFolder, filenameNoExt + ".ADF");

            // Change file extension to adf
            string dosAppleDoubleLower = Path.Combine(parentFolder, filenameNoExt + ".adf");

            // Store AppleDouble header file in ".AppleDouble" folder with same name
            string netatalkAppleDouble = Path.Combine(parentFolder, ".AppleDouble", filename);

            // Store AppleDouble header file in "resource.frk" folder with same name
            string daveAppleDouble = Path.Combine(parentFolder, "resource.frk", filename);

            // Prepend data fork name with "._"
            string osxAppleDouble = Path.Combine(parentFolder, "._" + filename);

            // Adds ".rsrc" extension
            string unArAppleDouble = Path.Combine(parentFolder, filename + ".rsrc");

            // Check AppleDouble created by A/UX in ProDOS filesystem
            if (File.Exists(proDosAppleDouble))
            {
                var prodosStream = new FileStream(proDosAppleDouble, FileMode.Open, FileAccess.Read);

                if (prodosStream.Length > 26)
                {
                    byte[] prodosB = new byte[26];
                    prodosStream.Read(prodosB, 0, 26);
                    _header = Marshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(prodosB);
                    prodosStream.Close();

                    if (_header.magic == MAGIC &&
                        (_header.version == VERSION || _header.version == VERSION2))
                    {
                        _headerPath = proDosAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by A/UX in UFS filesystem
            if (File.Exists(unixAppleDouble))
            {
                var unixStream = new FileStream(unixAppleDouble, FileMode.Open, FileAccess.Read);

                if (unixStream.Length > 26)
                {
                    byte[] unixB = new byte[26];
                    unixStream.Read(unixB, 0, 26);
                    _header = Marshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(unixB);
                    unixStream.Close();

                    if (_header.magic == MAGIC &&
                        (_header.version == VERSION || _header.version == VERSION2))
                    {
                        _headerPath = unixAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by A/UX in FAT filesystem
            if (File.Exists(dosAppleDouble))
            {
                var dosStream = new FileStream(dosAppleDouble, FileMode.Open, FileAccess.Read);

                if (dosStream.Length > 26)
                {
                    byte[] dosB = new byte[26];
                    dosStream.Read(dosB, 0, 26);
                    _header = Marshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(dosB);
                    dosStream.Close();

                    if (_header.magic == MAGIC &&
                        (_header.version == VERSION || _header.version == VERSION2))
                    {
                        _headerPath = dosAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by A/UX in case preserving FAT filesystem
            if (File.Exists(dosAppleDoubleLower))
            {
                var doslStream = new FileStream(dosAppleDoubleLower, FileMode.Open, FileAccess.Read);

                if (doslStream.Length > 26)
                {
                    byte[] doslB = new byte[26];
                    doslStream.Read(doslB, 0, 26);
                    _header = Marshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(doslB);
                    doslStream.Close();

                    if (_header.magic == MAGIC &&
                        (_header.version == VERSION || _header.version == VERSION2))
                    {
                        _headerPath = dosAppleDoubleLower;
                    }
                }
            }

            // Check AppleDouble created by Netatalk
            if (File.Exists(netatalkAppleDouble))
            {
                var netatalkStream = new FileStream(netatalkAppleDouble, FileMode.Open, FileAccess.Read);

                if (netatalkStream.Length > 26)
                {
                    byte[] netatalkB = new byte[26];
                    netatalkStream.Read(netatalkB, 0, 26);
                    _header = Marshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(netatalkB);
                    netatalkStream.Close();

                    if (_header.magic == MAGIC &&
                        (_header.version == VERSION || _header.version == VERSION2))
                    {
                        _headerPath = netatalkAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by DAVE
            if (File.Exists(daveAppleDouble))
            {
                var daveStream = new FileStream(daveAppleDouble, FileMode.Open, FileAccess.Read);

                if (daveStream.Length > 26)
                {
                    byte[] daveB = new byte[26];
                    daveStream.Read(daveB, 0, 26);
                    _header = Marshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(daveB);
                    daveStream.Close();

                    if (_header.magic == MAGIC &&
                        (_header.version == VERSION || _header.version == VERSION2))
                    {
                        _headerPath = daveAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by Mac OS X
            if (File.Exists(osxAppleDouble))
            {
                var osxStream = new FileStream(osxAppleDouble, FileMode.Open, FileAccess.Read);

                if (osxStream.Length > 26)
                {
                    byte[] osxB = new byte[26];
                    osxStream.Read(osxB, 0, 26);
                    _header = Marshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(osxB);
                    osxStream.Close();

                    if (_header.magic == MAGIC &&
                        (_header.version == VERSION || _header.version == VERSION2))
                    {
                        _headerPath = osxAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by UnAr (from The Unarchiver)
            if (File.Exists(unArAppleDouble))
            {
                var unarStream = new FileStream(unArAppleDouble, FileMode.Open, FileAccess.Read);

                if (unarStream.Length > 26)
                {
                    byte[] unarB = new byte[26];
                    unarStream.Read(unarB, 0, 26);
                    _header = Marshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(unarB);
                    unarStream.Close();

                    if (_header.magic == MAGIC &&
                        (_header.version == VERSION || _header.version == VERSION2))
                    {
                        _headerPath = unArAppleDouble;
                    }
                }
            }

            var fs = new FileStream(_headerPath, FileMode.Open, FileAccess.Read);

            fs.Seek(0, SeekOrigin.Begin);

            byte[] hdrB = new byte[26];
            fs.Read(hdrB, 0, 26);
            _header = Marshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(hdrB);

            AppleDoubleEntry[] entries = new AppleDoubleEntry[_header.entries];

            for (int i = 0; i < _header.entries; i++)
            {
                byte[] entry = new byte[12];
                fs.Read(entry, 0, 12);
                entries[i] = Marshal.ByteArrayToStructureBigEndian <AppleDoubleEntry>(entry);
            }

            _creationTime  = DateTime.UtcNow;
            _lastWriteTime = _creationTime;

            foreach (AppleDoubleEntry entry in entries)
            {
                switch ((AppleDoubleEntryID)entry.id)
                {
                case AppleDoubleEntryID.DataFork:
                    // AppleDouble have datafork in separated file
                    break;

                case AppleDoubleEntryID.FileDates:
                    fs.Seek(entry.offset, SeekOrigin.Begin);
                    byte[] datesB = new byte[16];
                    fs.Read(datesB, 0, 16);

                    AppleDoubleFileDates dates =
                        Marshal.ByteArrayToStructureBigEndian <AppleDoubleFileDates>(datesB);

                    _creationTime  = DateHandlers.UnixUnsignedToDateTime(dates.creationDate);
                    _lastWriteTime = DateHandlers.UnixUnsignedToDateTime(dates.modificationDate);

                    break;

                case AppleDoubleEntryID.FileInfo:
                    fs.Seek(entry.offset, SeekOrigin.Begin);
                    byte[] finfo = new byte[entry.length];
                    fs.Read(finfo, 0, finfo.Length);

                    if (_macintoshHome.SequenceEqual(_header.homeFilesystem))
                    {
                        AppleDoubleMacFileInfo macinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleDoubleMacFileInfo>(finfo);

                        _creationTime  = DateHandlers.MacToDateTime(macinfo.creationDate);
                        _lastWriteTime = DateHandlers.MacToDateTime(macinfo.modificationDate);
                    }
                    else if (_proDosHome.SequenceEqual(_header.homeFilesystem))
                    {
                        AppleDoubleProDOSFileInfo prodosinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleDoubleProDOSFileInfo>(finfo);

                        _creationTime  = DateHandlers.MacToDateTime(prodosinfo.creationDate);
                        _lastWriteTime = DateHandlers.MacToDateTime(prodosinfo.modificationDate);
                    }
                    else if (_unixHome.SequenceEqual(_header.homeFilesystem))
                    {
                        AppleDoubleUnixFileInfo unixinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleDoubleUnixFileInfo>(finfo);

                        _creationTime  = DateHandlers.UnixUnsignedToDateTime(unixinfo.creationDate);
                        _lastWriteTime = DateHandlers.UnixUnsignedToDateTime(unixinfo.modificationDate);
                    }
                    else if (_dosHome.SequenceEqual(_header.homeFilesystem))
                    {
                        AppleDoubleDOSFileInfo dosinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleDoubleDOSFileInfo>(finfo);

                        _lastWriteTime =
                            DateHandlers.DosToDateTime(dosinfo.modificationDate, dosinfo.modificationTime);
                    }

                    break;

                case AppleDoubleEntryID.ResourceFork:
                    _rsrcFork = entry;

                    break;
                }
            }

            _dataFork = new AppleDoubleEntry
            {
                id = (uint)AppleDoubleEntryID.DataFork
            };

            if (File.Exists(path))
            {
                var dataFs = new FileStream(path, FileMode.Open, FileAccess.Read);
                _dataFork.length = (uint)dataFs.Length;
                dataFs.Close();
            }

            fs.Close();
            _opened   = true;
            _basePath = path;
        }
Esempio n. 8
0
        public Errno Stat(string path, out FileEntryInfo stat)
        {
            stat = null;
            if (!mounted)
            {
                return(Errno.AccessDenied);
            }

            if (debug && (string.IsNullOrEmpty(path) || path == "$" || path == "/"))
            {
                stat = new FileEntryInfo
                {
                    Attributes = FileAttributes.Directory | FileAttributes.System | FileAttributes.Hidden,
                    Blocks     = GetClusters(superblock.rootDirectoryCluster).Length,
                    BlockSize  = bytesPerCluster,
                    Length     = GetClusters(superblock.rootDirectoryCluster).Length *bytesPerCluster,
                    Inode      = superblock.rootDirectoryCluster,
                    Links      = 1
                };

                return(Errno.NoError);
            }

            Errno err = GetFileEntry(path, out DirectoryEntry entry);

            if (err != Errno.NoError)
            {
                return(err);
            }

            stat = new FileEntryInfo
            {
                Attributes   = new FileAttributes(),
                Blocks       = entry.length / bytesPerCluster,
                BlockSize    = bytesPerCluster,
                Length       = entry.length,
                Inode        = entry.firstCluster,
                Links        = 1,
                CreationTime =
                    littleEndian
                        ? DateHandlers.DosToDateTime(entry.creationDate, entry.creationTime).AddYears(20)
                        : DateHandlers.DosToDateTime(entry.creationTime, entry.creationDate),
                AccessTime =
                    littleEndian
                        ? DateHandlers.DosToDateTime(entry.lastAccessDate, entry.lastAccessTime).AddYears(20)
                        : DateHandlers.DosToDateTime(entry.lastAccessTime, entry.lastAccessDate),
                LastWriteTime = littleEndian
                                    ? DateHandlers
                                .DosToDateTime(entry.lastWrittenDate, entry.lastWrittenTime).AddYears(20)
                                    : DateHandlers.DosToDateTime(entry.lastWrittenTime, entry.lastWrittenDate)
            };

            if (entry.length % bytesPerCluster > 0)
            {
                stat.Blocks++;
            }

            if (entry.attributes.HasFlag(Attributes.Directory))
            {
                stat.Attributes |= FileAttributes.Directory;
                stat.Blocks      = GetClusters(entry.firstCluster).Length;
                stat.Length      = stat.Blocks * stat.BlockSize;
            }

            if (entry.attributes.HasFlag(Attributes.ReadOnly))
            {
                stat.Attributes |= FileAttributes.ReadOnly;
            }
            if (entry.attributes.HasFlag(Attributes.Hidden))
            {
                stat.Attributes |= FileAttributes.Hidden;
            }
            if (entry.attributes.HasFlag(Attributes.System))
            {
                stat.Attributes |= FileAttributes.System;
            }
            if (entry.attributes.HasFlag(Attributes.Archive))
            {
                stat.Attributes |= FileAttributes.Archive;
            }

            return(Errno.NoError);
        }
Esempio n. 9
0
        public void Open(string path)
        {
            string filename      = Path.GetFileName(path);
            string filenameNoExt = Path.GetFileNameWithoutExtension(path);
            string parentFolder  = Path.GetDirectoryName(path);

            // Prepend data fork name with "R."
            string ProDosAppleDouble =
                Path.Combine(parentFolder ?? throw new InvalidOperationException(), "R." + filename);
            // Prepend data fork name with '%'
            string UNIXAppleDouble = Path.Combine(parentFolder, "%" + filename);
            // Change file extension to ADF
            string DOSAppleDouble = Path.Combine(parentFolder, filenameNoExt + ".ADF");
            // Change file extension to adf
            string DOSAppleDoubleLower = Path.Combine(parentFolder, filenameNoExt + ".adf");
            // Store AppleDouble header file in ".AppleDouble" folder with same name
            string NetatalkAppleDouble =
                Path.Combine(parentFolder, ".AppleDouble", filename ?? throw new InvalidOperationException());
            // Store AppleDouble header file in "resource.frk" folder with same name
            string DAVEAppleDouble = Path.Combine(parentFolder, "resource.frk", filename);
            // Prepend data fork name with "._"
            string OSXAppleDouble = Path.Combine(parentFolder, "._" + filename);
            // Adds ".rsrc" extension
            string UnArAppleDouble = Path.Combine(parentFolder, filename + ".rsrc");

            // Check AppleDouble created by A/UX in ProDOS filesystem
            if (File.Exists(ProDosAppleDouble))
            {
                FileStream prodosStream = new FileStream(ProDosAppleDouble, FileMode.Open, FileAccess.Read);
                if (prodosStream.Length > 26)
                {
                    byte[] prodos_b = new byte[26];
                    prodosStream.Read(prodos_b, 0, 26);
                    header = BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(prodos_b);
                    prodosStream.Close();
                    if (header.magic == AppleDoubleMagic &&
                        (header.version == AppleDoubleVersion || header.version == AppleDoubleVersion2))
                    {
                        headerPath = ProDosAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by A/UX in UFS filesystem
            if (File.Exists(UNIXAppleDouble))
            {
                FileStream unixStream = new FileStream(UNIXAppleDouble, FileMode.Open, FileAccess.Read);
                if (unixStream.Length > 26)
                {
                    byte[] unix_b = new byte[26];
                    unixStream.Read(unix_b, 0, 26);
                    header = BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(unix_b);
                    unixStream.Close();
                    if (header.magic == AppleDoubleMagic &&
                        (header.version == AppleDoubleVersion || header.version == AppleDoubleVersion2))
                    {
                        headerPath = UNIXAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by A/UX in FAT filesystem
            if (File.Exists(DOSAppleDouble))
            {
                FileStream dosStream = new FileStream(DOSAppleDouble, FileMode.Open, FileAccess.Read);
                if (dosStream.Length > 26)
                {
                    byte[] dos_b = new byte[26];
                    dosStream.Read(dos_b, 0, 26);
                    header = BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(dos_b);
                    dosStream.Close();
                    if (header.magic == AppleDoubleMagic &&
                        (header.version == AppleDoubleVersion || header.version == AppleDoubleVersion2))
                    {
                        headerPath = DOSAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by A/UX in case preserving FAT filesystem
            if (File.Exists(DOSAppleDoubleLower))
            {
                FileStream doslStream = new FileStream(DOSAppleDoubleLower, FileMode.Open, FileAccess.Read);
                if (doslStream.Length > 26)
                {
                    byte[] dosl_b = new byte[26];
                    doslStream.Read(dosl_b, 0, 26);
                    header = BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(dosl_b);
                    doslStream.Close();
                    if (header.magic == AppleDoubleMagic &&
                        (header.version == AppleDoubleVersion || header.version == AppleDoubleVersion2))
                    {
                        headerPath = DOSAppleDoubleLower;
                    }
                }
            }

            // Check AppleDouble created by Netatalk
            if (File.Exists(NetatalkAppleDouble))
            {
                FileStream netatalkStream = new FileStream(NetatalkAppleDouble, FileMode.Open, FileAccess.Read);
                if (netatalkStream.Length > 26)
                {
                    byte[] netatalk_b = new byte[26];
                    netatalkStream.Read(netatalk_b, 0, 26);
                    header = BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(netatalk_b);
                    netatalkStream.Close();
                    if (header.magic == AppleDoubleMagic &&
                        (header.version == AppleDoubleVersion || header.version == AppleDoubleVersion2))
                    {
                        headerPath = NetatalkAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by DAVE
            if (File.Exists(DAVEAppleDouble))
            {
                FileStream daveStream = new FileStream(DAVEAppleDouble, FileMode.Open, FileAccess.Read);
                if (daveStream.Length > 26)
                {
                    byte[] dave_b = new byte[26];
                    daveStream.Read(dave_b, 0, 26);
                    header = BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(dave_b);
                    daveStream.Close();
                    if (header.magic == AppleDoubleMagic &&
                        (header.version == AppleDoubleVersion || header.version == AppleDoubleVersion2))
                    {
                        headerPath = DAVEAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by Mac OS X
            if (File.Exists(OSXAppleDouble))
            {
                FileStream osxStream = new FileStream(OSXAppleDouble, FileMode.Open, FileAccess.Read);
                if (osxStream.Length > 26)
                {
                    byte[] osx_b = new byte[26];
                    osxStream.Read(osx_b, 0, 26);
                    header = BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(osx_b);
                    osxStream.Close();
                    if (header.magic == AppleDoubleMagic &&
                        (header.version == AppleDoubleVersion || header.version == AppleDoubleVersion2))
                    {
                        headerPath = OSXAppleDouble;
                    }
                }
            }

            // Check AppleDouble created by UnAr (from The Unarchiver)
            if (File.Exists(UnArAppleDouble))
            {
                FileStream unarStream = new FileStream(UnArAppleDouble, FileMode.Open, FileAccess.Read);
                if (unarStream.Length > 26)
                {
                    byte[] unar_b = new byte[26];
                    unarStream.Read(unar_b, 0, 26);
                    header = BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(unar_b);
                    unarStream.Close();
                    if (header.magic == AppleDoubleMagic &&
                        (header.version == AppleDoubleVersion || header.version == AppleDoubleVersion2))
                    {
                        headerPath = UnArAppleDouble;
                    }
                }
            }

            FileStream fs = new FileStream(headerPath, FileMode.Open, FileAccess.Read);

            fs.Seek(0, SeekOrigin.Begin);

            byte[] hdr_b = new byte[26];
            fs.Read(hdr_b, 0, 26);
            header = BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleHeader>(hdr_b);

            AppleDoubleEntry[] entries = new AppleDoubleEntry[header.entries];
            for (int i = 0; i < header.entries; i++)
            {
                byte[] entry = new byte[12];
                fs.Read(entry, 0, 12);
                entries[i] = BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleEntry>(entry);
            }

            creationTime  = DateTime.UtcNow;
            lastWriteTime = creationTime;
            foreach (AppleDoubleEntry entry in entries)
            {
                switch ((AppleDoubleEntryID)entry.id)
                {
                case AppleDoubleEntryID.DataFork:
                    // AppleDouble have datafork in separated file
                    break;

                case AppleDoubleEntryID.FileDates:
                    fs.Seek(entry.offset, SeekOrigin.Begin);
                    byte[] dates_b = new byte[16];
                    fs.Read(dates_b, 0, 16);
                    AppleDoubleFileDates dates =
                        BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleFileDates>(dates_b);
                    creationTime  = DateHandlers.UnixUnsignedToDateTime(dates.creationDate);
                    lastWriteTime = DateHandlers.UnixUnsignedToDateTime(dates.modificationDate);
                    break;

                case AppleDoubleEntryID.FileInfo:
                    fs.Seek(entry.offset, SeekOrigin.Begin);
                    byte[] finfo = new byte[entry.length];
                    fs.Read(finfo, 0, finfo.Length);
                    if (MacintoshHome.SequenceEqual(header.homeFilesystem))
                    {
                        AppleDoubleMacFileInfo macinfo =
                            BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleMacFileInfo>(finfo);
                        creationTime  = DateHandlers.MacToDateTime(macinfo.creationDate);
                        lastWriteTime = DateHandlers.MacToDateTime(macinfo.modificationDate);
                    }
                    else if (ProDOSHome.SequenceEqual(header.homeFilesystem))
                    {
                        AppleDoubleProDOSFileInfo prodosinfo =
                            BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleProDOSFileInfo>(finfo);
                        creationTime  = DateHandlers.MacToDateTime(prodosinfo.creationDate);
                        lastWriteTime = DateHandlers.MacToDateTime(prodosinfo.modificationDate);
                    }
                    else if (UNIXHome.SequenceEqual(header.homeFilesystem))
                    {
                        AppleDoubleUNIXFileInfo unixinfo =
                            BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleUNIXFileInfo>(finfo);
                        creationTime  = DateHandlers.UnixUnsignedToDateTime(unixinfo.creationDate);
                        lastWriteTime = DateHandlers.UnixUnsignedToDateTime(unixinfo.modificationDate);
                    }
                    else if (DOSHome.SequenceEqual(header.homeFilesystem))
                    {
                        AppleDoubleDOSFileInfo dosinfo =
                            BigEndianMarshal.ByteArrayToStructureBigEndian <AppleDoubleDOSFileInfo>(finfo);
                        lastWriteTime =
                            DateHandlers.DosToDateTime(dosinfo.modificationDate, dosinfo.modificationTime);
                    }

                    break;

                case AppleDoubleEntryID.ResourceFork:
                    rsrcFork = entry;
                    break;
                }
            }

            dataFork = new AppleDoubleEntry {
                id = (uint)AppleDoubleEntryID.DataFork
            };
            if (File.Exists(path))
            {
                FileStream dataFs = new FileStream(path, FileMode.Open, FileAccess.Read);
                dataFork.length = (uint)dataFs.Length;
                dataFs.Close();
            }

            fs.Close();
            opened   = true;
            basePath = path;
        }
Esempio n. 10
0
        /// <inheritdoc />
        public Errno Mount(IMediaImage imagePlugin, Partition partition, Encoding encoding,
                           Dictionary <string, string> options, string @namespace)
        {
            XmlFsType = new FileSystemType();

            options ??= GetDefaultOptions();

            if (options.TryGetValue("debug", out string debugString))
            {
                bool.TryParse(debugString, out _debug);
            }

            // Default namespace
            @namespace ??= "ecs";

            switch (@namespace.ToLowerInvariant())
            {
            case "dos":
                _namespace = Namespace.Dos;

                break;

            case "nt":
                _namespace = Namespace.Nt;

                break;

            case "os2":
                _namespace = Namespace.Os2;

                break;

            case "ecs":
                _namespace = Namespace.Ecs;

                break;

            case "lfn":
                _namespace = Namespace.Lfn;

                break;

            case "human":
                _namespace = Namespace.Human;

                break;

            default: return(Errno.InvalidArgument);
            }

            AaruConsole.DebugWriteLine("FAT plugin", "Reading BPB");

            uint sectorsPerBpb = imagePlugin.Info.SectorSize < 512 ? 512 / imagePlugin.Info.SectorSize : 1;

            byte[] bpbSector = imagePlugin.ReadSectors(0 + partition.Start, sectorsPerBpb);

            BpbKind bpbKind = DetectBpbKind(bpbSector, imagePlugin, partition, out BiosParameterBlockEbpb fakeBpb,
                                            out HumanParameterBlock humanBpb, out AtariParameterBlock atariBpb,
                                            out byte minBootNearJump, out bool andosOemCorrect, out bool bootable);

            _fat12             = false;
            _fat16             = false;
            _fat32             = false;
            _useFirstFat       = true;
            XmlFsType.Bootable = bootable;

            _statfs = new FileSystemInfo
            {
                FilenameLength = 11,
                Files          = 0, // Requires traversing all directories
                FreeFiles      = 0,
                PluginId       = Id,
                FreeBlocks     = 0 // Requires traversing the FAT
            };

            // This is needed because for FAT16, GEMDOS increases bytes per sector count instead of using big_sectors field.
            uint sectorsPerRealSector = 1;

            // This is needed because some OSes don't put volume label as first entry in the root directory
            uint sectorsForRootDirectory = 0;
            uint rootDirectoryCluster    = 0;

            Encoding = encoding ?? (bpbKind == BpbKind.Human ? Encoding.GetEncoding("shift_jis")
                                        : Encoding.GetEncoding("IBM437"));

            switch (bpbKind)
            {
            case BpbKind.DecRainbow:
            case BpbKind.Hardcoded:
            case BpbKind.Msx:
            case BpbKind.Apricot:
                _fat12 = true;

                break;

            case BpbKind.ShortFat32:
            case BpbKind.LongFat32:
            {
                _fat32 = true;

                Fat32ParameterBlock fat32Bpb =
                    Marshal.ByteArrayToStructureLittleEndian <Fat32ParameterBlock>(bpbSector);

                Fat32ParameterBlockShort shortFat32Bpb =
                    Marshal.ByteArrayToStructureLittleEndian <Fat32ParameterBlockShort>(bpbSector);

                rootDirectoryCluster = fat32Bpb.root_cluster;

                // This is to support FAT partitions on hybrid ISO/USB images
                if (imagePlugin.Info.XmlMediaType == XmlMediaType.OpticalDisc)
                {
                    fat32Bpb.bps       *= 4;
                    fat32Bpb.spc       /= 4;
                    fat32Bpb.big_spfat /= 4;
                    fat32Bpb.hsectors  /= 4;
                    fat32Bpb.sptrk     /= 4;
                }

                XmlFsType.Type = fat32Bpb.version != 0 ? "FAT+" : "FAT32";

                if (fat32Bpb.oem_name != null &&
                    (fat32Bpb.oem_name[5] != 0x49 || fat32Bpb.oem_name[6] != 0x48 || fat32Bpb.oem_name[7] != 0x43))
                {
                    XmlFsType.SystemIdentifier = StringHandlers.CToString(fat32Bpb.oem_name);
                }

                _sectorsPerCluster    = fat32Bpb.spc;
                XmlFsType.ClusterSize = (uint)(fat32Bpb.bps * fat32Bpb.spc);
                _reservedSectors      = fat32Bpb.rsectors;

                if (fat32Bpb.big_sectors == 0 &&
                    fat32Bpb.signature == 0x28)
                {
                    XmlFsType.Clusters = shortFat32Bpb.huge_sectors / shortFat32Bpb.spc;
                }
                else if (fat32Bpb.sectors == 0)
                {
                    XmlFsType.Clusters = fat32Bpb.big_sectors / fat32Bpb.spc;
                }
                else
                {
                    XmlFsType.Clusters = (ulong)(fat32Bpb.sectors / fat32Bpb.spc);
                }

                _sectorsPerFat         = fat32Bpb.big_spfat;
                XmlFsType.VolumeSerial = $"{fat32Bpb.serial_no:X8}";

                _statfs.Id = new FileSystemId
                {
                    IsInt    = true,
                    Serial32 = fat32Bpb.serial_no
                };

                if ((fat32Bpb.flags & 0xF8) == 0x00)
                {
                    if ((fat32Bpb.flags & 0x01) == 0x01)
                    {
                        XmlFsType.Dirty = true;
                    }
                }

                if ((fat32Bpb.mirror_flags & 0x80) == 0x80)
                {
                    _useFirstFat = (fat32Bpb.mirror_flags & 0xF) != 1;
                }

                if (fat32Bpb.signature == 0x29)
                {
                    XmlFsType.VolumeName = StringHandlers.SpacePaddedToString(fat32Bpb.volume_label, Encoding);
                    XmlFsType.VolumeName = XmlFsType.VolumeName?.Replace("\0", "");
                }

                // Check that jumps to a correct boot code position and has boot signature set.
                // This will mean that the volume will boot, even if just to say "this is not bootable change disk"......
                XmlFsType.Bootable =
                    (fat32Bpb.jump[0] == 0xEB && fat32Bpb.jump[1] >= minBootNearJump && fat32Bpb.jump[1] < 0x80) ||
                    (fat32Bpb.jump[0] == 0xE9 && fat32Bpb.jump.Length >= 3 &&
                     BitConverter.ToUInt16(fat32Bpb.jump, 1) >= minBootNearJump &&
                     BitConverter.ToUInt16(fat32Bpb.jump, 1) <= 0x1FC);

                sectorsPerRealSector = fat32Bpb.bps / imagePlugin.Info.SectorSize;
                _sectorsPerCluster  *= sectorsPerRealSector;

                // First root directory sector
                _firstClusterSector =
                    ((ulong)((fat32Bpb.big_spfat * fat32Bpb.fats_no) + fat32Bpb.rsectors) * sectorsPerRealSector) -
                    (2 * _sectorsPerCluster);

                if (fat32Bpb.fsinfo_sector + partition.Start <= partition.End)
                {
                    byte[] fsinfoSector = imagePlugin.ReadSector(fat32Bpb.fsinfo_sector + partition.Start);

                    FsInfoSector fsInfo = Marshal.ByteArrayToStructureLittleEndian <FsInfoSector>(fsinfoSector);

                    if (fsInfo.signature1 == FSINFO_SIGNATURE1 &&
                        fsInfo.signature2 == FSINFO_SIGNATURE2 &&
                        fsInfo.signature3 == FSINFO_SIGNATURE3)
                    {
                        if (fsInfo.free_clusters < 0xFFFFFFFF)
                        {
                            XmlFsType.FreeClusters          = fsInfo.free_clusters;
                            XmlFsType.FreeClustersSpecified = true;
                        }
                    }
                }

                break;
            }

            // Some fields could overflow fake BPB, those will be handled below
            case BpbKind.Atari:
            {
                ushort sum = 0;

                for (int i = 0; i < bpbSector.Length; i += 2)
                {
                    sum += BigEndianBitConverter.ToUInt16(bpbSector, i);
                }

                // TODO: Check this
                if (sum == 0x1234)
                {
                    XmlFsType.Bootable = true;
                }

                // BGM changes the bytes per sector instead of changing the sectors per cluster. Why?! WHY!?
                uint ratio = fakeBpb.bps / imagePlugin.Info.SectorSize;
                fakeBpb.bps         = (ushort)imagePlugin.Info.SectorSize;
                fakeBpb.spc         = (byte)(fakeBpb.spc * ratio);
                fakeBpb.rsectors    = (ushort)(fakeBpb.rsectors * ratio);
                fakeBpb.big_sectors = fakeBpb.sectors * ratio;
                fakeBpb.sectors     = 0;
                fakeBpb.spfat       = (ushort)(fakeBpb.spfat * ratio);
                fakeBpb.sptrk       = (ushort)(fakeBpb.sptrk * ratio);

                break;
            }

            case BpbKind.Human:
                // If not debug set Human68k namespace and ShiftJIS codepage as defaults
                if (!_debug)
                {
                    _namespace = Namespace.Human;
                }

                XmlFsType.Bootable = true;

                break;
            }

            ulong firstRootSector = 0;

            if (!_fat32)
            {
                // This is to support FAT partitions on hybrid ISO/USB images
                if (imagePlugin.Info.XmlMediaType == XmlMediaType.OpticalDisc)
                {
                    fakeBpb.bps      *= 4;
                    fakeBpb.spc      /= 4;
                    fakeBpb.spfat    /= 4;
                    fakeBpb.hsectors /= 4;
                    fakeBpb.sptrk    /= 4;
                    fakeBpb.rsectors /= 4;

                    if (fakeBpb.spc == 0)
                    {
                        fakeBpb.spc = 1;
                    }
                }

                ulong clusters;

                if (bpbKind != BpbKind.Human)
                {
                    int reservedSectors = fakeBpb.rsectors + (fakeBpb.fats_no * fakeBpb.spfat) +
                                          (fakeBpb.root_ent * 32 / fakeBpb.bps);

                    if (fakeBpb.sectors == 0)
                    {
                        clusters = (ulong)(fakeBpb.spc == 0 ? fakeBpb.big_sectors - reservedSectors
                                               : (fakeBpb.big_sectors - reservedSectors) / fakeBpb.spc);
                    }
                    else
                    {
                        clusters = (ulong)(fakeBpb.spc == 0 ? fakeBpb.sectors - reservedSectors
                                               : (fakeBpb.sectors - reservedSectors) / fakeBpb.spc);
                    }
                }
                else
                {
                    clusters = humanBpb.clusters == 0 ? humanBpb.big_clusters : humanBpb.clusters;
                }

                // This will walk all the FAT entries and check if they're valid FAT12 or FAT16 entries.
                // If the whole table is valid in both senses, it considers the type entry in the BPB.
                // BeOS is known to set the type as FAT16 but treat it as FAT12.
                if (!_fat12 &&
                    !_fat16)
                {
                    if (clusters < 4089)
                    {
                        ushort[] fat12 = new ushort[clusters];

                        _reservedSectors     = fakeBpb.rsectors;
                        sectorsPerRealSector = fakeBpb.bps / imagePlugin.Info.SectorSize;
                        _fatFirstSector      = partition.Start + (_reservedSectors * sectorsPerRealSector);

                        byte[] fatBytes = imagePlugin.ReadSectors(_fatFirstSector, fakeBpb.spfat);

                        int pos = 0;

                        for (int i = 0; i + 3 < fatBytes.Length && pos < fat12.Length; i += 3)
                        {
                            fat12[pos++] = (ushort)(((fatBytes[i + 1] & 0xF) << 8) + fatBytes[i + 0]);

                            if (pos >= fat12.Length)
                            {
                                break;
                            }

                            fat12[pos++] = (ushort)(((fatBytes[i + 1] & 0xF0) >> 4) + (fatBytes[i + 2] << 4));
                        }

                        bool fat12Valid = fat12[0] >= FAT12_RESERVED && fat12[1] >= FAT12_RESERVED;

                        foreach (ushort entry in fat12)
                        {
                            if (entry >= FAT12_RESERVED ||
                                entry <= clusters)
                            {
                                continue;
                            }

                            fat12Valid = false;

                            break;
                        }

                        ushort[] fat16 = MemoryMarshal.Cast <byte, ushort>(fatBytes).ToArray();

                        bool fat16Valid = fat16[0] >= FAT16_RESERVED && fat16[1] >= 0x3FF0;

                        foreach (ushort entry in fat16)
                        {
                            if (entry >= FAT16_RESERVED ||
                                entry <= clusters)
                            {
                                continue;
                            }

                            fat16Valid = false;

                            break;
                        }

                        _fat12 = fat12Valid;
                        _fat16 = fat16Valid;

                        // Check BPB type
                        if (_fat12 == _fat16)
                        {
                            _fat12 = Encoding.ASCII.GetString(fakeBpb.fs_type) == "FAT12   ";
                            _fat16 = Encoding.ASCII.GetString(fakeBpb.fs_type) == "FAT16   ";
                        }
                    }
                    else
                    {
                        _fat16 = true;
                    }
                }

                if (_fat12)
                {
                    XmlFsType.Type = "FAT12";
                }
                else if (_fat16)
                {
                    XmlFsType.Type = "FAT16";
                }

                if (bpbKind == BpbKind.Atari)
                {
                    if (atariBpb.serial_no[0] != 0x49 ||
                        atariBpb.serial_no[1] != 0x48 ||
                        atariBpb.serial_no[2] != 0x43)
                    {
                        XmlFsType.VolumeSerial =
                            $"{atariBpb.serial_no[0]:X2}{atariBpb.serial_no[1]:X2}{atariBpb.serial_no[2]:X2}";

                        _statfs.Id = new FileSystemId
                        {
                            IsInt    = true,
                            Serial32 = (uint)((atariBpb.serial_no[0] << 16) + (atariBpb.serial_no[1] << 8) +
                                              atariBpb.serial_no[2])
                        };
                    }

                    XmlFsType.SystemIdentifier = StringHandlers.CToString(atariBpb.oem_name);

                    if (string.IsNullOrEmpty(XmlFsType.SystemIdentifier))
                    {
                        XmlFsType.SystemIdentifier = null;
                    }
                }
                else if (fakeBpb.oem_name != null)
                {
                    if (fakeBpb.oem_name[5] != 0x49 ||
                        fakeBpb.oem_name[6] != 0x48 ||
                        fakeBpb.oem_name[7] != 0x43)
                    {
                        // Later versions of Windows create a DOS 3 BPB without OEM name on 8 sectors/track floppies
                        // OEM ID should be ASCII, otherwise ignore it
                        if (fakeBpb.oem_name[0] >= 0x20 &&
                            fakeBpb.oem_name[0] <= 0x7F &&
                            fakeBpb.oem_name[1] >= 0x20 &&
                            fakeBpb.oem_name[1] <= 0x7F &&
                            fakeBpb.oem_name[2] >= 0x20 &&
                            fakeBpb.oem_name[2] <= 0x7F &&
                            fakeBpb.oem_name[3] >= 0x20 &&
                            fakeBpb.oem_name[3] <= 0x7F &&
                            fakeBpb.oem_name[4] >= 0x20 &&
                            fakeBpb.oem_name[4] <= 0x7F &&
                            fakeBpb.oem_name[5] >= 0x20 &&
                            fakeBpb.oem_name[5] <= 0x7F &&
                            fakeBpb.oem_name[6] >= 0x20 &&
                            fakeBpb.oem_name[6] <= 0x7F &&
                            fakeBpb.oem_name[7] >= 0x20 &&
                            fakeBpb.oem_name[7] <= 0x7F)
                        {
                            XmlFsType.SystemIdentifier = StringHandlers.CToString(fakeBpb.oem_name);
                        }
                        else if (fakeBpb.oem_name[0] < 0x20 &&
                                 fakeBpb.oem_name[1] >= 0x20 &&
                                 fakeBpb.oem_name[1] <= 0x7F &&
                                 fakeBpb.oem_name[2] >= 0x20 &&
                                 fakeBpb.oem_name[2] <= 0x7F &&
                                 fakeBpb.oem_name[3] >= 0x20 &&
                                 fakeBpb.oem_name[3] <= 0x7F &&
                                 fakeBpb.oem_name[4] >= 0x20 &&
                                 fakeBpb.oem_name[4] <= 0x7F &&
                                 fakeBpb.oem_name[5] >= 0x20 &&
                                 fakeBpb.oem_name[5] <= 0x7F &&
                                 fakeBpb.oem_name[6] >= 0x20 &&
                                 fakeBpb.oem_name[6] <= 0x7F &&
                                 fakeBpb.oem_name[7] >= 0x20 &&
                                 fakeBpb.oem_name[7] <= 0x7F)
                        {
                            XmlFsType.SystemIdentifier = StringHandlers.CToString(fakeBpb.oem_name, Encoding, start: 1);
                        }
                    }

                    if (fakeBpb.signature == 0x28 ||
                        fakeBpb.signature == 0x29)
                    {
                        XmlFsType.VolumeSerial = $"{fakeBpb.serial_no:X8}";

                        _statfs.Id = new FileSystemId
                        {
                            IsInt    = true,
                            Serial32 = fakeBpb.serial_no
                        };
                    }
                }

                XmlFsType.Clusters    = clusters;
                _sectorsPerCluster    = fakeBpb.spc;
                XmlFsType.ClusterSize = (uint)(fakeBpb.bps * fakeBpb.spc);
                _reservedSectors      = fakeBpb.rsectors;
                _sectorsPerFat        = fakeBpb.spfat;

                if (fakeBpb.signature == 0x28 ||
                    fakeBpb.signature == 0x29 ||
                    andosOemCorrect)
                {
                    if ((fakeBpb.flags & 0xF8) == 0x00)
                    {
                        if ((fakeBpb.flags & 0x01) == 0x01)
                        {
                            XmlFsType.Dirty = true;
                        }
                    }

                    if (fakeBpb.signature == 0x29 || andosOemCorrect)
                    {
                        XmlFsType.VolumeName = StringHandlers.SpacePaddedToString(fakeBpb.volume_label, Encoding);
                        XmlFsType.VolumeName = XmlFsType.VolumeName?.Replace("\0", "");
                    }
                }

                // Workaround that PCExchange jumps into "FAT16   "...
                if (XmlFsType.SystemIdentifier == "PCX 2.0 ")
                {
                    fakeBpb.jump[1] += 8;
                }

                // Check that jumps to a correct boot code position and has boot signature set.
                // This will mean that the volume will boot, even if just to say "this is not bootable change disk"......
                if (XmlFsType.Bootable == false &&
                    fakeBpb.jump != null)
                {
                    XmlFsType.Bootable |=
                        (fakeBpb.jump[0] == 0xEB && fakeBpb.jump[1] >= minBootNearJump && fakeBpb.jump[1] < 0x80) ||
                        (fakeBpb.jump[0] == 0xE9 && fakeBpb.jump.Length >= 3 &&
                         BitConverter.ToUInt16(fakeBpb.jump, 1) >= minBootNearJump &&
                         BitConverter.ToUInt16(fakeBpb.jump, 1) <= 0x1FC);
                }

                // First root directory sector
                firstRootSector =
                    ((ulong)((fakeBpb.spfat * fakeBpb.fats_no) + fakeBpb.rsectors) * sectorsPerRealSector) +
                    partition.Start;

                sectorsForRootDirectory = (uint)(fakeBpb.root_ent * 32 / imagePlugin.Info.SectorSize);

                sectorsPerRealSector = fakeBpb.bps / imagePlugin.Info.SectorSize;
                _sectorsPerCluster  *= sectorsPerRealSector;
            }

            _firstClusterSector += partition.Start;

            _image = imagePlugin;

            if (_fat32)
            {
                _fatEntriesPerSector = imagePlugin.Info.SectorSize / 4;
            }
            else if (_fat16)
            {
                _fatEntriesPerSector = imagePlugin.Info.SectorSize / 2;
            }
            else
            {
                _fatEntriesPerSector = imagePlugin.Info.SectorSize * 2 / 3;
            }

            _fatFirstSector = partition.Start + (_reservedSectors * sectorsPerRealSector);

            _rootDirectoryCache = new Dictionary <string, CompleteDirectoryEntry>();
            byte[] rootDirectory;

            if (!_fat32)
            {
                _firstClusterSector = firstRootSector + sectorsForRootDirectory - (_sectorsPerCluster * 2);
                rootDirectory       = imagePlugin.ReadSectors(firstRootSector, sectorsForRootDirectory);

                if (bpbKind == BpbKind.DecRainbow)
                {
                    var rootMs = new MemoryStream();

                    foreach (byte[] tmp in from ulong rootSector in new[]
                    {
                        0x17, 0x19, 0x1B, 0x1D, 0x1E, 0x20
                    } select imagePlugin.ReadSector(rootSector))
                    {
                        rootMs.Write(tmp, 0, tmp.Length);
                    }

                    rootDirectory = rootMs.ToArray();
                }
            }
            else
            {
                if (rootDirectoryCluster == 0)
                {
                    return(Errno.InvalidArgument);
                }

                var    rootMs = new MemoryStream();
                uint[] rootDirectoryClusters = GetClusters(rootDirectoryCluster);

                foreach (byte[] buffer in rootDirectoryClusters.Select(cluster =>
                                                                       imagePlugin.
                                                                       ReadSectors(_firstClusterSector + (cluster * _sectorsPerCluster),
                                                                                   _sectorsPerCluster)))
                {
                    rootMs.Write(buffer, 0, buffer.Length);
                }

                rootDirectory = rootMs.ToArray();

                // OS/2 FAT32.IFS uses LFN instead of .LONGNAME
                if (_namespace == Namespace.Os2)
                {
                    _namespace = Namespace.Lfn;
                }
            }

            if (rootDirectory is null)
            {
                return(Errno.InvalidArgument);
            }

            byte[] lastLfnName     = null;
            byte   lastLfnChecksum = 0;

            for (int i = 0; i < rootDirectory.Length; i += Marshal.SizeOf <DirectoryEntry>())
            {
                DirectoryEntry entry =
                    Marshal.ByteArrayToStructureLittleEndian <DirectoryEntry>(rootDirectory, i,
                                                                              Marshal.SizeOf <DirectoryEntry>());

                if (entry.filename[0] == DIRENT_FINISHED)
                {
                    break;
                }

                if (entry.attributes.HasFlag(FatAttributes.LFN))
                {
                    if (_namespace != Namespace.Lfn &&
                        _namespace != Namespace.Ecs)
                    {
                        continue;
                    }

                    LfnEntry lfnEntry =
                        Marshal.ByteArrayToStructureLittleEndian <LfnEntry>(rootDirectory, i,
                                                                            Marshal.SizeOf <LfnEntry>());

                    int lfnSequence = lfnEntry.sequence & LFN_MASK;

                    if ((lfnEntry.sequence & LFN_ERASED) > 0)
                    {
                        continue;
                    }

                    if ((lfnEntry.sequence & LFN_LAST) > 0)
                    {
                        lastLfnName     = new byte[lfnSequence * 26];
                        lastLfnChecksum = lfnEntry.checksum;
                    }

                    if (lastLfnName is null)
                    {
                        continue;
                    }

                    if (lfnEntry.checksum != lastLfnChecksum)
                    {
                        continue;
                    }

                    lfnSequence--;

                    Array.Copy(lfnEntry.name1, 0, lastLfnName, lfnSequence * 26, 10);
                    Array.Copy(lfnEntry.name2, 0, lastLfnName, (lfnSequence * 26) + 10, 12);
                    Array.Copy(lfnEntry.name3, 0, lastLfnName, (lfnSequence * 26) + 22, 4);

                    continue;
                }

                // Not a correct entry
                if (entry.filename[0] < DIRENT_MIN &&
                    entry.filename[0] != DIRENT_E5)
                {
                    continue;
                }

                // Self
                if (Encoding.GetString(entry.filename).TrimEnd() == ".")
                {
                    continue;
                }

                // Parent
                if (Encoding.GetString(entry.filename).TrimEnd() == "..")
                {
                    continue;
                }

                // Deleted
                if (entry.filename[0] == DIRENT_DELETED)
                {
                    continue;
                }

                string filename;

                if (entry.attributes.HasFlag(FatAttributes.VolumeLabel))
                {
                    byte[] fullname = new byte[11];
                    Array.Copy(entry.filename, 0, fullname, 0, 8);
                    Array.Copy(entry.extension, 0, fullname, 8, 3);
                    string volname = Encoding.GetString(fullname).Trim();

                    if (!string.IsNullOrEmpty(volname))
                    {
                        XmlFsType.VolumeName =
                            entry.caseinfo.HasFlag(CaseInfo.AllLowerCase) && _namespace == Namespace.Nt
                                ? volname.ToLower() : volname;
                    }

                    XmlFsType.VolumeName = XmlFsType.VolumeName?.Replace("\0", "");

                    if (entry.ctime > 0 &&
                        entry.cdate > 0)
                    {
                        XmlFsType.CreationDate = DateHandlers.DosToDateTime(entry.cdate, entry.ctime);

                        if (entry.ctime_ms > 0)
                        {
                            XmlFsType.CreationDate = XmlFsType.CreationDate.AddMilliseconds(entry.ctime_ms * 10);
                        }

                        XmlFsType.CreationDateSpecified = true;
                    }

                    if (entry.mtime > 0 &&
                        entry.mdate > 0)
                    {
                        XmlFsType.ModificationDate          = DateHandlers.DosToDateTime(entry.mdate, entry.mtime);
                        XmlFsType.ModificationDateSpecified = true;
                    }

                    continue;
                }

                var completeEntry = new CompleteDirectoryEntry
                {
                    Dirent = entry
                };

                if ((_namespace == Namespace.Lfn || _namespace == Namespace.Ecs) &&
                    lastLfnName != null)
                {
                    byte calculatedLfnChecksum = LfnChecksum(entry.filename, entry.extension);

                    if (calculatedLfnChecksum == lastLfnChecksum)
                    {
                        filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true);

                        completeEntry.Lfn = filename;
                        lastLfnName       = null;
                        lastLfnChecksum   = 0;
                    }
                }

                if (entry.filename[0] == DIRENT_E5)
                {
                    entry.filename[0] = DIRENT_DELETED;
                }

                string name      = Encoding.GetString(entry.filename).TrimEnd();
                string extension = Encoding.GetString(entry.extension).TrimEnd();

                if (_namespace == Namespace.Nt)
                {
                    if (entry.caseinfo.HasFlag(CaseInfo.LowerCaseExtension))
                    {
                        extension = extension.ToLower(CultureInfo.CurrentCulture);
                    }

                    if (entry.caseinfo.HasFlag(CaseInfo.LowerCaseBasename))
                    {
                        name = name.ToLower(CultureInfo.CurrentCulture);
                    }
                }

                if (extension != "")
                {
                    filename = name + "." + extension;
                }
                else
                {
                    filename = name;
                }

                if (name == "" &&
                    extension == "")
                {
                    AaruConsole.DebugWriteLine("FAT filesystem", "Found empty filename in root directory");

                    if (!_debug ||
                        (entry.size > 0 && entry.start_cluster == 0))
                    {
                        continue; // Skip invalid name
                    }
                    // If debug, add it
                    name = ":{EMPTYNAME}:";

                    // Try to create a unique filename with an extension from 000 to 999
                    for (int uniq = 0; uniq < 1000; uniq++)
                    {
                        extension = $"{uniq:D03}";

                        if (!_rootDirectoryCache.ContainsKey($"{name}.{extension}"))
                        {
                            break;
                        }
                    }

                    // If we couldn't find it, just skip over
                    if (_rootDirectoryCache.ContainsKey($"{name}.{extension}"))
                    {
                        continue;
                    }
                }

                // Atari ST allows slash AND colon so cannot simply substitute one for the other like in Mac filesystems
                filename = filename.Replace('/', '\u2215');

                completeEntry.Shortname = filename;

                if (_namespace == Namespace.Human)
                {
                    HumanDirectoryEntry humanEntry =
                        Marshal.ByteArrayToStructureLittleEndian <HumanDirectoryEntry>(rootDirectory, i,
                                                                                       Marshal.SizeOf <HumanDirectoryEntry>());

                    completeEntry.HumanDirent = humanEntry;

                    name      = StringHandlers.CToString(humanEntry.name1, Encoding).TrimEnd();
                    extension = StringHandlers.CToString(humanEntry.extension, Encoding).TrimEnd();
                    string name2 = StringHandlers.CToString(humanEntry.name2, Encoding).TrimEnd();

                    if (extension != "")
                    {
                        filename = name + name2 + "." + extension;
                    }
                    else
                    {
                        filename = name + name2;
                    }

                    completeEntry.HumanName = filename;
                }

                if (!_fat32 &&
                    filename == "EA DATA. SF")
                {
                    _eaDirEntry     = entry;
                    lastLfnName     = null;
                    lastLfnChecksum = 0;

                    if (_debug)
                    {
                        _rootDirectoryCache[completeEntry.ToString()] = completeEntry;
                    }

                    continue;
                }

                _rootDirectoryCache[completeEntry.ToString()] = completeEntry;
                lastLfnName     = null;
                lastLfnChecksum = 0;
            }

            XmlFsType.VolumeName = XmlFsType.VolumeName?.Trim();
            _statfs.Blocks       = XmlFsType.Clusters;

            switch (bpbKind)
            {
            case BpbKind.Hardcoded:
                _statfs.Type = $"Microsoft FAT{(_fat16 ? "16" : "12")}";

                break;

            case BpbKind.Atari:
                _statfs.Type = $"Atari FAT{(_fat16 ? "16" : "12")}";

                break;

            case BpbKind.Msx:
                _statfs.Type = $"MSX FAT{(_fat16 ? "16" : "12")}";

                break;

            case BpbKind.Dos2:
            case BpbKind.Dos3:
            case BpbKind.Dos32:
            case BpbKind.Dos33:
            case BpbKind.ShortExtended:
            case BpbKind.Extended:
                _statfs.Type = $"Microsoft FAT{(_fat16 ? "16" : "12")}";

                break;

            case BpbKind.ShortFat32:
            case BpbKind.LongFat32:
                _statfs.Type = XmlFsType.Type == "FAT+" ? "FAT+" : "Microsoft FAT32";

                break;

            case BpbKind.Andos:
                _statfs.Type = $"ANDOS FAT{(_fat16 ? "16" : "12")}";

                break;

            case BpbKind.Apricot:
                _statfs.Type = $"Apricot FAT{(_fat16 ? "16" : "12")}";

                break;

            case BpbKind.DecRainbow:
                _statfs.Type = $"DEC FAT{(_fat16 ? "16" : "12")}";

                break;

            case BpbKind.Human:
                _statfs.Type = $"Human68k FAT{(_fat16 ? "16" : "12")}";

                break;

            default: throw new ArgumentOutOfRangeException();
            }

            _bytesPerCluster = _sectorsPerCluster * imagePlugin.Info.SectorSize;

            ushort[] _firstFatEntries  = new ushort[_statfs.Blocks];
            ushort[] _secondFatEntries = new ushort[_statfs.Blocks];
            bool     _firstFatValid    = true;
            bool     _secondFatValid   = true;

            if (_fat12)
            {
                AaruConsole.DebugWriteLine("FAT plugin", "Reading FAT12");

                byte[] fatBytes = imagePlugin.ReadSectors(_fatFirstSector, _sectorsPerFat);

                int pos = 0;

                for (int i = 0; i + 3 < fatBytes.Length && pos < _firstFatEntries.Length; i += 3)
                {
                    _firstFatEntries[pos++] = (ushort)(((fatBytes[i + 1] & 0xF) << 8) + fatBytes[i + 0]);

                    if (pos >= _firstFatEntries.Length)
                    {
                        break;
                    }

                    _firstFatEntries[pos++] = (ushort)(((fatBytes[i + 1] & 0xF0) >> 4) + (fatBytes[i + 2] << 4));
                }

                fatBytes = imagePlugin.ReadSectors(_fatFirstSector + _sectorsPerFat, _sectorsPerFat);

                _fatEntries = new ushort[_statfs.Blocks];

                pos = 0;

                for (int i = 0; i + 3 < fatBytes.Length && pos < _secondFatEntries.Length; i += 3)
                {
                    _secondFatEntries[pos++] = (ushort)(((fatBytes[i + 1] & 0xF) << 8) + fatBytes[i + 0]);

                    if (pos >= _secondFatEntries.Length)
                    {
                        break;
                    }

                    _secondFatEntries[pos++] = (ushort)(((fatBytes[i + 1] & 0xF0) >> 4) + (fatBytes[i + 2] << 4));
                }

                foreach (ushort entry in _firstFatEntries)
                {
                    if (entry >= FAT12_RESERVED ||
                        entry <= _statfs.Blocks)
                    {
                        continue;
                    }

                    _firstFatValid = false;

                    break;
                }

                foreach (ushort entry in _secondFatEntries)
                {
                    if (entry >= FAT12_RESERVED ||
                        entry <= _statfs.Blocks)
                    {
                        continue;
                    }

                    _secondFatValid = false;

                    break;
                }

                if (_firstFatValid == _secondFatValid)
                {
                    _fatEntries = _useFirstFat ? _firstFatEntries : _secondFatEntries;
                }
                else if (_firstFatValid)
                {
                    _fatEntries = _firstFatEntries;
                }
                else
                {
                    _fatEntries = _secondFatEntries;
                }
            }
            else if (_fat16)
            {
                AaruConsole.DebugWriteLine("FAT plugin", "Reading FAT16");

                byte[] fatBytes = imagePlugin.ReadSectors(_fatFirstSector, _sectorsPerFat);

                AaruConsole.DebugWriteLine("FAT plugin", "Casting FAT");
                _firstFatEntries = MemoryMarshal.Cast <byte, ushort>(fatBytes).ToArray();

                fatBytes = imagePlugin.ReadSectors(_fatFirstSector + _sectorsPerFat, _sectorsPerFat);

                AaruConsole.DebugWriteLine("FAT plugin", "Casting FAT");
                _secondFatEntries = MemoryMarshal.Cast <byte, ushort>(fatBytes).ToArray();

                foreach (ushort entry in _firstFatEntries)
                {
                    if (entry >= FAT16_RESERVED ||
                        entry <= _statfs.Blocks)
                    {
                        continue;
                    }

                    _firstFatValid = false;

                    break;
                }

                foreach (ushort entry in _secondFatEntries)
                {
                    if (entry >= FAT16_RESERVED ||
                        entry <= _statfs.Blocks)
                    {
                        continue;
                    }

                    _secondFatValid = false;

                    break;
                }

                if (_firstFatValid == _secondFatValid)
                {
                    _fatEntries = _useFirstFat ? _firstFatEntries : _secondFatEntries;
                }
                else if (_firstFatValid)
                {
                    _fatEntries = _firstFatEntries;
                }
                else
                {
                    _fatEntries = _secondFatEntries;
                }
            }

            // TODO: Check how this affects international filenames
            _cultureInfo    = new CultureInfo("en-US", false);
            _directoryCache = new Dictionary <string, Dictionary <string, CompleteDirectoryEntry> >();

            // Check it is really an OS/2 EA file
            if (_eaDirEntry.start_cluster != 0)
            {
                CacheEaData();
                ushort eamagic = BitConverter.ToUInt16(_cachedEaData, 0);

                if (eamagic != EADATA_MAGIC)
                {
                    _eaDirEntry   = new DirectoryEntry();
                    _cachedEaData = null;
                }
                else
                {
                    _eaCache = new Dictionary <string, Dictionary <string, byte[]> >();
                }
            }
            else if (_fat32)
            {
                _eaCache = new Dictionary <string, Dictionary <string, byte[]> >();
            }

            // Check OS/2 .LONGNAME
            if (_eaCache != null &&
                (_namespace == Namespace.Os2 || _namespace == Namespace.Ecs) &&
                !_fat32)
            {
                List <KeyValuePair <string, CompleteDirectoryEntry> > rootFilesWithEas =
                    _rootDirectoryCache.Where(t => t.Value.Dirent.ea_handle != 0).ToList();

                foreach (KeyValuePair <string, CompleteDirectoryEntry> fileWithEa in rootFilesWithEas)
                {
                    Dictionary <string, byte[]> eas = GetEas(fileWithEa.Value.Dirent.ea_handle);

                    if (eas is null)
                    {
                        continue;
                    }

                    if (!eas.TryGetValue("com.microsoft.os2.longname", out byte[] longnameEa))
Esempio n. 11
0
        /// <summary>
        ///     Mounts an Apple Lisa filesystem
        /// </summary>
        public Errno Mount(IMediaImage imagePlugin, Partition partition, Encoding encoding,
                           Dictionary <string, string> options, string @namespace)
        {
            XmlFsType = new FileSystemType();
            if (options == null)
            {
                options = GetDefaultOptions();
            }
            if (options.TryGetValue("debug", out string debugString))
            {
                bool.TryParse(debugString, out debug);
            }

            // Default namespace
            if (@namespace is null)
            {
                @namespace = "ecs";
            }

            switch (@namespace.ToLowerInvariant())
            {
            case "dos":
                this.@namespace = Namespace.Dos;
                break;

            case "nt":
                this.@namespace = Namespace.Nt;
                break;

            case "os2":
                this.@namespace = Namespace.Os2;
                break;

            case "ecs":
                this.@namespace = Namespace.Ecs;
                break;

            case "lfn":
                this.@namespace = Namespace.Lfn;
                break;

            case "human":
                this.@namespace = Namespace.Human;
                break;

            default: return(Errno.InvalidArgument);
            }

            DicConsole.DebugWriteLine("FAT plugin", "Reading BPB");

            uint sectorsPerBpb = imagePlugin.Info.SectorSize < 512 ? 512 / imagePlugin.Info.SectorSize : 1;

            byte[] bpbSector = imagePlugin.ReadSectors(0 + partition.Start, sectorsPerBpb);

            BpbKind bpbKind = DetectBpbKind(bpbSector, imagePlugin, partition,
                                            out BiosParameterBlockEbpb fakeBpb,
                                            out HumanParameterBlock humanBpb, out AtariParameterBlock atariBpb,
                                            out byte minBootNearJump, out bool andosOemCorrect,
                                            out bool bootable);

            fat12              = false;
            fat16              = false;
            fat32              = false;
            useFirstFat        = true;
            XmlFsType.Bootable = bootable;

            statfs = new FileSystemInfo
            {
                Blocks         = XmlFsType.Clusters,
                FilenameLength = 11,
                Files          = 0, // Requires traversing all directories
                FreeFiles      = 0,
                PluginId       = Id,
                FreeBlocks     = 0 // Requires traversing the FAT
            };

            // This is needed because for FAT16, GEMDOS increases bytes per sector count instead of using big_sectors field.
            uint sectorsPerRealSector = 1;
            // This is needed because some OSes don't put volume label as first entry in the root directory
            uint sectorsForRootDirectory = 0;
            uint rootDirectoryCluster    = 0;

            switch (bpbKind)
            {
            case BpbKind.DecRainbow:
            case BpbKind.Hardcoded:
            case BpbKind.Msx:
            case BpbKind.Apricot:
                fat12 = true;
                break;

            case BpbKind.ShortFat32:
            case BpbKind.LongFat32:
            {
                fat32 = true;
                Fat32ParameterBlock fat32Bpb =
                    Marshal.ByteArrayToStructureLittleEndian <Fat32ParameterBlock>(bpbSector);
                Fat32ParameterBlockShort shortFat32Bpb =
                    Marshal.ByteArrayToStructureLittleEndian <Fat32ParameterBlockShort>(bpbSector);

                rootDirectoryCluster = fat32Bpb.root_cluster;

                // This is to support FAT partitions on hybrid ISO/USB images
                if (imagePlugin.Info.XmlMediaType == XmlMediaType.OpticalDisc)
                {
                    fat32Bpb.bps       *= 4;
                    fat32Bpb.spc       /= 4;
                    fat32Bpb.big_spfat /= 4;
                    fat32Bpb.hsectors  /= 4;
                    fat32Bpb.sptrk     /= 4;
                }

                XmlFsType.Type = fat32Bpb.version != 0 ? "FAT+" : "FAT32";

                if (fat32Bpb.oem_name != null && (fat32Bpb.oem_name[5] != 0x49 || fat32Bpb.oem_name[6] != 0x48 ||
                                                  fat32Bpb.oem_name[7] != 0x43))
                {
                    XmlFsType.SystemIdentifier = StringHandlers.CToString(fat32Bpb.oem_name);
                }

                sectorsPerCluster     = fat32Bpb.spc;
                XmlFsType.ClusterSize = (uint)(fat32Bpb.bps * fat32Bpb.spc);
                reservedSectors       = fat32Bpb.rsectors;
                if (fat32Bpb.big_sectors == 0 && fat32Bpb.signature == 0x28)
                {
                    XmlFsType.Clusters = shortFat32Bpb.huge_sectors / shortFat32Bpb.spc;
                }
                else
                {
                    XmlFsType.Clusters = fat32Bpb.big_sectors / fat32Bpb.spc;
                }

                sectorsPerFat          = fat32Bpb.big_spfat;
                XmlFsType.VolumeSerial = $"{fat32Bpb.serial_no:X8}";
                statfs.Id = new FileSystemId {
                    IsInt = true, Serial32 = fat32Bpb.serial_no
                };

                if ((fat32Bpb.flags & 0xF8) == 0x00)
                {
                    if ((fat32Bpb.flags & 0x01) == 0x01)
                    {
                        XmlFsType.Dirty = true;
                    }
                }

                if ((fat32Bpb.mirror_flags & 0x80) == 0x80)
                {
                    useFirstFat = (fat32Bpb.mirror_flags & 0xF) != 1;
                }

                if (fat32Bpb.signature == 0x29)
                {
                    XmlFsType.VolumeName = Encoding.ASCII.GetString(fat32Bpb.volume_label);
                }

                // Check that jumps to a correct boot code position and has boot signature set.
                // This will mean that the volume will boot, even if just to say "this is not bootable change disk"......
                XmlFsType.Bootable =
                    fat32Bpb.jump[0] == 0xEB && fat32Bpb.jump[1] >= minBootNearJump && fat32Bpb.jump[1] < 0x80 ||
                    fat32Bpb.jump[0] == 0xE9 && fat32Bpb.jump.Length >= 3 &&
                    BitConverter.ToUInt16(fat32Bpb.jump, 1) >= minBootNearJump &&
                    BitConverter.ToUInt16(fat32Bpb.jump, 1) <= 0x1FC;

                sectorsPerRealSector = fat32Bpb.bps / imagePlugin.Info.SectorSize;
                sectorsPerCluster   *= sectorsPerRealSector;

                // First root directory sector
                firstClusterSector =
                    (ulong)(fat32Bpb.big_spfat * fat32Bpb.fats_no + fat32Bpb.rsectors) * sectorsPerRealSector -
                    2 * sectorsPerCluster;

                if (fat32Bpb.fsinfo_sector + partition.Start <= partition.End)
                {
                    byte[]       fsinfoSector = imagePlugin.ReadSector(fat32Bpb.fsinfo_sector + partition.Start);
                    FsInfoSector fsInfo       =
                        Marshal.ByteArrayToStructureLittleEndian <FsInfoSector>(fsinfoSector);

                    if (fsInfo.signature1 == FSINFO_SIGNATURE1 && fsInfo.signature2 == FSINFO_SIGNATURE2 &&
                        fsInfo.signature3 == FSINFO_SIGNATURE3)
                    {
                        if (fsInfo.free_clusters < 0xFFFFFFFF)
                        {
                            XmlFsType.FreeClusters          = fsInfo.free_clusters;
                            XmlFsType.FreeClustersSpecified = true;
                        }
                    }
                }

                break;
            }

            // Some fields could overflow fake BPB, those will be handled below
            case BpbKind.Atari:
            {
                ushort sum = 0;
                for (int i = 0; i < bpbSector.Length; i += 2)
                {
                    sum += BigEndianBitConverter.ToUInt16(bpbSector, i);
                }

                // TODO: Check this
                if (sum == 0x1234)
                {
                    XmlFsType.Bootable = true;
                }

                break;
            }

            case BpbKind.Human:
                // If not debug set Human68k namespace and ShiftJIS codepage as defaults
                if (!debug)
                {
                    this.@namespace = Namespace.Human;
                    encoding        = Encoding.GetEncoding("shift_jis");
                }

                XmlFsType.Bootable = true;
                break;
            }

            Encoding = encoding ?? (bpbKind == BpbKind.Human
                                        ? Encoding.GetEncoding("shift_jis")
                                        : Encoding.GetEncoding("IBM437"));

            ulong firstRootSector = 0;

            if (!fat32)
            {
                // This is to support FAT partitions on hybrid ISO/USB images
                if (imagePlugin.Info.XmlMediaType == XmlMediaType.OpticalDisc)
                {
                    fakeBpb.bps      *= 4;
                    fakeBpb.spc      /= 4;
                    fakeBpb.spfat    /= 4;
                    fakeBpb.hsectors /= 4;
                    fakeBpb.sptrk    /= 4;
                    fakeBpb.rsectors /= 4;

                    if (fakeBpb.spc == 0)
                    {
                        fakeBpb.spc = 1;
                    }
                }

                // This assumes no sane implementation will violate cluster size rules
                // However nothing prevents this to happen
                // If first file on disk uses only one cluster there is absolutely no way to differentiate between FAT12 and FAT16,
                // so let's hope implementations use common sense?
                if (!fat12 && !fat16)
                {
                    ulong clusters;

                    if (fakeBpb.sectors == 0)
                    {
                        clusters = fakeBpb.spc == 0 ? fakeBpb.big_sectors : fakeBpb.big_sectors / fakeBpb.spc;
                    }
                    else
                    {
                        clusters = fakeBpb.spc == 0 ? fakeBpb.sectors : (ulong)fakeBpb.sectors / fakeBpb.spc;
                    }

                    if (clusters < 4089)
                    {
                        fat12 = true;
                    }
                    else
                    {
                        fat16 = true;
                    }
                }

                if (fat12)
                {
                    XmlFsType.Type = "FAT12";
                }
                else if (fat16)
                {
                    XmlFsType.Type = "FAT16";
                }

                if (bpbKind == BpbKind.Atari)
                {
                    if (atariBpb.serial_no[0] != 0x49 || atariBpb.serial_no[1] != 0x48 || atariBpb.serial_no[2] != 0x43)
                    {
                        XmlFsType.VolumeSerial =
                            $"{atariBpb.serial_no[0]:X2}{atariBpb.serial_no[1]:X2}{atariBpb.serial_no[2]:X2}";
                        statfs.Id = new FileSystemId
                        {
                            IsInt    = true,
                            Serial32 = (uint)((atariBpb.serial_no[0] << 16) + (atariBpb.serial_no[1] << 8) +
                                              atariBpb.serial_no[2])
                        };
                    }

                    XmlFsType.SystemIdentifier = StringHandlers.CToString(atariBpb.oem_name);
                    if (string.IsNullOrEmpty(XmlFsType.SystemIdentifier))
                    {
                        XmlFsType.SystemIdentifier = null;
                    }
                }
                else if (fakeBpb.oem_name != null)
                {
                    if (fakeBpb.oem_name[5] != 0x49 || fakeBpb.oem_name[6] != 0x48 || fakeBpb.oem_name[7] != 0x43)
                    {
                        // Later versions of Windows create a DOS 3 BPB without OEM name on 8 sectors/track floppies
                        // OEM ID should be ASCII, otherwise ignore it
                        if (fakeBpb.oem_name[0] >= 0x20 && fakeBpb.oem_name[0] <= 0x7F && fakeBpb.oem_name[1] >= 0x20 &&
                            fakeBpb.oem_name[1] <= 0x7F && fakeBpb.oem_name[2] >= 0x20 && fakeBpb.oem_name[2] <= 0x7F &&
                            fakeBpb.oem_name[3] >= 0x20 && fakeBpb.oem_name[3] <= 0x7F && fakeBpb.oem_name[4] >= 0x20 &&
                            fakeBpb.oem_name[4] <= 0x7F && fakeBpb.oem_name[5] >= 0x20 && fakeBpb.oem_name[5] <= 0x7F &&
                            fakeBpb.oem_name[6] >= 0x20 && fakeBpb.oem_name[6] <= 0x7F && fakeBpb.oem_name[7] >= 0x20 &&
                            fakeBpb.oem_name[7] <= 0x7F)
                        {
                            XmlFsType.SystemIdentifier = StringHandlers.CToString(fakeBpb.oem_name);
                        }
                        else if (fakeBpb.oem_name[0] < 0x20 && fakeBpb.oem_name[1] >= 0x20 &&
                                 fakeBpb.oem_name[1] <= 0x7F && fakeBpb.oem_name[2] >= 0x20 &&
                                 fakeBpb.oem_name[2] <= 0x7F && fakeBpb.oem_name[3] >= 0x20 &&
                                 fakeBpb.oem_name[3] <= 0x7F && fakeBpb.oem_name[4] >= 0x20 &&
                                 fakeBpb.oem_name[4] <= 0x7F && fakeBpb.oem_name[5] >= 0x20 &&
                                 fakeBpb.oem_name[5] <= 0x7F && fakeBpb.oem_name[6] >= 0x20 &&
                                 fakeBpb.oem_name[6] <= 0x7F && fakeBpb.oem_name[7] >= 0x20 &&
                                 fakeBpb.oem_name[7] <= 0x7F)
                        {
                            XmlFsType.SystemIdentifier = StringHandlers.CToString(fakeBpb.oem_name, Encoding, start: 1);
                        }
                    }

                    if (fakeBpb.signature == 0x28 || fakeBpb.signature == 0x29)
                    {
                        XmlFsType.VolumeSerial = $"{fakeBpb.serial_no:X8}";
                        statfs.Id = new FileSystemId {
                            IsInt = true, Serial32 = fakeBpb.serial_no
                        };
                    }
                }

                if (bpbKind != BpbKind.Human)
                {
                    if (fakeBpb.sectors == 0)
                    {
                        XmlFsType.Clusters = fakeBpb.spc == 0 ? fakeBpb.big_sectors : fakeBpb.big_sectors / fakeBpb.spc;
                    }
                    else
                    {
                        XmlFsType.Clusters =
                            (ulong)(fakeBpb.spc == 0 ? fakeBpb.sectors : fakeBpb.sectors / fakeBpb.spc);
                    }
                }
                else
                {
                    XmlFsType.Clusters = humanBpb.clusters == 0 ? humanBpb.big_clusters : humanBpb.clusters;
                }

                sectorsPerCluster     = fakeBpb.spc;
                XmlFsType.ClusterSize = (uint)(fakeBpb.bps * fakeBpb.spc);
                reservedSectors       = fakeBpb.rsectors;
                sectorsPerFat         = fakeBpb.spfat;

                if (fakeBpb.signature == 0x28 || fakeBpb.signature == 0x29 || andosOemCorrect)
                {
                    if ((fakeBpb.flags & 0xF8) == 0x00)
                    {
                        if ((fakeBpb.flags & 0x01) == 0x01)
                        {
                            XmlFsType.Dirty = true;
                        }
                    }

                    if (fakeBpb.signature == 0x29 || andosOemCorrect)
                    {
                        XmlFsType.VolumeName = Encoding.ASCII.GetString(fakeBpb.volume_label);
                    }
                }

                // Workaround that PCExchange jumps into "FAT16   "...
                if (XmlFsType.SystemIdentifier == "PCX 2.0 ")
                {
                    fakeBpb.jump[1] += 8;
                }

                // Check that jumps to a correct boot code position and has boot signature set.
                // This will mean that the volume will boot, even if just to say "this is not bootable change disk"......
                if (XmlFsType.Bootable == false && fakeBpb.jump != null)
                {
                    XmlFsType.Bootable |=
                        fakeBpb.jump[0] == 0xEB && fakeBpb.jump[1] >= minBootNearJump && fakeBpb.jump[1] < 0x80 ||
                        fakeBpb.jump[0] == 0xE9 && fakeBpb.jump.Length >= 3 &&
                        BitConverter.ToUInt16(fakeBpb.jump, 1) >= minBootNearJump &&
                        BitConverter.ToUInt16(fakeBpb.jump, 1) <= 0x1FC;
                }

                // First root directory sector
                firstRootSector = (ulong)(fakeBpb.spfat * fakeBpb.fats_no + fakeBpb.rsectors) * sectorsPerRealSector +
                                  partition.Start;
                sectorsForRootDirectory = (uint)(fakeBpb.root_ent * 32 / imagePlugin.Info.SectorSize);

                sectorsPerRealSector = fakeBpb.bps / imagePlugin.Info.SectorSize;
                sectorsPerCluster   *= sectorsPerRealSector;
            }

            firstClusterSector += partition.Start;

            image = imagePlugin;

            if (fat32)
            {
                fatEntriesPerSector = imagePlugin.Info.SectorSize / 4;
            }
            else if (fat16)
            {
                fatEntriesPerSector = imagePlugin.Info.SectorSize / 2;
            }
            else
            {
                fatEntriesPerSector = imagePlugin.Info.SectorSize * 2 / 3;
            }
            fatFirstSector = partition.Start + reservedSectors * sectorsPerRealSector;

            rootDirectoryCache = new Dictionary <string, CompleteDirectoryEntry>();
            byte[] rootDirectory = null;

            if (!fat32)
            {
                firstClusterSector = firstRootSector + sectorsForRootDirectory - sectorsPerCluster * 2;
                rootDirectory      = imagePlugin.ReadSectors(firstRootSector, sectorsForRootDirectory);

                if (bpbKind == BpbKind.DecRainbow)
                {
                    MemoryStream rootMs = new MemoryStream();
                    foreach (byte[] tmp in from ulong rootSector in new[] { 0x17, 0x19, 0x1B, 0x1D, 0x1E, 0x20 }
                             select imagePlugin.ReadSector(rootSector))
                    {
                        rootMs.Write(tmp, 0, tmp.Length);
                    }

                    rootDirectory = rootMs.ToArray();
                }
            }
            else
            {
                if (rootDirectoryCluster == 0)
                {
                    return(Errno.InvalidArgument);
                }

                MemoryStream rootMs = new MemoryStream();
                uint[]       rootDirectoryClusters = GetClusters(rootDirectoryCluster);

                foreach (uint cluster in rootDirectoryClusters)
                {
                    byte[] buffer =
                        imagePlugin.ReadSectors(firstClusterSector + cluster * sectorsPerCluster, sectorsPerCluster);

                    rootMs.Write(buffer, 0, buffer.Length);
                }

                rootDirectory = rootMs.ToArray();

                // OS/2 FAT32.IFS uses LFN instead of .LONGNAME
                if (this.@namespace == Namespace.Os2)
                {
                    this.@namespace = Namespace.Os2;
                }
            }

            if (rootDirectory is null)
            {
                return(Errno.InvalidArgument);
            }

            byte[] lastLfnName     = null;
            byte   lastLfnChecksum = 0;

            for (int i = 0; i < rootDirectory.Length; i += Marshal.SizeOf <DirectoryEntry>())
            {
                DirectoryEntry entry =
                    Marshal.ByteArrayToStructureLittleEndian <DirectoryEntry>(rootDirectory, i,
                                                                              Marshal.SizeOf <DirectoryEntry>());

                if (entry.filename[0] == DIRENT_FINISHED)
                {
                    break;
                }

                if (entry.attributes.HasFlag(FatAttributes.LFN))
                {
                    if (this.@namespace != Namespace.Lfn && this.@namespace != Namespace.Ecs)
                    {
                        continue;
                    }

                    LfnEntry lfnEntry =
                        Marshal.ByteArrayToStructureLittleEndian <LfnEntry>(rootDirectory, i,
                                                                            Marshal.SizeOf <LfnEntry>());

                    int lfnSequence = lfnEntry.sequence & LFN_MASK;

                    if ((lfnEntry.sequence & LFN_ERASED) > 0)
                    {
                        continue;
                    }

                    if ((lfnEntry.sequence & LFN_LAST) > 0)
                    {
                        lastLfnName     = new byte[lfnSequence * 26];
                        lastLfnChecksum = lfnEntry.checksum;
                    }

                    if (lastLfnName is null)
                    {
                        continue;
                    }
                    if (lfnEntry.checksum != lastLfnChecksum)
                    {
                        continue;
                    }

                    lfnSequence--;

                    Array.Copy(lfnEntry.name1, 0, lastLfnName, lfnSequence * 26, 10);
                    Array.Copy(lfnEntry.name2, 0, lastLfnName, lfnSequence * 26 + 10, 12);
                    Array.Copy(lfnEntry.name3, 0, lastLfnName, lfnSequence * 26 + 22, 4);

                    continue;
                }

                // Not a correct entry
                if (entry.filename[0] < DIRENT_MIN && entry.filename[0] != DIRENT_E5)
                {
                    continue;
                }

                // Self
                if (Encoding.GetString(entry.filename).TrimEnd() == ".")
                {
                    continue;
                }

                // Parent
                if (Encoding.GetString(entry.filename).TrimEnd() == "..")
                {
                    continue;
                }

                // Deleted
                if (entry.filename[0] == DIRENT_DELETED)
                {
                    continue;
                }

                string filename;

                if (entry.attributes.HasFlag(FatAttributes.VolumeLabel))
                {
                    byte[] fullname = new byte[11];
                    Array.Copy(entry.filename, 0, fullname, 0, 8);
                    Array.Copy(entry.extension, 0, fullname, 8, 3);
                    string volname = Encoding.GetString(fullname).Trim();
                    if (!string.IsNullOrEmpty(volname))
                    {
                        XmlFsType.VolumeName =
                            entry.caseinfo.HasFlag(CaseInfo.AllLowerCase) && this.@namespace == Namespace.Nt
                                ? volname.ToLower()
                                : volname;
                    }

                    if (entry.ctime > 0 && entry.cdate > 0)
                    {
                        XmlFsType.CreationDate = DateHandlers.DosToDateTime(entry.cdate, entry.ctime);
                        if (entry.ctime_ms > 0)
                        {
                            XmlFsType.CreationDate = XmlFsType.CreationDate.AddMilliseconds(entry.ctime_ms * 10);
                        }
                        XmlFsType.CreationDateSpecified = true;
                    }

                    if (entry.mtime > 0 && entry.mdate > 0)
                    {
                        XmlFsType.ModificationDate          = DateHandlers.DosToDateTime(entry.mdate, entry.mtime);
                        XmlFsType.ModificationDateSpecified = true;
                    }

                    continue;
                }

                CompleteDirectoryEntry completeEntry = new CompleteDirectoryEntry {
                    Dirent = entry
                };

                if ((this.@namespace == Namespace.Lfn || this.@namespace == Namespace.Ecs) && lastLfnName != null)
                {
                    byte calculatedLfnChecksum = LfnChecksum(entry.filename, entry.extension);

                    if (calculatedLfnChecksum == lastLfnChecksum)
                    {
                        filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true);

                        completeEntry.Lfn = filename;
                        lastLfnName       = null;
                        lastLfnChecksum   = 0;
                    }
                }

                if (entry.filename[0] == DIRENT_E5)
                {
                    entry.filename[0] = DIRENT_DELETED;
                }

                string name      = Encoding.GetString(entry.filename).TrimEnd();
                string extension = Encoding.GetString(entry.extension).TrimEnd();

                if (this.@namespace == Namespace.Nt)
                {
                    if (entry.caseinfo.HasFlag(CaseInfo.LowerCaseExtension))
                    {
                        extension = extension.ToLower(CultureInfo.CurrentCulture);
                    }

                    if (entry.caseinfo.HasFlag(CaseInfo.LowerCaseBasename))
                    {
                        name = name.ToLower(CultureInfo.CurrentCulture);
                    }
                }

                if (extension != "")
                {
                    filename = name + "." + extension;
                }
                else
                {
                    filename = name;
                }

                completeEntry.Shortname = filename;

                if (this.@namespace == Namespace.Human)
                {
                    HumanDirectoryEntry humanEntry =
                        Marshal.ByteArrayToStructureLittleEndian <HumanDirectoryEntry>(rootDirectory, i,
                                                                                       Marshal
                                                                                       .SizeOf <HumanDirectoryEntry
                                                                                                >());

                    completeEntry.HumanDirent = humanEntry;

                    name      = StringHandlers.CToString(humanEntry.name1, Encoding).TrimEnd();
                    extension = StringHandlers.CToString(humanEntry.extension, Encoding).TrimEnd();
                    string name2 = StringHandlers.CToString(humanEntry.name2, Encoding).TrimEnd();

                    if (extension != "")
                    {
                        filename = name + name2 + "." + extension;
                    }
                    else
                    {
                        filename = name + name2;
                    }

                    completeEntry.HumanName = filename;
                }

                if (!fat32 && filename == "EA DATA. SF")
                {
                    eaDirEntry      = entry;
                    lastLfnName     = null;
                    lastLfnChecksum = 0;

                    if (debug)
                    {
                        rootDirectoryCache[completeEntry.ToString()] = completeEntry;
                    }

                    continue;
                }

                rootDirectoryCache[completeEntry.ToString()] = completeEntry;
                lastLfnName     = null;
                lastLfnChecksum = 0;
            }

            XmlFsType.VolumeName = XmlFsType.VolumeName?.Trim();
            statfs.Blocks        = XmlFsType.Clusters;

            switch (bpbKind)
            {
            case BpbKind.Hardcoded:
                statfs.Type = $"Microsoft FAT{(fat16 ? "16" : "12")}";
                break;

            case BpbKind.Atari:
                statfs.Type = $"Atari FAT{(fat16 ? "16" : "12")}";
                break;

            case BpbKind.Msx:
                statfs.Type = $"MSX FAT{(fat16 ? "16" : "12")}";
                break;

            case BpbKind.Dos2:
            case BpbKind.Dos3:
            case BpbKind.Dos32:
            case BpbKind.Dos33:
            case BpbKind.ShortExtended:
            case BpbKind.Extended:
                statfs.Type = $"Microsoft FAT{(fat16 ? "16" : "12")}";
                break;

            case BpbKind.ShortFat32:
            case BpbKind.LongFat32:
                statfs.Type = XmlFsType.Type == "FAT+" ? "FAT+" : "Microsoft FAT32";
                break;

            case BpbKind.Andos:
                statfs.Type = $"ANDOS FAT{(fat16 ? "16" : "12")}";
                break;

            case BpbKind.Apricot:
                statfs.Type = $"Apricot FAT{(fat16 ? "16" : "12")}";
                break;

            case BpbKind.DecRainbow:
                statfs.Type = $"DEC FAT{(fat16 ? "16" : "12")}";
                break;

            case BpbKind.Human:
                statfs.Type = $"Human68k FAT{(fat16 ? "16" : "12")}";
                break;

            default: throw new ArgumentOutOfRangeException();
            }

            bytesPerCluster = sectorsPerCluster * imagePlugin.Info.SectorSize;

            if (fat12)
            {
                byte[] fatBytes =
                    imagePlugin.ReadSectors(fatFirstSector + (useFirstFat ? 0 : sectorsPerFat), sectorsPerFat);

                fatEntries = new ushort[statfs.Blocks];

                int pos = 0;
                for (int i = 0; i + 3 < fatBytes.Length && pos < fatEntries.Length; i += 3)
                {
                    fatEntries[pos++] = (ushort)(((fatBytes[i + 1] & 0xF) << 8) + fatBytes[i + 0]);
                    fatEntries[pos++] = (ushort)(((fatBytes[i + 1] & 0xF0) >> 4) + (fatBytes[i + 2] << 4));
                }
            }
            else if (fat16)
            {
                DicConsole.DebugWriteLine("FAT plugin", "Reading FAT16");

                byte[] fatBytes =
                    imagePlugin.ReadSectors(fatFirstSector + (useFirstFat ? 0 : sectorsPerFat), sectorsPerFat);

                DicConsole.DebugWriteLine("FAT plugin", "Casting FAT");
                fatEntries = MemoryMarshal.Cast <byte, ushort>(fatBytes).ToArray();
            }

            // TODO: Check how this affects international filenames
            cultureInfo    = new CultureInfo("en-US", false);
            directoryCache = new Dictionary <string, Dictionary <string, CompleteDirectoryEntry> >();

            // Check it is really an OS/2 EA file
            if (eaDirEntry.start_cluster != 0)
            {
                CacheEaData();
                ushort eamagic = BitConverter.ToUInt16(cachedEaData, 0);

                if (eamagic != EADATA_MAGIC)
                {
                    eaDirEntry   = new DirectoryEntry();
                    cachedEaData = null;
                }
                else
                {
                    eaCache = new Dictionary <string, Dictionary <string, byte[]> >();
                }
            }
            else if (fat32)
            {
                eaCache = new Dictionary <string, Dictionary <string, byte[]> >();
            }

            // Check OS/2 .LONGNAME
            if (eaCache != null && (this.@namespace == Namespace.Os2 || this.@namespace == Namespace.Ecs))
            {
                List <KeyValuePair <string, CompleteDirectoryEntry> > rootFilesWithEas =
                    rootDirectoryCache.Where(t => t.Value.Dirent.ea_handle != 0).ToList();

                foreach (KeyValuePair <string, CompleteDirectoryEntry> fileWithEa in rootFilesWithEas)
                {
                    Dictionary <string, byte[]> eas = GetEas(fileWithEa.Value.Dirent.ea_handle);

                    if (eas is null)
                    {
                        continue;
                    }

                    if (!eas.TryGetValue("com.microsoft.os2.longname", out byte[] longnameEa))
Esempio n. 12
0
        public void Open(Stream stream)
        {
            stream.Seek(0, SeekOrigin.Begin);

            byte[] hdrB = new byte[26];
            stream.Read(hdrB, 0, 26);
            _header = Marshal.ByteArrayToStructureBigEndian <AppleSingleHeader>(hdrB);

            AppleSingleEntry[] entries = new AppleSingleEntry[_header.entries];

            for (int i = 0; i < _header.entries; i++)
            {
                byte[] entry = new byte[12];
                stream.Read(entry, 0, 12);
                entries[i] = Marshal.ByteArrayToStructureBigEndian <AppleSingleEntry>(entry);
            }

            _creationTime  = DateTime.UtcNow;
            _lastWriteTime = _creationTime;

            foreach (AppleSingleEntry entry in entries)
            {
                switch ((AppleSingleEntryID)entry.id)
                {
                case AppleSingleEntryID.DataFork:
                    _dataFork = entry;

                    break;

                case AppleSingleEntryID.FileDates:
                    stream.Seek(entry.offset, SeekOrigin.Begin);
                    byte[] datesB = new byte[16];
                    stream.Read(datesB, 0, 16);

                    AppleSingleFileDates dates =
                        Marshal.ByteArrayToStructureBigEndian <AppleSingleFileDates>(datesB);

                    _creationTime  = DateHandlers.MacToDateTime(dates.creationDate);
                    _lastWriteTime = DateHandlers.MacToDateTime(dates.modificationDate);

                    break;

                case AppleSingleEntryID.FileInfo:
                    stream.Seek(entry.offset, SeekOrigin.Begin);
                    byte[] finfo = new byte[entry.length];
                    stream.Read(finfo, 0, finfo.Length);

                    if (_macintoshHome.SequenceEqual(_header.homeFilesystem))
                    {
                        AppleSingleMacFileInfo macinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleSingleMacFileInfo>(finfo);

                        _creationTime  = DateHandlers.MacToDateTime(macinfo.creationDate);
                        _lastWriteTime = DateHandlers.MacToDateTime(macinfo.modificationDate);
                    }
                    else if (_proDosHome.SequenceEqual(_header.homeFilesystem))
                    {
                        AppleSingleProDOSFileInfo prodosinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleSingleProDOSFileInfo>(finfo);

                        _creationTime  = DateHandlers.MacToDateTime(prodosinfo.creationDate);
                        _lastWriteTime = DateHandlers.MacToDateTime(prodosinfo.modificationDate);
                    }
                    else if (_unixHome.SequenceEqual(_header.homeFilesystem))
                    {
                        AppleSingleUnixFileInfo unixinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleSingleUnixFileInfo>(finfo);

                        _creationTime  = DateHandlers.UnixUnsignedToDateTime(unixinfo.creationDate);
                        _lastWriteTime = DateHandlers.UnixUnsignedToDateTime(unixinfo.modificationDate);
                    }
                    else if (_dosHome.SequenceEqual(_header.homeFilesystem))
                    {
                        AppleSingleDOSFileInfo dosinfo =
                            Marshal.ByteArrayToStructureBigEndian <AppleSingleDOSFileInfo>(finfo);

                        _lastWriteTime =
                            DateHandlers.DosToDateTime(dosinfo.modificationDate, dosinfo.modificationTime);
                    }

                    break;

                case AppleSingleEntryID.ResourceFork:
                    _rsrcFork = entry;

                    break;
                }
            }

            stream.Seek(0, SeekOrigin.Begin);
            _opened   = true;
            _isStream = true;
            _stream   = stream;
        }