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