diff --git a/Content/UniversalEditor.Content.PlatformIndependent/Extensions/GameDeveloper/Extensions/Webfoot/Associations/DAT.uexml b/Content/UniversalEditor.Content.PlatformIndependent/Extensions/GameDeveloper/Extensions/Webfoot/Associations/DAT.uexml
new file mode 100644
index 00000000..8d3aa00d
--- /dev/null
+++ b/Content/UniversalEditor.Content.PlatformIndependent/Extensions/GameDeveloper/Extensions/Webfoot/Associations/DAT.uexml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+ *.dat
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 2d7a4e52..e412ce99 100644
--- a/Content/UniversalEditor.Content.PlatformIndependent/UniversalEditor.Content.PlatformIndependent.csproj
+++ b/Content/UniversalEditor.Content.PlatformIndependent/UniversalEditor.Content.PlatformIndependent.csproj
@@ -717,6 +717,7 @@
+
@@ -767,6 +768,8 @@
+
+
diff --git a/Plugins/UniversalEditor.Plugins.Webfoot/DataFormats/FileSystem/Webfoot/DATDataFormat.cs b/Plugins/UniversalEditor.Plugins.Webfoot/DataFormats/FileSystem/Webfoot/DATDataFormat.cs
new file mode 100644
index 00000000..8ec820a9
--- /dev/null
+++ b/Plugins/UniversalEditor.Plugins.Webfoot/DataFormats/FileSystem/Webfoot/DATDataFormat.cs
@@ -0,0 +1,187 @@
+//
+// DATDataFormat.cs
+//
+// Author:
+// Mike Becker
+//
+// Copyright (c) 2020 Mike Becker
+//
+// 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 System.Collections.Generic;
+using UniversalEditor.Accessors;
+using UniversalEditor.IO;
+using UniversalEditor.ObjectModels.FileSystem;
+
+namespace UniversalEditor.Plugins.Webfoot.DataFormats.FileSystem.Webfoot
+{
+ public class DATDataFormat : DataFormat
+ {
+ private static DataFormatReference _dfr = null;
+ protected override DataFormatReference MakeReferenceInternal()
+ {
+ if (_dfr == null)
+ {
+ _dfr = base.MakeReferenceInternal();
+ _dfr.Capabilities.Add(typeof(FileSystemObjectModel), DataFormatCapabilities.All);
+ _dfr.ExportOptions.Add(new CustomOptionNumber("Key", "Encryption _key", 0xAA, 0x00, 0xFF));
+ }
+ return _dfr;
+ }
+
+ public byte Key { get; set; } = 0xAA;
+
+ protected override void LoadInternal(ref ObjectModel objectModel)
+ {
+ FileSystemObjectModel fsom = (objectModel as FileSystemObjectModel);
+ if (fsom == null) throw new ObjectModelNotSupportedException();
+
+ fsom.AdditionalDetails.Add("Webfoot.FileKey", "File key");
+
+ Reader reader = Accessor.Reader;
+ reader.Seek(-13, SeekOrigin.End);
+
+ reader.Transformations.Clear();
+ reader.Transformations.Add(xort);
+
+ xort.XorKey = null;
+
+ // not sure what this field is, crc or something else? but we can keep it in sync in case we open a DAT file and re-save it
+ uint unknown = reader.ReadUInt32();
+ fsom.SetCustomProperty(MakeReference(), "Unknown", unknown);
+
+ uint directoryOffset = reader.ReadUInt32();
+ uint fileCount = reader.ReadUInt32();
+ Key = reader.ReadByte();
+ xort.XorKey = new byte[] { Key };
+
+ reader.Seek(directoryOffset, SeekOrigin.Begin);
+ for (uint i = 0; i < fileCount; i++)
+ {
+ string fileName1 = reader.ReadNullTerminatedString();
+ string fileName2 = reader.ReadNullTerminatedString();
+
+ uint offset = reader.ReadUInt32();
+ uint length = reader.ReadUInt32();
+
+ // not sure what this field is, crc or something else? but we can keep it in sync in case we open a DAT file and re-save it
+ // using the epoch 1990-01-01 it comes up as September 1999, which kind of makes sense...
+ // ... but is the same in 3DFROG.DAT and MISSILE.DAT, one would expect them to be diferent...
+ uint maybeTimestamp = reader.ReadUInt32();
+
+ DateTime dt = new DateTime(1990, 01, 01);
+ dt = dt.AddSeconds(maybeTimestamp);
+
+
+ byte fileKey = reader.ReadByte();
+
+ File f = fsom.AddFile(fileName1);
+ f.ModificationTimestamp = dt;
+ f.Properties["maybeTimestamp"] = maybeTimestamp;
+ f.SetAdditionalDetail("Webfoot.FileKey", fileKey.ToString("X").PadRight(2, '0'));
+ f.Properties["reader"] = reader;
+ f.Properties["xort"] = xort;
+ f.Properties["offset"] = offset;
+ f.Properties["length"] = length;
+ f.Properties["key"] = fileKey;
+ f.Properties["index"] = i;
+ f.DataRequest += F_DataRequest;
+ f.Size = length;
+ }
+ }
+
+ void F_DataRequest(object sender, DataRequestEventArgs e)
+ {
+ File f = (File)sender;
+ Reader reader = (Reader)f.Properties["reader"];
+ IO.Transformations.XorTransformation xort = (IO.Transformations.XorTransformation)f.Properties["xort"];
+
+ uint offset = (uint)f.Properties["offset"];
+ uint length = (uint)f.Properties["length"];
+ byte fileKey = (byte)f.Properties["key"];
+
+ reader.Seek(offset, SeekOrigin.Begin);
+ xort.XorKey = new byte[] { fileKey };
+ byte[] data = reader.ReadBytes(length);
+ e.Data = data;
+ }
+
+ private IO.Transformations.XorTransformation xort = new IO.Transformations.XorTransformation();
+ private Checksum.Modules.CRC32.CRC32ChecksumModule crc = new Checksum.Modules.CRC32.CRC32ChecksumModule();
+
+ protected override void SaveInternal(ObjectModel objectModel)
+ {
+ FileSystemObjectModel fsom = (objectModel as FileSystemObjectModel);
+ if (fsom == null) throw new ObjectModelNotSupportedException();
+
+ Writer writer = Accessor.Writer;
+
+ writer.Transformations.Clear();
+ writer.Transformations.Add(xort);
+
+ File[] files = fsom.GetAllFiles("\\");
+ Array.Sort(files, (x, y) => x.GetProperty("index").CompareTo(y.GetProperty("index")));
+
+ for (uint i = 0; i < files.Length; i++)
+ {
+ byte key = Byte.Parse(files[i].GetAdditionalDetail("Webfoot.FileKey", "00"), System.Globalization.NumberStyles.HexNumber);
+ xort.XorKey = new byte[] { key };
+
+ byte[] filedata = files[i].GetData();
+ writer.WriteBytes(filedata);
+ }
+
+ uint directoryOffset = (uint)writer.Accessor.Position;
+
+ MemoryAccessor maDirectory = new MemoryAccessor();
+ maDirectory.Writer.Transformations.Add(xort);
+
+ uint offset = 0;
+ for (int i = 0; i < files.Length; i++)
+ {
+ byte key = Byte.Parse(files[i].GetAdditionalDetail("Webfoot.FileKey", "00"), System.Globalization.NumberStyles.HexNumber);
+ uint length = (uint)files[i].GetData().Length;
+
+ // must be reset after each call to GetData because the DataRequest twiddles it
+ xort.XorKey = new byte[] { Key };
+
+ maDirectory.Writer.WriteNullTerminatedString(files[i].Name);
+ maDirectory.Writer.WriteNullTerminatedString(files[i].Name);
+ maDirectory.Writer.WriteUInt32(offset);
+ maDirectory.Writer.WriteUInt32(length);
+
+ // not sure what this field is, crc or something else? but we can keep it in sync in case we open a DAT file and re-save it
+ // using the epoch 1990-01-01 it comes up as September 1999, which kind of makes sense...
+ // ... but is the same in 3DFROG.DAT and MISSILE.DAT, one would expect them to be diferent...
+ maDirectory.Writer.WriteUInt32(files[i].GetProperty("maybeTimestamp"));
+
+ maDirectory.Writer.WriteByte(key);
+ offset += length;
+ }
+ maDirectory.Flush();
+ xort.XorKey = null;
+
+ byte[] directory = maDirectory.ToArray();
+ writer.WriteBytes(directory);
+
+ // not sure what this field is, crc or something else? but we can keep it in sync in case we open a DAT file and re-save it
+ uint unknown = fsom.GetCustomProperty(MakeReference(), "Unknown", 0);
+ writer.WriteUInt32(unknown);
+
+ writer.WriteUInt32(directoryOffset);
+ writer.WriteUInt32((uint)files.Length);
+ writer.WriteByte(Key);
+ }
+ }
+}
diff --git a/Plugins/UniversalEditor.Plugins.Webfoot/Properties/AssemblyInfo.cs b/Plugins/UniversalEditor.Plugins.Webfoot/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..6f262a20
--- /dev/null
+++ b/Plugins/UniversalEditor.Plugins.Webfoot/Properties/AssemblyInfo.cs
@@ -0,0 +1,46 @@
+//
+// AssemblyInfo.cs
+//
+// Author:
+// Mike Becker
+//
+// Copyright (c) 2020 Mike Becker
+//
+// 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.Webfoot")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("Mike Becker")]
+[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.Webfoot/UniversalEditor.Plugins.Webfoot.csproj b/Plugins/UniversalEditor.Plugins.Webfoot/UniversalEditor.Plugins.Webfoot.csproj
new file mode 100644
index 00000000..ced0199b
--- /dev/null
+++ b/Plugins/UniversalEditor.Plugins.Webfoot/UniversalEditor.Plugins.Webfoot.csproj
@@ -0,0 +1,57 @@
+
+
+
+ Debug
+ AnyCPU
+ {6C55828C-B0BA-440F-9279-8C4CCE3B361E}
+ Library
+ UniversalEditor.Plugins.Webfoot
+ UniversalEditor.Plugins.Webfoot
+ 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
+
+
+ {0F7D5BD4-7970-412F-ABD7-0A098BB01ACE}
+ UniversalEditor.Checksum
+
+
+
+
\ No newline at end of file
diff --git a/UniversalEditor.sln b/UniversalEditor.sln
index 06067f3d..247637cc 100644
--- a/UniversalEditor.sln
+++ b/UniversalEditor.sln
@@ -161,6 +161,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalEditor.Plugins.Rav
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalEditor.Plugins.UnrealEngine.UserInterface", "Plugins.UserInterface\UniversalEditor.Plugins.UnrealEngine.UserInterface\UniversalEditor.Plugins.UnrealEngine.UserInterface.csproj", "{0E994C49-3BCF-4E13-B0B5-3FE0B8419A22}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalEditor.Plugins.Webfoot", "Plugins\UniversalEditor.Plugins.Webfoot\UniversalEditor.Plugins.Webfoot.csproj", "{6C55828C-B0BA-440F-9279-8C4CCE3B361E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -465,6 +467,10 @@ Global
{0E994C49-3BCF-4E13-B0B5-3FE0B8419A22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E994C49-3BCF-4E13-B0B5-3FE0B8419A22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E994C49-3BCF-4E13-B0B5-3FE0B8419A22}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6C55828C-B0BA-440F-9279-8C4CCE3B361E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6C55828C-B0BA-440F-9279-8C4CCE3B361E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6C55828C-B0BA-440F-9279-8C4CCE3B361E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6C55828C-B0BA-440F-9279-8C4CCE3B361E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{6F0AB1AF-E1A1-4D19-B19C-05BBB15C94B2} = {05D15661-E684-4EC9-8FBD-C014BA433CC5}
@@ -540,6 +546,7 @@ Global
{90945326-FA6C-4A3E-90B9-5E29D7F85DF8} = {4BC75679-085E-45DA-B00A-D9BA89D8748A}
{23AC9D59-B206-49C7-99D5-E5A9380973EC} = {7B535D74-5496-4802-B809-89ED88274A91}
{0E994C49-3BCF-4E13-B0B5-3FE0B8419A22} = {7B535D74-5496-4802-B809-89ED88274A91}
+ {6C55828C-B0BA-440F-9279-8C4CCE3B361E} = {2ED32D16-6C06-4450-909A-40D32DA67FB4}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0