diff --git a/CSharp/Content/UniversalEditor.Content.PlatformIndependent/Extensions/GameDeveloper/Associations/FileSystem/ChaosWorksEngine.uexml b/CSharp/Content/UniversalEditor.Content.PlatformIndependent/Extensions/GameDeveloper/Associations/FileSystem/ChaosWorksEngine.uexml index 200db86b..29f1c734 100644 --- a/CSharp/Content/UniversalEditor.Content.PlatformIndependent/Extensions/GameDeveloper/Associations/FileSystem/ChaosWorksEngine.uexml +++ b/CSharp/Content/UniversalEditor.Content.PlatformIndependent/Extensions/GameDeveloper/Associations/FileSystem/ChaosWorksEngine.uexml @@ -3,7 +3,7 @@ - + *.vol @@ -13,6 +13,26 @@ + + + *.vol + *.v01 + *.v02 + *.v03 + *.v04 + *.v05 + *.v06 + *.v07 + *.v08 + *.v09 + *.v10 + + + + cweVOLF + + + diff --git a/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/ChaosWorksVOLDataFormat.cs b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/ChaosWorksVOLDataFormat.cs index 4f0f84a2..8044a8ac 100644 --- a/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/ChaosWorksVOLDataFormat.cs +++ b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/ChaosWorksVOLDataFormat.cs @@ -20,6 +20,11 @@ namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks _dfr = base.MakeReferenceInternal(); _dfr.Capabilities.Add(typeof(FileSystemObjectModel), DataFormatCapabilities.All); _dfr.ExportOptions.Add(new CustomOptionBoolean("Compressed", "&Compress this archive using the LZRW1 algorithm", true)); + _dfr.ExportOptions.Add(new CustomOptionChoice("FormatVersion", "Format &version", true, new CustomOptionFieldChoice[] + { + new CustomOptionFieldChoice("Version 1 (Fire Fight)", ChaosWorksVOLFormatVersion.V1, true), + new CustomOptionFieldChoice("Version 2 (Akimbo, Excessive Speed)", ChaosWorksVOLFormatVersion.V2) + })); _dfr.Sources.Add("Based on a requested QuickBMS script by WRS from xentax.com"); } return _dfr; @@ -28,6 +33,9 @@ namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks static LZRW1CompressionModule lzrw1 = new LZRW1CompressionModule(); public bool Compressed { get; set; } = true; + public ChaosWorksVOLFormatVersion FormatVersion { get; set; } = ChaosWorksVOLFormatVersion.V1; + + private long startOfFile = 0; protected override void LoadInternal(ref ObjectModel objectModel) { @@ -37,7 +45,41 @@ namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks fsom.AdditionalDetails.Add("ChaosWorks.VOL.Label", "Label"); Reader br = Accessor.Reader; - long startOfFile = Accessor.Position; + startOfFile = Accessor.Position; + + Accessor.Seek(-8, SeekOrigin.End); + // check to see if we're reading V2 or not + string trailerSignature = br.ReadFixedLengthString(8); + if (trailerSignature == " cweVOLF") + { + FormatVersion = ChaosWorksVOLFormatVersion.V2; + } + else + { + FormatVersion = ChaosWorksVOLFormatVersion.V1; + } + + Accessor.Seek(startOfFile, SeekOrigin.Begin); + + switch (FormatVersion) + { + case ChaosWorksVOLFormatVersion.V1: + { + LoadInternalV1(fsom); + break; + } + case ChaosWorksVOLFormatVersion.V2: + { + LoadInternalV2(fsom); + break; + } + } + } + + private void LoadInternalV1(FileSystemObjectModel fsom) + { + Reader br = Accessor.Reader; + Accessor.Seek(-20, SeekOrigin.End); // 20 byte header at the end of the file @@ -89,7 +131,7 @@ namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks if (br.EndOfStream) break; } bwms.Flush(); - + ma.Position = 0; Reader brms = new Reader(ma); @@ -102,22 +144,128 @@ namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks string fileType = brms.ReadFixedLengthString(260); string fileName = brms.ReadFixedLengthString(292); fileName = fileName.TrimNull(); - fileType = fileType.TrimNull(); + fileType = fileType.TrimNull(); - Console.WriteLine("cwe-vol: adding file {0} with type {1}", fileName, fileType); + Console.WriteLine("cwe-vol: adding file {0} with type {1}", fileName, fileType); - int fileSize = brms.ReadInt32(); + int fileSize = brms.ReadInt32(); int unknown1 = brms.ReadInt32(); int fileOffset = brms.ReadInt32(); int unknown2 = brms.ReadInt32(); int unknown3 = brms.ReadInt32(); File file = fsom.AddFile(fileName); - file.Size = fileSize; file.AdditionalDetails.Add(fsom.AdditionalDetails["ChaosWorks.VOL.Label"], fileType); + file.Size = fileSize; file.Source = new EmbeddedFileSource(brms, fileOffset, fileSize); } } + private void LoadInternalV2(FileSystemObjectModel fsom) + { + List list = new List(); + // first read the chunk headers + Reader reader = Accessor.Reader; + while (!reader.EndOfStream) + { + long offset = reader.Accessor.Position; + + string VF = reader.ReadFixedLengthString(2); + if (VF != "VF") + { + // bail out + reader.Seek(-2, SeekOrigin.Current); + break; + } + + ChaosWorksVOLV2CompressionFlag flag = (ChaosWorksVOLV2CompressionFlag) reader.ReadUInt16(); + + uint compressedLength = reader.ReadUInt32(); + uint decompressedLength = reader.ReadUInt32(); + if ((flag & ChaosWorksVOLV2CompressionFlag.Compressed) == ChaosWorksVOLV2CompressionFlag.Compressed) + { + // compressed + } + else + { + // uncompressed + compressedLength = decompressedLength; // this is weird + } + + list.Add(new Internal.ChaosWorksVOLV2ChunkInfo(offset, ((flag & ChaosWorksVOLV2CompressionFlag.Compressed) == ChaosWorksVOLV2CompressionFlag.Compressed), compressedLength, decompressedLength)); + // skip the data, we don't need to read it just yet + reader.Seek(compressedLength, SeekOrigin.Current); + } + + reader.Seek(list[list.Count - 1].DataOffset, SeekOrigin.Begin); + byte[] compressedData = reader.ReadBytes(list[list.Count - 1].CompressedLength); + byte[] decompressedData = zlib.Decompress(compressedData, (int)list[list.Count - 1].DecompressedLength); + + MemoryAccessor ma = new MemoryAccessor(decompressedData); + uint stringTableOffset = ma.Reader.ReadUInt32(); + + List fileInfos = new List(); + while (ma.Position < stringTableOffset) + { + uint unknown = ma.Reader.ReadUInt32(); + uint chunkOffset = ma.Reader.ReadUInt32(); + uint stringTableOffsetToLabel = ma.Reader.ReadUInt32(); + uint always0xFFFFFFFF = ma.Reader.ReadUInt32(); + uint stringTableOffsetToFileName = ma.Reader.ReadUInt32(); + uint fileLength = ma.Reader.ReadUInt32(); + uint unknown2 = ma.Reader.ReadUInt32(); + uint unknown3 = ma.Reader.ReadUInt32(); + uint unknown4 = ma.Reader.ReadUInt32(); + + fileInfos.Add(new Internal.ChaosWorksVOLV2FileInfo(chunkOffset, stringTableOffsetToLabel, stringTableOffsetToFileName, fileLength)); + + ma.SavePosition(); + ma.Seek(stringTableOffset + 8 + stringTableOffsetToLabel, SeekOrigin.Begin); + string label = ma.Reader.ReadNullTerminatedString(); + ma.Seek(stringTableOffset + 8 + stringTableOffsetToFileName, SeekOrigin.Begin); + string fileName = ma.Reader.ReadNullTerminatedString(); + + ma.LoadPosition(); + + File f = fsom.AddFile(fileName); + f.AdditionalDetails.Add(fsom.AdditionalDetails["ChaosWorks.VOL.Label"], label); + f.Properties.Add("reader", reader); + f.Properties.Add("fileinfo", fileInfos[fileInfos.Count - 1]); + f.Size = fileInfos[fileInfos.Count - 1].FileLength; + f.DataRequest += F_DataRequestV2; + } + } + + void F_DataRequestV2(object sender, DataRequestEventArgs e) + { + File f = (sender as File); + Internal.ChaosWorksVOLV2FileInfo fileinfo = (Internal.ChaosWorksVOLV2FileInfo)f.Properties["fileinfo"]; + Reader reader = (Reader)f.Properties["reader"]; + + reader.Seek(fileinfo.ChunkOffset, SeekOrigin.Begin); + string VF = reader.ReadFixedLengthString(2); + if (VF != "VF") + throw new InvalidDataFormatException("chunk does not begin with 'VF'"); + + ChaosWorksVOLV2CompressionFlag flag = (ChaosWorksVOLV2CompressionFlag)reader.ReadUInt16(); + + uint compressedLength = reader.ReadUInt32(); + uint decompressedLength = reader.ReadUInt32(); + byte[] decompressedData = null; + if ((flag & ChaosWorksVOLV2CompressionFlag.Compressed) == ChaosWorksVOLV2CompressionFlag.Compressed) + { + // compressed + byte[] compressedData = reader.ReadBytes(compressedLength); + decompressedData = zlib.Decompress(compressedData); + } + else + { + // uncompressed + compressedLength = decompressedLength; // this is weird + decompressedData = reader.ReadBytes(compressedLength); + } + e.Data = decompressedData; + } + protected override void SaveInternal(ObjectModel objectModel) { @@ -125,23 +273,40 @@ namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks if (fsom == null) throw new ObjectModelNotSupportedException(); + switch (FormatVersion) + { + case ChaosWorksVOLFormatVersion.V1: + { + SaveInternalV1(fsom); + break; + } + case ChaosWorksVOLFormatVersion.V2: + { + SaveInternalV1(fsom); + break; + } + } + } + + private void SaveInternalV1(FileSystemObjectModel fsom) + { Writer writer = Accessor.Writer; MemoryAccessor ma = new MemoryAccessor(); File[] files = fsom.GetAllFiles(); - int fileListOffset = 0; - for (int i = 0; i < files.Length; i++) - { - byte[] filedata = files[i].GetData(); - ma.Writer.WriteBytes(filedata); - fileListOffset += filedata.Length; - } - ma.Flush(); + int fileListOffset = 0; + for (int i = 0; i < files.Length; i++) + { + byte[] filedata = files[i].GetData(); + ma.Writer.WriteBytes(filedata); + fileListOffset += filedata.Length; + } + ma.Flush(); byte[] decompressedData = ma.ToArray(); - byte[] compressedData = lzrw1.Compress(decompressedData); + byte[] compressedData = lzrw1.Compress(decompressedData); writer.WriteBytes(compressedData); writer.WriteInt32(decompressedData.Length); @@ -150,5 +315,55 @@ namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks writer.WriteInt32(fileListOffset); writer.WriteInt32(Compressed ? 0x43024202 : 0x42024202); } + + private Compression.Modules.Zlib.ZlibCompressionModule zlib = new Compression.Modules.Zlib.ZlibCompressionModule(); + + private void SaveInternalV2(FileSystemObjectModel fsom) + { + Writer writer = Accessor.Writer; + + File[] files = fsom.GetAllFiles(); + + int fileListOffset = 0; + Internal.ChaosWorksVOLV2FileInfo[] infos = new Internal.ChaosWorksVOLV2FileInfo[files.Length]; + uint chunkOffset = 0, fileNameOffset = 0, labelOffset = 0; + for (int i = 0; i < files.Length; i++) + { + infos[i].ChunkOffset = chunkOffset; + infos[i].FileNameOffset = fileNameOffset; + infos[i].LabelOffset = labelOffset; + uint written = WriteV2CompressedChunk(writer, files[i].GetData()); + chunkOffset += written; + } + + // table of contents + MemoryAccessor ma = new MemoryAccessor(); + Writer maw = new Writer(ma); + maw.WriteUInt32((uint)(36 * files.Length)); // offset within this chunk to string table (not including the 8 bytes in this header) + maw.WriteInt32(0); + for (int i = 0; i < files.Length; i++) + { + maw.WriteUInt32(infos[i].ChunkOffset); + maw.WriteUInt32(infos[i].LabelOffset); + maw.WriteUInt32(0xFFFFFFFF); + maw.WriteUInt32(infos[i].FileNameOffset); + maw.WriteUInt32(0); + maw.WriteUInt32(0); + maw.WriteUInt32(0); + maw.WriteUInt32(0); + maw.WriteUInt32(0); + } + } + + private uint WriteV2CompressedChunk(Writer writer, byte[] decompressedData) + { + byte[] compressedData = zlib.Compress(decompressedData); + writer.WriteFixedLengthString("VF"); + writer.WriteUInt16(0x8000); // compressed + writer.WriteUInt32((uint)compressedData.Length); + writer.WriteUInt32((uint)decompressedData.Length); + writer.WriteBytes(compressedData); + return (uint)(compressedData.Length + 12); + } } } diff --git a/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/ChaosWorksVOLFormatVersion.cs b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/ChaosWorksVOLFormatVersion.cs new file mode 100644 index 00000000..db163c92 --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/ChaosWorksVOLFormatVersion.cs @@ -0,0 +1,29 @@ +// +// ChaosWorksVOLFormatVersion.cs - specifies whether VOL data format is version 1 (fire fight) or version 2 (' cweVOLF', akimbo et al.) +// +// 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; +namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks +{ + public enum ChaosWorksVOLFormatVersion + { + V1, + V2 + } +} diff --git a/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/ChaosWorksVOLV2CompressionFlag.cs b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/ChaosWorksVOLV2CompressionFlag.cs new file mode 100644 index 00000000..6cf664fd --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/ChaosWorksVOLV2CompressionFlag.cs @@ -0,0 +1,29 @@ +// +// ChaosWorksVOLV2CompressionFlag.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; +namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks +{ + public enum ChaosWorksVOLV2CompressionFlag : ushort + { + None, + Compressed = 0x8000 + } +} diff --git a/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/Internal/ChaosWorksVOLV2ChunkInfo.cs b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/Internal/ChaosWorksVOLV2ChunkInfo.cs new file mode 100644 index 00000000..78acf608 --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/Internal/ChaosWorksVOLV2ChunkInfo.cs @@ -0,0 +1,41 @@ +// +// ChaosWorksVOLV2ChunkInfo.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; +namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks.Internal +{ + public struct ChaosWorksVOLV2ChunkInfo + { + public long Offset; + public bool Compressed; + public uint CompressedLength; + public uint DecompressedLength; + + public long DataOffset { get { return Offset + 12; } } + + public ChaosWorksVOLV2ChunkInfo(long offset, bool compressed, uint compressedLength, uint decompressedLength) + { + Offset = offset; + Compressed = compressed; + CompressedLength = compressedLength; + DecompressedLength = decompressedLength; + } + } +} diff --git a/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/Internal/ChaosWorksVOLV2FileInfo.cs b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/Internal/ChaosWorksVOLV2FileInfo.cs new file mode 100644 index 00000000..9d101efb --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/DataFormats/FileSystem/ChaosWorks/Internal/ChaosWorksVOLV2FileInfo.cs @@ -0,0 +1,39 @@ +// +// ChaosWorksVOLV2FileInfo.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; +namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks.Internal +{ + public struct ChaosWorksVOLV2FileInfo + { + public uint ChunkOffset; + public uint LabelOffset; + public uint FileNameOffset; + public uint FileLength; + + public ChaosWorksVOLV2FileInfo(uint chunkOffset, uint labelOffset, uint fileNameOffset, uint fileLength) + { + ChunkOffset = chunkOffset; + LabelOffset = labelOffset; + FileNameOffset = fileNameOffset; + FileLength = fileLength; + } + } +} diff --git a/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/UniversalEditor.Plugins.ChaosWorks.csproj b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/UniversalEditor.Plugins.ChaosWorks.csproj index 4445e64a..d6dffacb 100644 --- a/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/UniversalEditor.Plugins.ChaosWorks.csproj +++ b/CSharp/Plugins/UniversalEditor.Plugins.ChaosWorks/UniversalEditor.Plugins.ChaosWorks.csproj @@ -37,6 +37,10 @@ + + + + @@ -52,6 +56,9 @@ UniversalEditor.Essential + + +