implement support for newer ' cweVOLF' format VOL archives - SAVE SUPPORT UNTESTED

This commit is contained in:
Michael Becker 2020-03-15 16:54:15 -04:00
parent 5972c9fb6e
commit f285a5061a
No known key found for this signature in database
GPG Key ID: 389DFF5D73781A12
7 changed files with 396 additions and 16 deletions

View File

@ -3,7 +3,7 @@
<Associations>
<Association>
<Filters>
<Filter Title="Chaos Works Engine volume archive">
<Filter Title="Chaos Works Engine volume archive, format version 1">
<FileNameFilters>
<FileNameFilter>*.vol</FileNameFilter>
</FileNameFilters>
@ -13,6 +13,26 @@
</MagicByteSequence>
</MagicByteSequences>
</Filter>
<Filter Title="Chaos Works Engine volume archive, format version 2">
<FileNameFilters>
<FileNameFilter>*.vol</FileNameFilter>
<FileNameFilter>*.v01</FileNameFilter>
<FileNameFilter>*.v02</FileNameFilter>
<FileNameFilter>*.v03</FileNameFilter>
<FileNameFilter>*.v04</FileNameFilter>
<FileNameFilter>*.v05</FileNameFilter>
<FileNameFilter>*.v06</FileNameFilter>
<FileNameFilter>*.v07</FileNameFilter>
<FileNameFilter>*.v08</FileNameFilter>
<FileNameFilter>*.v09</FileNameFilter>
<FileNameFilter>*.v10</FileNameFilter>
</FileNameFilters>
<MagicByteSequences>
<MagicByteSequence Offset="-8">
<MagicByte Type="String"> cweVOLF</MagicByte>
</MagicByteSequence>
</MagicByteSequences>
</Filter>
</Filters>
<ObjectModels>
<ObjectModel TypeName="UniversalEditor.ObjectModels.FileSystem.FileSystemObjectModel" />

View File

@ -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<Internal.ChaosWorksVOLV2ChunkInfo> list = new List<Internal.ChaosWorksVOLV2ChunkInfo>();
// 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<Internal.ChaosWorksVOLV2FileInfo> fileInfos = new List<Internal.ChaosWorksVOLV2FileInfo>();
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);
}
}
}

View File

@ -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 <alcexhim@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks
{
public enum ChaosWorksVOLFormatVersion
{
V1,
V2
}
}

View File

@ -0,0 +1,29 @@
//
// ChaosWorksVOLV2CompressionFlag.cs
//
// Author:
// Mike Becker <alcexhim@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.FileSystem.ChaosWorks
{
public enum ChaosWorksVOLV2CompressionFlag : ushort
{
None,
Compressed = 0x8000
}
}

View File

@ -0,0 +1,41 @@
//
// ChaosWorksVOLV2ChunkInfo.cs
//
// Author:
// Mike Becker <alcexhim@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
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;
}
}
}

View File

@ -0,0 +1,39 @@
//
// ChaosWorksVOLV2FileInfo.cs
//
// Author:
// Mike Becker <alcexhim@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
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;
}
}
}

View File

@ -37,6 +37,10 @@
<ItemGroup>
<Compile Include="DataFormats\FileSystem\ChaosWorks\ChaosWorksVOLDataFormat.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="DataFormats\FileSystem\ChaosWorks\ChaosWorksVOLV2CompressionFlag.cs" />
<Compile Include="DataFormats\FileSystem\ChaosWorks\ChaosWorksVOLFormatVersion.cs" />
<Compile Include="DataFormats\FileSystem\ChaosWorks\Internal\ChaosWorksVOLV2FileInfo.cs" />
<Compile Include="DataFormats\FileSystem\ChaosWorks\Internal\ChaosWorksVOLV2ChunkInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\UniversalEditor.Compression\UniversalEditor.Compression.csproj">
@ -52,6 +56,9 @@
<Name>UniversalEditor.Essential</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="DataFormats\FileSystem\ChaosWorks\Internal\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.