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