// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; namespace Spludlow.Email { /// /// Read 'AppleDouble' files, normally emailed from a Macintosh /// Works with the 'MacBinary' class to create a file that can be extracted on a Macintosh to restore the data fork, File Type, and Creator Code /// You dont see as many AppleDoubles these days /// public class AppleDouble { public class AppleEntry { public uint Id; public uint OffSet; public uint Length; } public uint MagicNumber; public uint VersionNumber; public ushort NumberOfEntries; public AppleEntry[] AppleEntries; public byte[] DataFork; public byte[] ResourceFork; public string FileName; public byte[] FileNameData; public byte[] FileType; public byte[] CreatorCode; public byte[] DateInfo; public uint CreationDate; public uint ModificationDate; public uint BackUpDate; public uint AccessDate; public AppleDouble() { } public void Load(string filename) { using (BinaryReader headReader = new BinaryReader(new FileStream(filename, FileMode.Open))) { this.MagicNumber = Spludlow.Io.BinaryIO.ReadUInt32(headReader, true); this.VersionNumber = Spludlow.Io.BinaryIO.ReadUInt32(headReader, true); headReader.ReadBytes(16); this.NumberOfEntries = Spludlow.Io.BinaryIO.ReadUInt16(headReader, true); this.AppleEntries = new AppleEntry[this.NumberOfEntries]; for (int index = 0; index < this.NumberOfEntries; ++index) { AppleEntry appleEntry = new AppleEntry(); appleEntry.Id = Spludlow.Io.BinaryIO.ReadUInt32(headReader, true); appleEntry.OffSet = Spludlow.Io.BinaryIO.ReadUInt32(headReader, true); appleEntry.Length = Spludlow.Io.BinaryIO.ReadUInt32(headReader, true); this.AppleEntries[index] = appleEntry; } foreach (AppleEntry appleEntry in this.AppleEntries) { byte[] entryData = new byte[appleEntry.Length]; headReader.BaseStream.Seek(appleEntry.OffSet, SeekOrigin.Begin); headReader.Read(entryData, 0, entryData.Length); switch (appleEntry.Id) { case 0: break; case 1: //Data Fork 1 Data fork this.DataFork = entryData; break; case 2: //Resource Fork 2 Resource fork this.ResourceFork = entryData; break; case 3: //Real Name 3 File’s name as created on home file system this.FileName = System.Text.Encoding.ASCII.GetString(entryData); this.FileNameData = entryData; break; case 4: //Comment 4 Standard Macintosh comment break; case 5: //Icon, B&W 5 Standard Macintosh black and white icon break; case 6: //Icon, Color 6 Macintosh color icon break; case 7: // Not defined break; case 8: //File Dates Info 8 File creation date, modification date, and so on if (entryData.Length != 16) throw new ApplicationException("Apple Single/Double Date section is not 16 bytes"); this.CreationDate = Spludlow.Io.BinaryIO.ReadUInt32(entryData, 0, true); this.ModificationDate = Spludlow.Io.BinaryIO.ReadUInt32(entryData, 4, true); this.BackUpDate = Spludlow.Io.BinaryIO.ReadUInt32(entryData, 8, true); this.AccessDate = Spludlow.Io.BinaryIO.ReadUInt32(entryData, 12, true); break; case 9: //Finder Info 9 Standard Macintosh Finder information this.FileType = new byte[4]; this.FileType[0] = entryData[0]; this.FileType[1] = entryData[1]; this.FileType[2] = entryData[2]; this.FileType[3] = entryData[3]; this.CreatorCode = new byte[4]; this.CreatorCode[0] = entryData[4]; this.CreatorCode[1] = entryData[5]; this.CreatorCode[2] = entryData[6]; this.CreatorCode[3] = entryData[7]; break; case 10: //Macintosh File Info 10 Macintosh file information, attributes, and so on break; case 11: //ProDOS File Info 11 ProDOS file information, attributes, and so on break; case 12: //MS-DOS File Info 12 MS-DOS file information, attributes, and so on break; case 13: //Short Name 13 AFP short name break; case 14: //AFP File Info 14 AFP file information, attributes, and so on break; case 15: //Directory ID 15 AFP directory ID break; default: break; } } } if (this.FileType == null) this.FileType = Encoding.ASCII.GetBytes(" "); if (this.CreatorCode == null) this.CreatorCode = Encoding.ASCII.GetBytes(" "); if (this.ResourceFork == null) this.ResourceFork = new byte[0]; if (this.DataFork == null) this.DataFork = new byte[0]; } } }