diff --git a/Content/UniversalEditor.Content.PlatformIndependent/Extensions/Amiga/Associations/FileSystem/ADFDiskImage.uexml b/Content/UniversalEditor.Content.PlatformIndependent/Extensions/Amiga/Associations/FileSystem/ADFDiskImage.uexml new file mode 100644 index 00000000..58aa6fda --- /dev/null +++ b/Content/UniversalEditor.Content.PlatformIndependent/Extensions/Amiga/Associations/FileSystem/ADFDiskImage.uexml @@ -0,0 +1,20 @@ + + + + + + + + *.adf + + + + + + + + + + + + \ No newline at end of file diff --git a/Content/UniversalEditor.Content.PlatformIndependent/UniversalEditor.Content.PlatformIndependent.csproj b/Content/UniversalEditor.Content.PlatformIndependent/UniversalEditor.Content.PlatformIndependent.csproj index c29376fa..78141bf4 100644 --- a/Content/UniversalEditor.Content.PlatformIndependent/UniversalEditor.Content.PlatformIndependent.csproj +++ b/Content/UniversalEditor.Content.PlatformIndependent/UniversalEditor.Content.PlatformIndependent.csproj @@ -733,6 +733,7 @@ + @@ -795,6 +796,9 @@ + + + diff --git a/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/ADFDiskImageBlockSecondaryType.cs b/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/ADFDiskImageBlockSecondaryType.cs new file mode 100644 index 00000000..c919b4c7 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/ADFDiskImageBlockSecondaryType.cs @@ -0,0 +1,30 @@ +// +// ADFDiskImageBlockSecondaryType.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +namespace UniversalEditor.Plugins.Amiga.DataFormats.FileSystem.ADF +{ + public enum ADFDiskImageBlockSecondaryType + { + Root = 1, + Directory = 2, + File = -3 + } +} diff --git a/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/ADFDiskImageDataFormat.cs b/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/ADFDiskImageDataFormat.cs new file mode 100644 index 00000000..f6befbc5 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/ADFDiskImageDataFormat.cs @@ -0,0 +1,237 @@ +// +// ADFDiskImageDataFormat.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +using UniversalEditor.Accessors; +using UniversalEditor.IO; +using UniversalEditor.ObjectModels.FileSystem; +using UniversalEditor.Plugins.Amiga.DataFormats.FileSystem.ADF.Internal; +using UniversalEditor.UserInterface; + +namespace UniversalEditor.Plugins.Amiga.DataFormats.FileSystem.ADF +{ + public class ADFDiskImageDataFormat : DataFormat + { + private static DataFormatReference _dfr = null; + protected override DataFormatReference MakeReferenceInternal() + { + if (_dfr == null) + { + _dfr = base.MakeReferenceInternal(); + _dfr.Capabilities.Add(typeof(FileSystemObjectModel), DataFormatCapabilities.All); + } + return _dfr; + } + + public int BytesPerSector { get; set; } = 512; + [CustomOptionText("Volume _name")] + public string VolumeName { get; set; } = String.Empty; + + protected override void LoadInternal(ref ObjectModel objectModel) + { + FileSystemObjectModel fsom = (objectModel as FileSystemObjectModel); + if (fsom == null) throw new ObjectModelNotSupportedException(); + + Reader reader = Accessor.Reader; + reader.Endianness = Endianness.BigEndian; + + // boot block + string diskType = reader.ReadFixedLengthString(3); + if (diskType == "DOS") + { + + } + else if (diskType == "PFS") + { + throw new NotImplementedException("Professional File System not implemented ... yet!"); + } + else + { + throw new InvalidDataFormatException(); + } + + ADFDiskImageFlags flags = (ADFDiskImageFlags)reader.ReadByte(); + uint checksum = reader.ReadUInt32(); + uint rootblock = reader.ReadUInt32(); // value is 880 for DD and HD + byte[] bootblock = reader.ReadBytes(1012); // The size for a floppy disk is 1012; for a harddisk it is (DosEnvVec->Bootblocks * BSIZE) - 12 + + uint checksumVerify = CalculateChecksum(diskType, flags, rootblock, bootblock); + if (checksumVerify != checksum) + { + HostApplication.Messages.Add(HostApplicationMessageSeverity.Warning, "boot block checksum mismatch", Accessor.GetFileName()); + } + + reader.Seek(CalculateSectorOffset(rootblock), SeekOrigin.Begin); + + uint[] hashTableEntries = null; + #region Rootblock + { + Internal.ADFBlock block = ReadBlock(reader); + VolumeName = block.filename; + hashTableEntries = block.hashTableEntries; + /* + ulong 1 next_hash unused (value = 0) + ulong 1 parent_dir unused (value = 0) +BSIZE- 8/-0x08 ulong 1 extension FFS: first directory cache block, + 0 otherwise +BSIZE- 4/-0x04 ulong 1 sec_type block secondary type = ST_ROOT + (value 1) + */ + LoadDirectory(reader, block, fsom); + } + #endregion + } + + private void LoadDirectory(Reader reader, ADFBlock block, IFileSystemContainer fsom) + { + for (int i = 0; i < block.hashTableEntries.Length; i++) + { + if (block.hashTableEntries[i] == 0) continue; + + reader.Seek(CalculateSectorOffset(block.hashTableEntries[i]), SeekOrigin.Begin); + + Internal.ADFBlock block2 = ReadBlock(reader); + if (block2.sec_type == ADFDiskImageBlockSecondaryType.File) + { + File f = fsom.AddFile(block2.filename); + f.Properties["block"] = block2; + f.Properties["reader"] = reader; + f.DataRequest += F_DataRequest; + } + else if (block2.sec_type == ADFDiskImageBlockSecondaryType.Directory) + { + Folder folder = fsom.Folders.Add(block2.filename); + LoadDirectory(reader, block2, folder); + } + } + } + + private void F_DataRequest(object sender, DataRequestEventArgs e) + { + File f = (File)sender; + Internal.ADFBlock block = f.GetProperty("block"); + Reader reader = f.GetProperty("reader"); + + byte[] data = new byte[0]; + int len = 0; + for (int i = 0; i < block.hashTableEntries.Length; i++) + { + if (block.hashTableEntries[i] == 0) continue; + reader.Seek(CalculateSectorOffset(block.hashTableEntries[i]), SeekOrigin.Begin); + byte[] blockData = reader.ReadBytes(BytesPerSector); + Array.Resize(ref data, data.Length + blockData.Length); + Array.Copy(blockData, 0, data, len, BytesPerSector); + len += BytesPerSector; + } + e.Data = data; + } + + + private Internal.ADFBlock ReadBlock(Reader reader) + { + Internal.ADFBlock block = new Internal.ADFBlock(); + block.blockPrimaryType = reader.ReadUInt32(); // 2 = T_HEADER + block.headerKey = reader.ReadUInt32(); + block.highSeq = reader.ReadUInt32(); // file: number of data block ptr stored here + block.hashTableSize = reader.ReadUInt32(); + block.firstData = reader.ReadUInt32(); // unused (value 0) + block.blockChecksum = reader.ReadUInt32(); + + int hashTableEntryCount = (BytesPerSector / 4) - 56; + block.hashTableEntries = reader.ReadUInt32Array(hashTableEntryCount); // 72 for floppy disk, (BSIZE/4) - 56 for hard disks + + block.bm_flag = reader.ReadUInt32(); // -1 means VALID + block.bm_pages = reader.ReadUInt32Array(25); // bitmap blocks pointers (first one at bm_pages[0]) + block.bm_ext = reader.ReadUInt32(); // first bitmap extension block (hard disks only) + + // last root alteration date + block.r_days = reader.ReadUInt32(); // days since 1 jan 78 + block.r_mins = reader.ReadUInt32(); // minutes past midnight + block.r_ticks = reader.ReadUInt32(); // ticks (1/50 sec) past last minute + byte name_len = reader.ReadByte(); // volume name length + block.filename = reader.ReadFixedLengthString(30).TrimNull(); // volume name + block.unused1 = reader.ReadByte(); // set to 0 + block.unused2 = reader.ReadUInt32(); // set to 0 + block.unused3 = reader.ReadUInt32(); // set to 0 + + // last disk alteration date + block.v_days = reader.ReadUInt32(); // days since 1 jan 78 + block.v_mins = reader.ReadUInt32(); + block.v_ticks = reader.ReadUInt32(); + + // filesystem creation date + block.c_days = reader.ReadUInt32(); + block.c_mins = reader.ReadUInt32(); + block.c_ticks = reader.ReadUInt32(); + + block.next_hash = reader.ReadUInt32(); + block.parent_dir = reader.ReadUInt32(); + + block.extension = reader.ReadUInt32(); + block.sec_type = (ADFDiskImageBlockSecondaryType)reader.ReadUInt32(); + return block; + } + + private long CalculateSectorOffset(long sector) + { + return BytesPerSector * sector; + } + + private uint CalculateChecksum(string diskType, ADFDiskImageFlags flags, uint rootblock, byte[] data) + { + MemoryAccessor ma = new MemoryAccessor(); + ma.Writer.Endianness = Endianness.BigEndian; + ma.Writer.WriteFixedLengthString(diskType, 3); + ma.Writer.WriteByte((byte)flags); + ma.Writer.WriteUInt32(0); + ma.Writer.WriteUInt32(rootblock); + ma.Writer.WriteBytes(data); + ma.Flush(); + ma.Close(); + return CalculateChecksum(ma.ToArray()); + } + private uint CalculateChecksum(byte[] data) + { + return CalculateChecksum(new MemoryAccessor(data)); + } + private uint CalculateChecksum(MemoryAccessor ma) + { + uint newsum = 0; + ma.Reader.Endianness = Endianness.BigEndian; + for (int i = 0; i < ma.Length / 4; i++) + { + uint d = ma.Reader.ReadUInt32(); + if ((UInt32.MaxValue - newsum) < d) /* overflow */ + newsum++; + newsum += d; + } + newsum = ~newsum; /* not */ + return newsum; + } + + protected override void SaveInternal(ObjectModel objectModel) + { + FileSystemObjectModel fsom = (objectModel as FileSystemObjectModel); + if (fsom == null) throw new ObjectModelNotSupportedException(); + + + } + } +} diff --git a/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/ADFDiskImageFlags.cs b/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/ADFDiskImageFlags.cs new file mode 100644 index 00000000..3e4be101 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/ADFDiskImageFlags.cs @@ -0,0 +1,45 @@ +// +// ADFDiskImageFlags.cs - indicates attributes for an ADF disk image file +// +// Author: +// Michael Becker +// +// Copyright (c) 2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; + +namespace UniversalEditor.Plugins.Amiga.DataFormats.FileSystem.ADF +{ + /// + /// Indicates attributes for an ADF disk image file. + /// + [Flags()] + public enum ADFDiskImageFlags + { + /// + /// Indicates the disk image uses the Fast File System (AmigaDOS 2.04), as opposed to the Original File System (AmigaDOS 1.2). + /// + FFS = 0x01, + /// + /// Indicates the disk image supports international characters mode only. + /// + InternationalOnly = 0x02, + /// + /// Indicates the disk image supports directory cache mode and international characters. This mode speeds up directory listing, but uses more disk space. + /// + DirectoryCacheModeAndInternational = 0x04 + } +} diff --git a/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/Internal/ADFBlock.cs b/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/Internal/ADFBlock.cs new file mode 100644 index 00000000..b4f98099 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Amiga/DataFormats/FileSystem/ADF/Internal/ADFBlock.cs @@ -0,0 +1,54 @@ +// +// ADFBlock.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +namespace UniversalEditor.Plugins.Amiga.DataFormats.FileSystem.ADF.Internal +{ + public class ADFBlock + { + internal uint blockPrimaryType; + internal uint headerKey; + internal uint highSeq; + internal uint hashTableSize; + internal uint firstData; + internal uint blockChecksum; + internal uint[] hashTableEntries; + internal uint bm_flag; + internal uint[] bm_pages; + internal uint bm_ext; + internal uint r_days; + internal uint r_mins; + internal uint r_ticks; + internal string filename; + internal byte unused1; + internal uint unused2; + internal uint unused3; + internal uint v_days; + internal uint v_mins; + internal uint v_ticks; + internal uint c_days; + internal uint c_mins; + internal uint c_ticks; + internal uint next_hash; + internal uint parent_dir; + internal uint extension; + internal ADFDiskImageBlockSecondaryType sec_type; + } +} diff --git a/Plugins/UniversalEditor.Plugins.Amiga/Properties/AssemblyInfo.cs b/Plugins/UniversalEditor.Plugins.Amiga/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..c4bb5235 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Amiga/Properties/AssemblyInfo.cs @@ -0,0 +1,46 @@ +// +// AssemblyInfo.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("UniversalEditor.Plugins.Amiga")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Mike Becker's Software")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Mike Becker's Software")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/Plugins/UniversalEditor.Plugins.Amiga/UniversalEditor.Plugins.Amiga.csproj b/Plugins/UniversalEditor.Plugins.Amiga/UniversalEditor.Plugins.Amiga.csproj new file mode 100644 index 00000000..069926f1 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Amiga/UniversalEditor.Plugins.Amiga.csproj @@ -0,0 +1,61 @@ + + + + Debug + AnyCPU + {BCEB66A6-24E3-4877-B8D4-83FCF8EC748B} + Library + UniversalEditor.Plugins.Amiga + UniversalEditor.Plugins.Amiga + v4.7 + 4.0.2019.12 + + + true + full + false + ..\..\Output\Debug\Plugins + DEBUG; + prompt + 4 + false + + + true + ..\..\Output\Release\Plugins + prompt + 4 + false + + + + + + + + + + + + + + + + + + + + {2D4737E6-6D95-408A-90DB-8DFF38147E85} + UniversalEditor.Core + + + {30467E5C-05BC-4856-AADC-13906EF4CADD} + UniversalEditor.Essential + + + {8622EBC4-8E20-476E-B284-33D472081F5C} + UniversalEditor.UserInterface + + + + \ No newline at end of file diff --git a/UniversalEditor.sln b/UniversalEditor.sln index fb5a15e4..02133080 100644 --- a/UniversalEditor.sln +++ b/UniversalEditor.sln @@ -167,6 +167,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalEditor.Plugins.Ado EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalEditor.Plugins.Vocaloid", "Plugins\UniversalEditor.Plugins.Vocaloid\UniversalEditor.Plugins.Vocaloid.csproj", "{C8953DB2-AE48-4F04-87EC-549E6A3E30D8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalEditor.Plugins.Amiga", "Plugins\UniversalEditor.Plugins.Amiga\UniversalEditor.Plugins.Amiga.csproj", "{BCEB66A6-24E3-4877-B8D4-83FCF8EC748B}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalEditor.Plugins.Designer.UserInterface", "Plugins.UserInterface\UniversalEditor.Plugins.Designer.UserInterface\UniversalEditor.Plugins.Designer.UserInterface.csproj", "{08168BEA-E652-4493-8D89-5AB72B225841}" EndProject Global @@ -485,6 +487,10 @@ Global {C8953DB2-AE48-4F04-87EC-549E6A3E30D8}.Debug|Any CPU.Build.0 = Debug|Any CPU {C8953DB2-AE48-4F04-87EC-549E6A3E30D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {C8953DB2-AE48-4F04-87EC-549E6A3E30D8}.Release|Any CPU.Build.0 = Release|Any CPU + {BCEB66A6-24E3-4877-B8D4-83FCF8EC748B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCEB66A6-24E3-4877-B8D4-83FCF8EC748B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCEB66A6-24E3-4877-B8D4-83FCF8EC748B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCEB66A6-24E3-4877-B8D4-83FCF8EC748B}.Release|Any CPU.Build.0 = Release|Any CPU {08168BEA-E652-4493-8D89-5AB72B225841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {08168BEA-E652-4493-8D89-5AB72B225841}.Debug|Any CPU.Build.0 = Debug|Any CPU {08168BEA-E652-4493-8D89-5AB72B225841}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -567,6 +573,7 @@ Global {553DFD37-805C-4553-980A-D5696D88241A} = {2ED32D16-6C06-4450-909A-40D32DA67FB4} {FD46EA8D-7711-4BAF-A486-719D754504F8} = {2ED32D16-6C06-4450-909A-40D32DA67FB4} {C8953DB2-AE48-4F04-87EC-549E6A3E30D8} = {2ED32D16-6C06-4450-909A-40D32DA67FB4} + {BCEB66A6-24E3-4877-B8D4-83FCF8EC748B} = {2ED32D16-6C06-4450-909A-40D32DA67FB4} {08168BEA-E652-4493-8D89-5AB72B225841} = {7B535D74-5496-4802-B809-89ED88274A91} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution