diff --git a/OpenRA.Game/FileSystem/FileSystem.cs b/OpenRA.Game/FileSystem/FileSystem.cs index 52fa4e27d5..e21b89fe7c 100644 --- a/OpenRA.Game/FileSystem/FileSystem.cs +++ b/OpenRA.Game/FileSystem/FileSystem.cs @@ -57,8 +57,6 @@ namespace OpenRA.FileSystem return new BigFile(this, filename); if (filename.EndsWith(".bag", StringComparison.InvariantCultureIgnoreCase)) return new BagFile(this, filename); - if (filename.EndsWith(".hdr", StringComparison.InvariantCultureIgnoreCase)) - return new InstallShieldCABExtractor(this, filename); IReadOnlyPackage parent; string subPath = null; diff --git a/OpenRA.Game/FileSystem/InstallShieldCABExtractor.cs b/OpenRA.Game/FileSystem/InstallShieldCABExtractor.cs deleted file mode 100644 index 88399d42f8..0000000000 --- a/OpenRA.Game/FileSystem/InstallShieldCABExtractor.cs +++ /dev/null @@ -1,508 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2016 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you 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. For more - * information, see COPYING. - */ -#endregion - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using ICSharpCode.SharpZipLib.Zip.Compression; - -namespace OpenRA.FileSystem -{ - public sealed class InstallShieldCABExtractor : IReadOnlyPackage - { - const uint FileSplit = 0x1; - const uint FileObfuscated = 0x2; - const uint FileCompressed = 0x4; - const uint FileInvalid = 0x8; - - const uint LinkPrev = 0x1; - const uint LinkNext = 0x2; - const uint MaxFileGroupCount = 71; - -#region Nested Structs - - struct FileGroup - { - public readonly string Name; - public readonly uint FirstFile; - public readonly uint LastFile; - - public FileGroup(Stream reader, long offset) - { - var nameOffset = reader.ReadUInt32(); - /* unknown */ reader.ReadBytes(18); - FirstFile = reader.ReadUInt32(); - LastFile = reader.ReadUInt32(); - - reader.Seek(offset + nameOffset, SeekOrigin.Begin); - Name = reader.ReadASCIIZ(); - } - } - - struct VolumeHeader - { - public readonly uint DataOffset; - public readonly uint DataOffsetHigh; - public readonly uint FirstFileIndex; - public readonly uint LastFileIndex; - - public readonly uint FirstFileOffset; - public readonly uint FirstFileOffsetHigh; - public readonly uint FirstFileSizeExpanded; - public readonly uint FirstFileSizeExpandedHigh; - - public readonly uint FirstFileSizeCompressed; - public readonly uint FirstFileSizeCompressedHigh; - public readonly uint LastFileOffset; - public readonly uint LastFileOffsetHigh; - - public readonly uint LastFileSizeExpanded; - public readonly uint LastFileSizeExpandedHigh; - public readonly uint LastFileSizeCompressed; - public readonly uint LastFileSizeCompressedHigh; - - public VolumeHeader(Stream reader) - { - DataOffset = reader.ReadUInt32(); - DataOffsetHigh = reader.ReadUInt32(); - - FirstFileIndex = reader.ReadUInt32(); - LastFileIndex = reader.ReadUInt32(); - FirstFileOffset = reader.ReadUInt32(); - FirstFileOffsetHigh = reader.ReadUInt32(); - - FirstFileSizeExpanded = reader.ReadUInt32(); - FirstFileSizeExpandedHigh = reader.ReadUInt32(); - FirstFileSizeCompressed = reader.ReadUInt32(); - FirstFileSizeCompressedHigh = reader.ReadUInt32(); - - LastFileOffset = reader.ReadUInt32(); - LastFileOffsetHigh = reader.ReadUInt32(); - LastFileSizeExpanded = reader.ReadUInt32(); - LastFileSizeExpandedHigh = reader.ReadUInt32(); - - LastFileSizeCompressed = reader.ReadUInt32(); - LastFileSizeCompressedHigh = reader.ReadUInt32(); - } - } - - struct CommonHeader - { - public const long Size = 16; - public readonly uint Version; - public readonly uint VolumeInfo; - public readonly long CabDescriptorOffset; - public readonly uint CabDescriptorSize; - - public CommonHeader(Stream reader) - { - Version = reader.ReadUInt32(); - VolumeInfo = reader.ReadUInt32(); - CabDescriptorOffset = reader.ReadUInt32(); - CabDescriptorSize = reader.ReadUInt32(); - } - } - - struct CabDescriptor - { - public readonly long FileTableOffset; - public readonly uint FileTableSize; - public readonly uint FileTableSize2; - public readonly uint DirectoryCount; - - public readonly uint FileCount; - public readonly long FileTableOffset2; - - public CabDescriptor(Stream reader, CommonHeader commonHeader) - { - reader.Seek(commonHeader.CabDescriptorOffset + 12, SeekOrigin.Begin); - FileTableOffset = reader.ReadUInt32(); - /* unknown */ reader.ReadUInt32(); - FileTableSize = reader.ReadUInt32(); - - FileTableSize2 = reader.ReadUInt32(); - DirectoryCount = reader.ReadUInt32(); - /* unknown */ reader.ReadBytes(8); - FileCount = reader.ReadUInt32(); - - FileTableOffset2 = reader.ReadUInt32(); - } - } - - struct FileDescriptor - { - public readonly ushort Flags; - public readonly uint ExpandedSize; - public readonly uint CompressedSize; - public readonly uint DataOffset; - - public readonly byte[] MD5; - public readonly uint NameOffset; - public readonly ushort DirectoryIndex; - public readonly uint LinkToPrevious; - - public readonly uint LinkToNext; - public readonly byte LinkFlags; - public readonly ushort Volume; - public readonly string Filename; - - public FileDescriptor(Stream reader, long tableOffset) - { - Flags = reader.ReadUInt16(); - ExpandedSize = reader.ReadUInt32(); - /* unknown */ reader.ReadUInt32(); - CompressedSize = reader.ReadUInt32(); - - /* unknown */ reader.ReadUInt32(); - DataOffset = reader.ReadUInt32(); - /* unknown */ reader.ReadUInt32(); - MD5 = reader.ReadBytes(16); - - /* unknown */ reader.ReadBytes(16); - NameOffset = reader.ReadUInt32(); - DirectoryIndex = reader.ReadUInt16(); - /* unknown */ reader.ReadBytes(12); - LinkToPrevious = reader.ReadUInt32(); - LinkToNext = reader.ReadUInt32(); - - LinkFlags = reader.ReadBytes(1)[0]; - Volume = reader.ReadUInt16(); - var posSave = reader.Position; - - reader.Seek(tableOffset + NameOffset, SeekOrigin.Begin); - Filename = reader.ReadASCIIZ(); - reader.Seek(posSave, SeekOrigin.Begin); - } - } - - class CabReader : IDisposable - { - readonly FileSystem context; - readonly FileDescriptor fileDes; - public uint RemainingArchiveStream; - public uint RemainingFileStream; - readonly uint index; - readonly string commonName; - ushort volumeNumber; - Stream cabFile; - - public CabReader(FileSystem context, FileDescriptor fileDes, uint index, string commonName) - { - this.fileDes = fileDes; - this.index = index; - this.commonName = commonName; - this.context = context; - volumeNumber = (ushort)(fileDes.Volume - 1u); - RemainingArchiveStream = 0; - if ((fileDes.Flags & FileCompressed) > 0) - RemainingFileStream = fileDes.CompressedSize; - else - RemainingFileStream = fileDes.ExpandedSize; - - cabFile = null; - NextFile(context); - } - - public void CopyTo(Stream dest) - { - if ((fileDes.Flags & FileCompressed) != 0) - { - var inf = new Inflater(true); - var buffer = new byte[165535]; - do - { - var bytesToExtract = cabFile.ReadUInt16(); - RemainingArchiveStream -= 2u; - RemainingFileStream -= 2u; - inf.SetInput(GetBytes(bytesToExtract)); - RemainingFileStream -= bytesToExtract; - while (!inf.IsNeedingInput) - { - var inflated = inf.Inflate(buffer); - dest.Write(buffer, 0, inflated); - } - - inf.Reset(); - } - while (RemainingFileStream > 0); - } - else - { - do - { - RemainingFileStream -= RemainingArchiveStream; - dest.Write(GetBytes(RemainingArchiveStream), 0, (int)RemainingArchiveStream); - } - while (RemainingFileStream > 0); - } - } - - public byte[] GetBytes(uint count) - { - if (count < RemainingArchiveStream) - { - RemainingArchiveStream -= count; - return cabFile.ReadBytes((int)count); - } - else - { - var outArray = new byte[count]; - var read = cabFile.Read(outArray, 0, (int)RemainingArchiveStream); - if (RemainingFileStream > RemainingArchiveStream) - { - NextFile(context); - RemainingArchiveStream -= (uint)cabFile.Read(outArray, read, (int)count - read); - } - - return outArray; - } - } - - public void Dispose() - { - cabFile.Dispose(); - } - - void NextFile(FileSystem context) - { - if (cabFile != null) - cabFile.Dispose(); - - ++volumeNumber; - cabFile = context.Open("{0}{1}.cab".F(commonName, volumeNumber)); - if (cabFile.ReadUInt32() != 0x28635349) - throw new InvalidDataException("Not an Installshield CAB package"); - - uint fileOffset; - if ((fileDes.Flags & FileSplit) != 0) - { - cabFile.Seek(CommonHeader.Size, SeekOrigin.Current); - var head = new VolumeHeader(cabFile); - if (index == head.LastFileIndex) - { - if ((fileDes.Flags & FileCompressed) != 0) - RemainingArchiveStream = head.LastFileSizeCompressed; - else - RemainingArchiveStream = head.LastFileSizeExpanded; - - fileOffset = head.LastFileOffset; - } - else if (index == head.FirstFileIndex) - { - if ((fileDes.Flags & FileCompressed) != 0) - RemainingArchiveStream = head.FirstFileSizeCompressed; - else - RemainingArchiveStream = head.FirstFileSizeExpanded; - - fileOffset = head.FirstFileOffset; - } - else - throw new Exception("Cannot Resolve Remaining Stream"); - } - else - { - if ((fileDes.Flags & FileCompressed) != 0) - RemainingArchiveStream = fileDes.CompressedSize; - else - RemainingArchiveStream = fileDes.ExpandedSize; - - fileOffset = fileDes.DataOffset; - } - - cabFile.Seek(fileOffset, SeekOrigin.Begin); - } - } -#endregion - - readonly Stream hdrFile; - readonly CommonHeader commonHeader; - readonly CabDescriptor cabDescriptor; - readonly List directoryTable; - readonly Dictionary directoryNames = new Dictionary(); - readonly Dictionary fileDescriptors = new Dictionary(); - readonly Dictionary index = new Dictionary(); - readonly FileSystem context; - - public string Name { get; private set; } - public IEnumerable Contents { get { return index.Keys; } } - - public InstallShieldCABExtractor(FileSystem context, string hdrFilename) - { - var fileGroups = new List(); - var fileGroupOffsets = new List(); - - hdrFile = context.Open(hdrFilename); - this.context = context; - - // Strips archive number AND file extension - Name = Regex.Replace(hdrFilename, @"\d*\.[^\.]*$", ""); - var signature = hdrFile.ReadUInt32(); - - if (signature != 0x28635349) - throw new InvalidDataException("Not an Installshield CAB package"); - - commonHeader = new CommonHeader(hdrFile); - cabDescriptor = new CabDescriptor(hdrFile, commonHeader); - /* unknown */ hdrFile.ReadBytes(14); - - for (var i = 0U; i < MaxFileGroupCount; ++i) - fileGroupOffsets.Add(hdrFile.ReadUInt32()); - - hdrFile.Seek(commonHeader.CabDescriptorOffset + cabDescriptor.FileTableOffset, SeekOrigin.Begin); - directoryTable = new List(); - - for (var i = 0U; i < cabDescriptor.DirectoryCount; ++i) - directoryTable.Add(hdrFile.ReadUInt32()); - - foreach (var offset in fileGroupOffsets) - { - var nextOffset = offset; - while (nextOffset != 0) - { - hdrFile.Seek((long)nextOffset + 4 + commonHeader.CabDescriptorOffset, SeekOrigin.Begin); - var descriptorOffset = hdrFile.ReadUInt32(); - nextOffset = hdrFile.ReadUInt32(); - hdrFile.Seek(descriptorOffset + commonHeader.CabDescriptorOffset, SeekOrigin.Begin); - - fileGroups.Add(new FileGroup(hdrFile, commonHeader.CabDescriptorOffset)); - } - } - - hdrFile.Seek(commonHeader.CabDescriptorOffset + cabDescriptor.FileTableOffset + cabDescriptor.FileTableOffset2, SeekOrigin.Begin); - foreach (var fileGroup in fileGroups) - { - for (var i = fileGroup.FirstFile; i <= fileGroup.LastFile; ++i) - { - AddFileDescriptorToList(i); - var fileDescriptor = fileDescriptors[i]; - var fullFilePath = "{0}\\{1}\\{2}".F(fileGroup.Name, DirectoryName(fileDescriptor.DirectoryIndex), fileDescriptor.Filename); - index.Add(fullFilePath, i); - } - } - } - - public string DirectoryName(uint index) - { - if (directoryNames.ContainsKey(index)) - return directoryNames[index]; - - hdrFile.Seek(commonHeader.CabDescriptorOffset + - cabDescriptor.FileTableOffset + - directoryTable[(int)index], - SeekOrigin.Begin); - - var test = hdrFile.ReadASCIIZ(); - return test; - } - - public bool Contains(string filename) - { - return index.ContainsKey(filename); - } - - public uint DirectoryCount() - { - return cabDescriptor.DirectoryCount; - } - - public string FileName(uint index) - { - if (!fileDescriptors.ContainsKey(index)) - AddFileDescriptorToList(index); - - return fileDescriptors[index].Filename; - } - - void AddFileDescriptorToList(uint index) - { - hdrFile.Seek(commonHeader.CabDescriptorOffset + - cabDescriptor.FileTableOffset + - cabDescriptor.FileTableOffset2 + - index * 0x57, - SeekOrigin.Begin); - - var fd = new FileDescriptor(hdrFile, - commonHeader.CabDescriptorOffset + cabDescriptor.FileTableOffset); - - fileDescriptors.Add(index, fd); - } - - public uint FileCount() - { - return cabDescriptor.FileCount; - } - - public void ExtractFile(uint index, string fileName) - { - Directory.CreateDirectory(Path.GetDirectoryName(fileName)); - using (var destfile = File.Open(fileName, FileMode.Create)) - GetContentById(index, destfile); - } - - public Stream GetContentById(uint index) - { - var fileDes = fileDescriptors[index]; - if ((fileDes.Flags & FileInvalid) != 0) - throw new Exception("File Invalid"); - - if ((fileDes.LinkFlags & LinkPrev) != 0) - return GetContentById(fileDes.LinkToPrevious); - - if ((fileDes.Flags & FileObfuscated) != 0) - throw new NotImplementedException("Haven't implemented obfuscated files"); - - var output = new MemoryStream((int)fileDes.ExpandedSize); - - using (var reader = new CabReader(context, fileDes, index, Name)) - reader.CopyTo(output); - - if (output.Length != fileDes.ExpandedSize) - throw new Exception("Did not fully extract Expected = {0}, Got = {1}".F(fileDes.ExpandedSize, output.Length)); - - output.Position = 0; - return output; - } - - public void GetContentById(uint index, Stream output) - { - var fileDes = fileDescriptors[index]; - if ((fileDes.Flags & FileInvalid) != 0) - throw new Exception("File Invalid"); - - if ((fileDes.LinkFlags & LinkPrev) != 0) - { - GetContentById(fileDes.LinkToPrevious, output); - return; - } - - if ((fileDes.Flags & FileObfuscated) != 0) - throw new NotImplementedException("Haven't implemented obfuscated files"); - - using (var reader = new CabReader(context, fileDes, index, Name)) - reader.CopyTo(output); - - if (output.Length != fileDes.ExpandedSize) - throw new Exception("Did not fully extract Expected = {0}, Got = {1}".F(fileDes.ExpandedSize, output.Length)); - } - - public Stream GetStream(string fileName) - { - return GetContentById(index[fileName]); - } - - public void Dispose() - { - hdrFile.Dispose(); - } - } -} diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index deac88e2f5..bc19c443d7 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -250,7 +250,6 @@ - diff --git a/OpenRA.Mods.Common/FileFormats/InstallShieldCABCompression.cs b/OpenRA.Mods.Common/FileFormats/InstallShieldCABCompression.cs new file mode 100644 index 0000000000..53b970a483 --- /dev/null +++ b/OpenRA.Mods.Common/FileFormats/InstallShieldCABCompression.cs @@ -0,0 +1,438 @@ +#region Copyright & License Information +/* + * Copyright 2007-2016 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you 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. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using ICSharpCode.SharpZipLib.Zip.Compression; + +namespace OpenRA.Mods.Common.FileFormats +{ + public sealed class InstallShieldCABCompression + { + const uint MaxFileGroupCount = 71; + + enum CABFlags : ushort + { + FileSplit = 0x1, + FileObfuscated = 0x2, + FileCompressed = 0x4, + FileInvalid = 0x8, + } + + enum LinkFlags : byte + { + Prev = 0x1, + Next = 0x2 + } + + struct FileGroup + { + public readonly string Name; + public readonly uint FirstFile; + public readonly uint LastFile; + + public FileGroup(Stream stream, long offset) + { + var nameOffset = stream.ReadUInt32(); + stream.Position += 18; + FirstFile = stream.ReadUInt32(); + LastFile = stream.ReadUInt32(); + + var pos = stream.Position; + stream.Position = offset + nameOffset; + Name = stream.ReadASCIIZ(); + stream.Position = pos; + } + } + + struct CabDescriptor + { + public readonly long FileTableOffset; + public readonly uint FileTableSize; + public readonly uint FileTableSize2; + public readonly uint DirectoryCount; + + public readonly uint FileCount; + public readonly long FileTableOffset2; + + public CabDescriptor(Stream stream) + { + FileTableOffset = stream.ReadUInt32(); + stream.Position += 4; + FileTableSize = stream.ReadUInt32(); + FileTableSize2 = stream.ReadUInt32(); + DirectoryCount = stream.ReadUInt32(); + stream.Position += 8; + FileCount = stream.ReadUInt32(); + FileTableOffset2 = stream.ReadUInt32(); + } + } + + struct DirectoryDescriptor + { + public readonly string Name; + + public DirectoryDescriptor(Stream stream, long nameTableOffset) + { + var nameOffset = stream.ReadUInt32(); + var pos = stream.Position; + + stream.Position = nameTableOffset + nameOffset; + + Name = stream.ReadASCIIZ(); + stream.Position = pos; + } + } + + struct FileDescriptor + { + public readonly uint Index; + public readonly CABFlags Flags; + public readonly uint ExpandedSize; + public readonly uint CompressedSize; + public readonly uint DataOffset; + + public readonly byte[] MD5; + public readonly uint NameOffset; + public readonly ushort DirectoryIndex; + public readonly uint LinkToPrevious; + + public readonly uint LinkToNext; + public readonly LinkFlags LinkFlags; + public readonly ushort Volume; + public readonly string Filename; + + public FileDescriptor(Stream stream, uint index, long tableOffset) + { + Index = index; + Flags = (CABFlags)stream.ReadUInt16(); + ExpandedSize = stream.ReadUInt32(); + stream.Position += 4; + CompressedSize = stream.ReadUInt32(); + + stream.Position += 4; + DataOffset = stream.ReadUInt32(); + stream.Position += 4; + MD5 = stream.ReadBytes(16); + + stream.Position += 16; + NameOffset = stream.ReadUInt32(); + DirectoryIndex = stream.ReadUInt16(); + stream.Position += 12; + LinkToPrevious = stream.ReadUInt32(); + LinkToNext = stream.ReadUInt32(); + + LinkFlags = (LinkFlags)stream.ReadUInt8(); + Volume = stream.ReadUInt16(); + + var pos = stream.Position; + stream.Position = tableOffset + NameOffset; + Filename = stream.ReadASCIIZ(); + stream.Position = pos; + } + } + + struct CommonHeader + { + public const long Size = 16; + public readonly uint Version; + public readonly uint VolumeInfo; + public readonly long CabDescriptorOffset; + public readonly uint CabDescriptorSize; + + public CommonHeader(Stream stream) + { + Version = stream.ReadUInt32(); + VolumeInfo = stream.ReadUInt32(); + CabDescriptorOffset = stream.ReadUInt32(); + CabDescriptorSize = stream.ReadUInt32(); + } + } + + struct VolumeHeader + { + public readonly uint DataOffset; + public readonly uint DataOffsetHigh; + public readonly uint FirstFileIndex; + public readonly uint LastFileIndex; + + public readonly uint FirstFileOffset; + public readonly uint FirstFileOffsetHigh; + public readonly uint FirstFileSizeExpanded; + public readonly uint FirstFileSizeExpandedHigh; + + public readonly uint FirstFileSizeCompressed; + public readonly uint FirstFileSizeCompressedHigh; + public readonly uint LastFileOffset; + public readonly uint LastFileOffsetHigh; + + public readonly uint LastFileSizeExpanded; + public readonly uint LastFileSizeExpandedHigh; + public readonly uint LastFileSizeCompressed; + public readonly uint LastFileSizeCompressedHigh; + + public VolumeHeader(Stream stream) + { + DataOffset = stream.ReadUInt32(); + DataOffsetHigh = stream.ReadUInt32(); + + FirstFileIndex = stream.ReadUInt32(); + LastFileIndex = stream.ReadUInt32(); + FirstFileOffset = stream.ReadUInt32(); + FirstFileOffsetHigh = stream.ReadUInt32(); + + FirstFileSizeExpanded = stream.ReadUInt32(); + FirstFileSizeExpandedHigh = stream.ReadUInt32(); + FirstFileSizeCompressed = stream.ReadUInt32(); + FirstFileSizeCompressedHigh = stream.ReadUInt32(); + + LastFileOffset = stream.ReadUInt32(); + LastFileOffsetHigh = stream.ReadUInt32(); + LastFileSizeExpanded = stream.ReadUInt32(); + LastFileSizeExpandedHigh = stream.ReadUInt32(); + + LastFileSizeCompressed = stream.ReadUInt32(); + LastFileSizeCompressedHigh = stream.ReadUInt32(); + } + } + + class CabExtracter + { + readonly FileDescriptor file; + readonly Dictionary volumes; + + uint remainingInArchive; + uint toExtract; + + int currentVolumeID; + Stream currentVolume; + + public CabExtracter(FileDescriptor file, Dictionary volumes) + { + this.file = file; + this.volumes = volumes; + + remainingInArchive = 0; + toExtract = file.Flags.HasFlag(CABFlags.FileCompressed) ? file.CompressedSize : file.ExpandedSize; + + SetVolume(file.Volume); + } + + public void CopyTo(Stream output, Action onProgress) + { + if (file.Flags.HasFlag(CABFlags.FileCompressed)) + { + var inf = new Inflater(true); + var buffer = new byte[165535]; + do + { + var bytesToExtract = currentVolume.ReadUInt16(); + remainingInArchive -= 2; + toExtract -= 2; + inf.SetInput(GetBytes(bytesToExtract)); + toExtract -= bytesToExtract; + while (!inf.IsNeedingInput) + { + if (onProgress != null) + onProgress((int)(100 * output.Position / file.ExpandedSize)); + + var inflated = inf.Inflate(buffer); + output.Write(buffer, 0, inflated); + } + + inf.Reset(); + } while (toExtract > 0); + } + else + { + do + { + if (onProgress != null) + onProgress((int)(100 * output.Position / file.ExpandedSize)); + + toExtract -= remainingInArchive; + output.Write(GetBytes(remainingInArchive), 0, (int)remainingInArchive); + } while (toExtract > 0); + } + } + + public byte[] GetBytes(uint count) + { + if (count < remainingInArchive) + { + remainingInArchive -= count; + return currentVolume.ReadBytes((int)count); + } + else + { + var outArray = new byte[count]; + var read = currentVolume.Read(outArray, 0, (int)remainingInArchive); + if (toExtract > remainingInArchive) + { + SetVolume(currentVolumeID + 1); + remainingInArchive -= (uint)currentVolume.Read(outArray, read, (int)count - read); + } + + return outArray; + } + } + + void SetVolume(int newVolume) + { + currentVolumeID = newVolume; + if (!volumes.TryGetValue(currentVolumeID, out currentVolume)) + throw new FileNotFoundException("Volume {0} is not available".F(currentVolumeID)); + + currentVolume.Position = 0; + if (currentVolume.ReadUInt32() != 0x28635349) + throw new InvalidDataException("Not an Installshield CAB package"); + + uint fileOffset; + if (file.Flags.HasFlag(CABFlags.FileSplit)) + { + currentVolume.Position += CommonHeader.Size; + var head = new VolumeHeader(currentVolume); + if (file.Index == head.LastFileIndex) + { + if (file.Flags.HasFlag(CABFlags.FileCompressed)) + remainingInArchive = head.LastFileSizeCompressed; + else + remainingInArchive = head.LastFileSizeExpanded; + + fileOffset = head.LastFileOffset; + } + else if (file.Index == head.FirstFileIndex) + { + if (file.Flags.HasFlag(CABFlags.FileCompressed)) + remainingInArchive = head.FirstFileSizeCompressed; + else + remainingInArchive = head.FirstFileSizeExpanded; + + fileOffset = head.FirstFileOffset; + } + else + throw new Exception("Cannot Resolve Remaining Stream"); + } + else + { + if (file.Flags.HasFlag(CABFlags.FileCompressed)) + remainingInArchive = file.CompressedSize; + else + remainingInArchive = file.ExpandedSize; + + fileOffset = file.DataOffset; + } + + currentVolume.Position = fileOffset; + } + } + + readonly Dictionary index = new Dictionary(); + readonly Dictionary volumes; + + public InstallShieldCABCompression(Stream header, Dictionary volumes) + { + this.volumes = volumes; + + if (header.ReadUInt32() != 0x28635349) + throw new InvalidDataException("Not an Installshield CAB package"); + + header.Position += 8; + var cabDescriptorOffset = header.ReadUInt32(); + header.Position = cabDescriptorOffset + 12; + var cabDescriptor = new CabDescriptor(header); + header.Position += 14; + + var fileGroupOffsets = new uint[MaxFileGroupCount]; + for (var i = 0; i < MaxFileGroupCount; i++) + fileGroupOffsets[i] = header.ReadUInt32(); + + header.Position = cabDescriptorOffset + cabDescriptor.FileTableOffset; + var directories = new DirectoryDescriptor[cabDescriptor.DirectoryCount]; + for (var i = 0; i < directories.Length; i++) + directories[i] = new DirectoryDescriptor(header, cabDescriptorOffset + cabDescriptor.FileTableOffset); + + var fileGroups = new List(); + foreach (var offset in fileGroupOffsets) + { + var nextOffset = offset; + while (nextOffset != 0) + { + header.Position = cabDescriptorOffset + (long)nextOffset + 4; + var descriptorOffset = header.ReadUInt32(); + nextOffset = header.ReadUInt32(); + header.Position = cabDescriptorOffset + descriptorOffset; + + fileGroups.Add(new FileGroup(header, cabDescriptorOffset)); + } + } + + header.Position = cabDescriptorOffset + cabDescriptor.FileTableOffset + cabDescriptor.FileTableOffset2; + foreach (var fileGroup in fileGroups) + { + for (var i = fileGroup.FirstFile; i <= fileGroup.LastFile; i++) + { + header.Position = cabDescriptorOffset + cabDescriptor.FileTableOffset + cabDescriptor.FileTableOffset2 + i * 0x57; + var file = new FileDescriptor(header, i, cabDescriptorOffset + cabDescriptor.FileTableOffset); + var path = "{0}\\{1}\\{2}".F(fileGroup.Name, directories[file.DirectoryIndex].Name, file.Filename); + index[path] = file; + } + } + } + + public void ExtractFile(string filename, Stream output, Action onProgress = null) + { + FileDescriptor file; + if (!index.TryGetValue(filename, out file)) + throw new FileNotFoundException(filename); + + ExtractFile(file, output, onProgress); + } + + void ExtractFile(FileDescriptor file, Stream output, Action onProgress = null) + { + if (file.Flags.HasFlag(CABFlags.FileInvalid)) + throw new Exception("File Invalid"); + + if (file.LinkFlags.HasFlag(LinkFlags.Prev)) + { + var prev = index.Values.First(f => f.Index == file.LinkToPrevious); + ExtractFile(prev, output, onProgress); + return; + } + + if (file.Flags.HasFlag(CABFlags.FileObfuscated)) + throw new NotImplementedException("Obfuscated files are not supported"); + + var extracter = new CabExtracter(file, volumes); + extracter.CopyTo(output, onProgress); + + if (output.Length != file.ExpandedSize) + throw new InvalidDataException("File expanded to wrong length. Expected = {0}, Got = {1}".F(file.ExpandedSize, output.Length)); + } + + public IReadOnlyDictionary> Contents + { + get + { + var contents = new Dictionary>(); + foreach (var kv in index) + contents.GetOrAdd(kv.Value.Volume).Add(kv.Key); + + return new ReadOnlyDictionary>(contents + .ToDictionary(x => x.Key, x => x.Value.AsEnumerable())); + } + } + } +} diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index aa47b17679..44c49a43f9 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -775,6 +775,8 @@ + + diff --git a/OpenRA.Mods.Common/UtilityCommands/ListInstallShieldCabContentsCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ListInstallShieldCabContentsCommand.cs new file mode 100644 index 0000000000..04e9610e50 --- /dev/null +++ b/OpenRA.Mods.Common/UtilityCommands/ListInstallShieldCabContentsCommand.cs @@ -0,0 +1,41 @@ +#region Copyright & License Information +/* + * Copyright 2007-2016 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you 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. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenRA.Mods.Common.FileFormats; + +namespace OpenRA.Mods.Common.UtilityCommands +{ + class ListInstallShieldCabContentsCommand : IUtilityCommand + { + public string Name { get { return "--list-installshield-cab"; } } + + public bool ValidateArguments(string[] args) + { + return args.Length == 2; + } + + [Desc("DATA.HDR", "Lists the filenames contained within an Installshield CAB volume set")] + public void Run(ModData modData, string[] args) + { + var package = new InstallShieldCABCompression(File.OpenRead(args[1]), null); + foreach (var volume in package.Contents.OrderBy(kv => kv.Key)) + { + Console.WriteLine("Volume: {0}", volume.Key); + foreach (var filename in volume.Value) + Console.WriteLine("\t{0}", filename); + } + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs index 8583959e37..72476be2c0 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs @@ -234,6 +234,25 @@ namespace OpenRA.Mods.Common.Widgets.Logic break; } + case "extract-iscab": + { + ExtractFromISCab(path, i.Value, extracted, m => message = m); + break; + } + + case "delete": + { + var sourcePath = Path.Combine(path, i.Value.Value); + + // Try as an absolute path + if (!File.Exists(sourcePath)) + sourcePath = Platform.ResolvePath(i.Value.Value); + + Log.Write("debug", "Deleting {0}", sourcePath); + File.Delete(sourcePath); + break; + } + default: Log.Write("debug", "Unknown installation command {0} - ignoring", i.Key); break; @@ -282,6 +301,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic static void ExtractFromPackage(ExtractionType type, string path, MiniYaml actionYaml, List extractedFiles, Action updateMessage) { var sourcePath = Path.Combine(path, actionYaml.Value); + + // Try as an absolute path + if (!File.Exists(sourcePath)) + sourcePath = Platform.ResolvePath(actionYaml.Value); + using (var source = File.OpenRead(sourcePath)) { foreach (var node in actionYaml.Nodes) @@ -344,6 +368,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic static void ExtractFromMSCab(string path, MiniYaml actionYaml, List extractedFiles, Action updateMessage) { var sourcePath = Path.Combine(path, actionYaml.Value); + + // Try as an absolute path + if (!File.Exists(sourcePath)) + sourcePath = Platform.ResolvePath(actionYaml.Value); + using (var source = File.OpenRead(sourcePath)) { var reader = new MSCabCompression(source); @@ -370,6 +399,64 @@ namespace OpenRA.Mods.Common.Widgets.Logic } } + static void ExtractFromISCab(string path, MiniYaml actionYaml, List extractedFiles, Action updateMessage) + { + var sourcePath = Path.Combine(path, actionYaml.Value); + + // Try as an absolute path + if (!File.Exists(sourcePath)) + sourcePath = Platform.ResolvePath(actionYaml.Value); + + var volumeNode = actionYaml.Nodes.FirstOrDefault(n => n.Key == "Volumes"); + if (volumeNode == null) + throw new InvalidDataException("extract-iscab entry doesn't define a Volumes node"); + + var extractNode = actionYaml.Nodes.FirstOrDefault(n => n.Key == "Extract"); + if (extractNode == null) + throw new InvalidDataException("extract-iscab entry doesn't define an Extract node"); + + var volumes = new Dictionary(); + try + { + foreach (var node in volumeNode.Value.Nodes) + { + var volume = FieldLoader.GetValue("(key)", node.Key); + var stream = File.OpenRead(Path.Combine(path, node.Value.Value)); + volumes.Add(volume, stream); + } + + using (var source = File.OpenRead(sourcePath)) + { + var reader = new InstallShieldCABCompression(source, volumes); + foreach (var node in extractNode.Value.Nodes) + { + var targetPath = Platform.ResolvePath(node.Key); + + if (File.Exists(targetPath)) + { + Log.Write("install", "Skipping installed file " + targetPath); + continue; + } + + extractedFiles.Add(targetPath); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); + using (var target = File.OpenWrite(targetPath)) + { + Log.Write("install", "Extracting {0} -> {1}".F(sourcePath, targetPath)); + var displayFilename = Path.GetFileName(Path.GetFileName(targetPath)); + Action onProgress = percent => updateMessage("Extracting {0} ({1}%)".F(displayFilename, percent)); + reader.ExtractFile(node.Value.Value, target, onProgress); + } + } + } + } + finally + { + foreach (var kv in volumes) + kv.Value.Dispose(); + } + } + string FindSourcePath(ModContent.ModSource source, IEnumerable volumes) { if (source.Type == ModContent.SourceType.Install) diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index 1ef75f4ba8..16d1371645 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -227,22 +227,22 @@ ModContent: Packages: base: Base Game Files TestFiles: ^Content/cnc/conquer.mix, ^Content/cnc/desert.mix, ^Content/cnc/sounds.mix, ^Content/cnc/speech.mix, ^Content/cnc/temperat.mix, ^Content/cnc/tempicnh.mix, ^Content/cnc/winter.mix - Sources: gdi95, gdi95-linux, nod95, nod95-linux, origin + Sources: gdi95, gdi95-linux, nod95, nod95-linux, tfd, origin Required: true Download: basefiles music: Base Game Music TestFiles: ^Content/cnc/scores.mix - Sources: gdi95, gdi95-linux, nod95, nod95-linux, origin + Sources: gdi95, gdi95-linux, nod95, nod95-linux, tfd, origin Download: music movies-gdi: GDI Campaign Briefings TestFiles: ^Content/cnc/movies/visor.vqa, ^Content/cnc/movies/turtkill.vqa, ^Content/cnc/movies/trailer.vqa, ^Content/cnc/movies/tbrinfo3.vqa, ^Content/cnc/movies/tbrinfo2.vqa, ^Content/cnc/movies/tbrinfo1.vqa, ^Content/cnc/movies/seige.vqa, ^Content/cnc/movies/samsite.vqa, ^Content/cnc/movies/samdie.vqa, ^Content/cnc/movies/sabotage.vqa, ^Content/cnc/movies/retro.vqa, ^Content/cnc/movies/podium.vqa, ^Content/cnc/movies/planecra.vqa, ^Content/cnc/movies/pintle.vqa, ^Content/cnc/movies/paratrop.vqa, ^Content/cnc/movies/nodsweep.vqa, ^Content/cnc/movies/nodlose.vqa, ^Content/cnc/movies/nodflees.vqa, ^Content/cnc/movies/nod1.vqa, ^Content/cnc/movies/nitejump.vqa, ^Content/cnc/movies/napalm.vqa, ^Content/cnc/movies/logo.vqa, ^Content/cnc/movies/landing.vqa, ^Content/cnc/movies/intro2.vqa, ^Content/cnc/movies/hellvaly.vqa, ^Content/cnc/movies/gunboat.vqa, ^Content/cnc/movies/generic.vqa, ^Content/cnc/movies/gdilose.vqa, ^Content/cnc/movies/gdifinb.vqa, ^Content/cnc/movies/gdifina.vqa, ^Content/cnc/movies/gdiend2.vqa, ^Content/cnc/movies/gdiend1.vqa, ^Content/cnc/movies/gdi9.vqa, ^Content/cnc/movies/gdi8b.vqa, ^Content/cnc/movies/gdi8a.vqa, ^Content/cnc/movies/gdi7.vqa, ^Content/cnc/movies/gdi6.vqa, ^Content/cnc/movies/gdi5.vqa, ^Content/cnc/movies/gdi4b.vqa, ^Content/cnc/movies/gdi4a.vqa, ^Content/cnc/movies/gdi3lose.vqa, ^Content/cnc/movies/gdi3.vqa, ^Content/cnc/movies/gdi2.vqa, ^Content/cnc/movies/gdi15.vqa, ^Content/cnc/movies/gdi14.vqa, ^Content/cnc/movies/gdi13.vqa, ^Content/cnc/movies/gdi12.vqa, ^Content/cnc/movies/gdi11.vqa, ^Content/cnc/movies/gdi10.vqa, ^Content/cnc/movies/gdi1.vqa, ^Content/cnc/movies/gameover.vqa, ^Content/cnc/movies/forestkl.vqa, ^Content/cnc/movies/flyy.vqa, ^Content/cnc/movies/flag.vqa, ^Content/cnc/movies/dino.vqa, ^Content/cnc/movies/desolat.vqa, ^Content/cnc/movies/consyard.vqa, ^Content/cnc/movies/cc2tease.vqa, ^Content/cnc/movies/burdet2.vqa, ^Content/cnc/movies/burdet1.vqa, ^Content/cnc/movies/bombflee.vqa, ^Content/cnc/movies/bombaway.vqa, ^Content/cnc/movies/bkground.vqa, ^Content/cnc/movies/bcanyon.vqa, ^Content/cnc/movies/banner.vqa - Sources: gdi95, gdi95-linux, origin + Sources: gdi95, gdi95-linux, tfd, origin movies-nod: Nod Campaign Briefings TestFiles: ^Content/cnc/movies/visor.vqa, ^Content/cnc/movies/trtkil_d.vqa, ^Content/cnc/movies/trailer.vqa, ^Content/cnc/movies/tiberfx.vqa, ^Content/cnc/movies/tankkill.vqa, ^Content/cnc/movies/tankgo.vqa, ^Content/cnc/movies/sundial.vqa, ^Content/cnc/movies/stealth.vqa, ^Content/cnc/movies/spycrash.vqa, ^Content/cnc/movies/sethpre.vqa, ^Content/cnc/movies/seige.vqa, ^Content/cnc/movies/samsite.vqa, ^Content/cnc/movies/retro.vqa, ^Content/cnc/movies/refint.vqa, ^Content/cnc/movies/obel.vqa, ^Content/cnc/movies/nuke.vqa, ^Content/cnc/movies/nodlose.vqa, ^Content/cnc/movies/nodfinal.vqa, ^Content/cnc/movies/nodend4.vqa, ^Content/cnc/movies/nodend3.vqa, ^Content/cnc/movies/nodend2.vqa, ^Content/cnc/movies/nodend1.vqa, ^Content/cnc/movies/nod9.vqa, ^Content/cnc/movies/nod8.vqa, ^Content/cnc/movies/nod7b.vqa, ^Content/cnc/movies/nod7a.vqa, ^Content/cnc/movies/nod6.vqa, ^Content/cnc/movies/nod5.vqa, ^Content/cnc/movies/nod4b.vqa, ^Content/cnc/movies/nod4a.vqa, ^Content/cnc/movies/nod3.vqa, ^Content/cnc/movies/nod2.vqa, ^Content/cnc/movies/nod1pre.vqa, ^Content/cnc/movies/nod13.vqa, ^Content/cnc/movies/nod12.vqa, ^Content/cnc/movies/nod11.vqa, ^Content/cnc/movies/nod10b.vqa, ^Content/cnc/movies/nod10a.vqa, ^Content/cnc/movies/nod1.vqa, ^Content/cnc/movies/logo.vqa, ^Content/cnc/movies/landing.vqa, ^Content/cnc/movies/kanepre.vqa, ^Content/cnc/movies/intro2.vqa, ^Content/cnc/movies/insites.vqa, ^Content/cnc/movies/generic.vqa, ^Content/cnc/movies/gdi1.vqa, ^Content/cnc/movies/gameover.vqa, ^Content/cnc/movies/forestkl.vqa, ^Content/cnc/movies/flag.vqa, ^Content/cnc/movies/dino.vqa, ^Content/cnc/movies/dessweep.vqa, ^Content/cnc/movies/deskill.vqa, ^Content/cnc/movies/desflees.vqa, ^Content/cnc/movies/consyard.vqa, ^Content/cnc/movies/cc2tease.vqa, ^Content/cnc/movies/bombflee.vqa, ^Content/cnc/movies/bombaway.vqa, ^Content/cnc/movies/bcanyon.vqa, ^Content/cnc/movies/banner.vqa, ^Content/cnc/movies/akira.vqa, ^Content/cnc/movies/airstrk.vqa - Sources: nod95, nod95-linux, origin + Sources: nod95, nod95-linux, tfd, origin music-covertops: Covert Operations Music TestFiles: ^Content/cnc/scores-covertops.mix - Sources: covertops, covertops-linux, origin + Sources: covertops, covertops-linux, tfd, origin Downloads: basefiles: Base Freeware Content MirrorList: http://www.openra.net/packages/cnc-mirrors.txt @@ -1127,6 +1127,342 @@ ModContent: Install: copy: . ^Content/cnc/scores-covertops.mix: scores.mix + tfd: C&C The First Decade (English) + IDFiles: + data1.hdr: bef3a08c3fc1b1caf28ca0dbb97c1f900005930e + data1.cab: 12ad6113a6890a1b4d5651a75378c963eaf513b9 + Install: + extract-iscab: data1.hdr + Volumes: + 2: data2.cab + 3: data3.cab + Extract: + ^Content/cnc/conquer.mix: CnC\\CONQUER.MIX + ^Content/cnc/desert.mix: CnC\\DESERT.MIX + ^Content/cnc/general.mix: CnC\\GENERAL.MIX + ^Content/cnc/scores.mix: CnC\\SCORES.MIX + ^Content/cnc/sounds.mix: CnC\\SOUNDS.MIX + ^Content/cnc/temperat.mix: CnC\\TEMPERAT.MIX + ^Content/cnc/winter.mix: CnC\\WINTER.MIX + ^Content/cnc/speech.mix: CnC\\SPEECH.MIX + ^Content/cnc/tempicnh.mix: CnC\\TEMPICNH.MIX + ^Content/cnc/transit.mix: CnC\\TRANSIT.MIX + ^Content/cnc/scores-covertops.mix: CnC\covert\SCORES.MIX + ^Content/cnc/movies.mix: CnC\\MOVIES.MIX + extract-raw: ^Content/cnc/movies.mix + ^Content/cnc/movies/airstrk.vqa: + Offset: 1266 + Length: 4444442 + ^Content/cnc/movies/akira.vqa: + Offset: 4445708 + Length: 7803444 + ^Content/cnc/movies/bkground.vqa: + Offset: 12249152 + Length: 15267052 + ^Content/cnc/movies/burdet1.vqa: + Offset: 27516204 + Length: 10410614 + ^Content/cnc/movies/burdet2.vqa: + Offset: 37926818 + Length: 2062190 + ^Content/cnc/movies/gdi4a.vqa: + Offset: 39989008 + Length: 4450582 + ^Content/cnc/movies/gdi4b.vqa: + Offset: 44439590 + Length: 6603530 + ^Content/cnc/movies/gdifina.vqa: + Offset: 51043120 + Length: 12888650 + ^Content/cnc/movies/gdifinb.vqa: + Offset: 63931770 + Length: 17769978 + ^Content/cnc/movies/nod7a.vqa: + Offset: 81701748 + Length: 4740470 + ^Content/cnc/movies/nod7b.vqa: + Offset: 86442218 + Length: 4671402 + ^Content/cnc/movies/visor.vqa: + Offset: 91113620 + Length: 4407162 + ^Content/cnc/movies/turtkill.vqa: + Offset: 95520782 + Length: 3323444 + ^Content/cnc/movies/trtkil_d.vqa: + Offset: 98844226 + Length: 3511836 + ^Content/cnc/movies/trailer.vqa: + Offset: 102356062 + Length: 23163930 + ^Content/cnc/movies/tiberfx.vqa: + Offset: 125519992 + Length: 8735102 + ^Content/cnc/movies/tbrinfo3.vqa: + Offset: 134255094 + Length: 14972046 + ^Content/cnc/movies/tbrinfo2.vqa: + Offset: 149227140 + Length: 16195290 + ^Content/cnc/movies/tbrinfo1.vqa: + Offset: 165422430 + Length: 23479052 + ^Content/cnc/movies/tankkill.vqa: + Offset: 188901482 + Length: 3298978 + ^Content/cnc/movies/tankgo.vqa: + Offset: 192200460 + Length: 1403876 + ^Content/cnc/movies/sundial.vqa: + Offset: 193604336 + Length: 3653636 + ^Content/cnc/movies/stealth.vqa: + Offset: 197257972 + Length: 4521860 + ^Content/cnc/movies/spycrash.vqa: + Offset: 201779832 + Length: 1886288 + ^Content/cnc/movies/sethpre.vqa: + Offset: 203666120 + Length: 10914610 + ^Content/cnc/movies/seige.vqa: + Offset: 214580730 + Length: 2705978 + ^Content/cnc/movies/samsite.vqa: + Offset: 217286708 + Length: 1410418 + ^Content/cnc/movies/samdie.vqa: + Offset: 218697126 + Length: 3741942 + ^Content/cnc/movies/sabotage.vqa: + Offset: 222439068 + Length: 1386202 + ^Content/cnc/movies/retro.vqa: + Offset: 223825270 + Length: 6434382 + ^Content/cnc/movies/refint.vqa: + Offset: 230259652 + Length: 5090040 + ^Content/cnc/movies/podium.vqa: + Offset: 235349692 + Length: 2790664 + ^Content/cnc/movies/planecra.vqa: + Offset: 238140356 + Length: 5085426 + ^Content/cnc/movies/pintle.vqa: + Offset: 243225782 + Length: 2757536 + ^Content/cnc/movies/paratrop.vqa: + Offset: 245983318 + Length: 3180272 + ^Content/cnc/movies/obel.vqa: + Offset: 249163590 + Length: 3851370 + ^Content/cnc/movies/nuke.vqa: + Offset: 253014960 + Length: 3126662 + ^Content/cnc/movies/nodsweep.vqa: + Offset: 256141622 + Length: 3642174 + ^Content/cnc/movies/nodlose.vqa: + Offset: 259783796 + Length: 5148924 + ^Content/cnc/movies/nodflees.vqa: + Offset: 264932720 + Length: 3056426 + ^Content/cnc/movies/nodfinal.vqa: + Offset: 267989146 + Length: 23326586 + ^Content/cnc/movies/nodend4.vqa: + Offset: 291315732 + Length: 25535232 + ^Content/cnc/movies/nodend3.vqa: + Offset: 316850964 + Length: 25725824 + ^Content/cnc/movies/nodend2.vqa: + Offset: 342576788 + Length: 27214048 + ^Content/cnc/movies/nodend1.vqa: + Offset: 369790836 + Length: 27172272 + ^Content/cnc/movies/nod9.vqa: + Offset: 396963108 + Length: 9357980 + ^Content/cnc/movies/nod8.vqa: + Offset: 406321088 + Length: 11597940 + ^Content/cnc/movies/nod6.vqa: + Offset: 417919028 + Length: 5007744 + ^Content/cnc/movies/nod5.vqa: + Offset: 422926772 + Length: 6641700 + ^Content/cnc/movies/nod4b.vqa: + Offset: 429568472 + Length: 3867618 + ^Content/cnc/movies/nod4a.vqa: + Offset: 433436090 + Length: 3838924 + ^Content/cnc/movies/nod3.vqa: + Offset: 437275014 + Length: 3603314 + ^Content/cnc/movies/nod2.vqa: + Offset: 440878328 + Length: 7200720 + ^Content/cnc/movies/nod1pre.vqa: + Offset: 448079048 + Length: 395720 + ^Content/cnc/movies/nod13.vqa: + Offset: 448474768 + Length: 2746626 + ^Content/cnc/movies/nod12.vqa: + Offset: 451221394 + Length: 6576562 + ^Content/cnc/movies/nod11.vqa: + Offset: 457797956 + Length: 4629270 + ^Content/cnc/movies/nod10b.vqa: + Offset: 462427226 + Length: 6386444 + ^Content/cnc/movies/nod10a.vqa: + Offset: 468813670 + Length: 7205632 + ^Content/cnc/movies/nod1.vqa: + Offset: 476019302 + Length: 4663258 + ^Content/cnc/movies/nitejump.vqa: + Offset: 480682560 + Length: 3702838 + ^Content/cnc/movies/napalm.vqa: + Offset: 484385398 + Length: 4004146 + ^Content/cnc/movies/logo.vqa: + Offset: 488389544 + Length: 3562630 + ^Content/cnc/movies/landing.vqa: + Offset: 491952174 + Length: 1617094 + ^Content/cnc/movies/kanepre.vqa: + Offset: 493569268 + Length: 17220712 + ^Content/cnc/movies/intro2.vqa: + Offset: 510789980 + Length: 21058732 + ^Content/cnc/movies/insites.vqa: + Offset: 531848712 + Length: 460482 + ^Content/cnc/movies/hellvaly.vqa: + Offset: 532309194 + Length: 6658950 + ^Content/cnc/movies/gunboat.vqa: + Offset: 538968144 + Length: 3203706 + ^Content/cnc/movies/generic.vqa: + Offset: 542171850 + Length: 1452820 + ^Content/cnc/movies/gdilose.vqa: + Offset: 543624670 + Length: 2097912 + ^Content/cnc/movies/gdiend2.vqa: + Offset: 545722582 + Length: 25242946 + ^Content/cnc/movies/gdiend1.vqa: + Offset: 570965528 + Length: 25311636 + ^Content/cnc/movies/gdi9.vqa: + Offset: 596277164 + Length: 11806994 + ^Content/cnc/movies/gdi8b.vqa: + Offset: 608084158 + Length: 5324410 + ^Content/cnc/movies/gdi8a.vqa: + Offset: 613408568 + Length: 4672548 + ^Content/cnc/movies/gdi7.vqa: + Offset: 618081116 + Length: 4241952 + ^Content/cnc/movies/gdi6.vqa: + Offset: 622323068 + Length: 3959650 + ^Content/cnc/movies/gdi5.vqa: + Offset: 626282718 + Length: 3818244 + ^Content/cnc/movies/gdi3lose.vqa: + Offset: 630100962 + Length: 2265770 + ^Content/cnc/movies/gdi3.vqa: + Offset: 632366732 + Length: 5443848 + ^Content/cnc/movies/gdi2.vqa: + Offset: 637810580 + Length: 7883500 + ^Content/cnc/movies/gdi15.vqa: + Offset: 645694080 + Length: 11684610 + ^Content/cnc/movies/gdi14.vqa: + Offset: 657378690 + Length: 5282770 + ^Content/cnc/movies/gdi13.vqa: + Offset: 662661460 + Length: 6900914 + ^Content/cnc/movies/gdi12.vqa: + Offset: 669562374 + Length: 3669404 + ^Content/cnc/movies/gdi11.vqa: + Offset: 673231778 + Length: 5895754 + ^Content/cnc/movies/gdi10.vqa: + Offset: 679127532 + Length: 5761514 + ^Content/cnc/movies/gdi1.vqa: + Offset: 684889046 + Length: 6341298 + ^Content/cnc/movies/gameover.vqa: + Offset: 691230344 + Length: 2087322 + ^Content/cnc/movies/forestkl.vqa: + Offset: 693317666 + Length: 1500986 + ^Content/cnc/movies/flyy.vqa: + Offset: 694818652 + Length: 1373532 + ^Content/cnc/movies/flag.vqa: + Offset: 696192184 + Length: 4308680 + ^Content/cnc/movies/dino.vqa: + Offset: 700500864 + Length: 1347896 + ^Content/cnc/movies/dessweep.vqa: + Offset: 701848760 + Length: 3563646 + ^Content/cnc/movies/desolat.vqa: + Offset: 705412406 + Length: 8385122 + ^Content/cnc/movies/deskill.vqa: + Offset: 713797528 + Length: 1483634 + ^Content/cnc/movies/desflees.vqa: + Offset: 715281162 + Length: 2698426 + ^Content/cnc/movies/consyard.vqa: + Offset: 717979588 + Length: 7864652 + ^Content/cnc/movies/cc2tease.vqa: + Offset: 725844240 + Length: 12506712 + ^Content/cnc/movies/bombflee.vqa: + Offset: 738350952 + Length: 2859726 + ^Content/cnc/movies/bombaway.vqa: + Offset: 741210678 + Length: 5579588 + ^Content/cnc/movies/bcanyon.vqa: + Offset: 746790266 + Length: 5172288 + ^Content/cnc/movies/banner.vqa: + Offset: 751962554 + Length: 2229408 + delete: ^Content/cnc/movies.mix origin: C&C The Ultimate Collection (Origin version, English) Type: Install RegistryKey: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\EA Games\CNC and The Covert Operations diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index 36d9c615f8..7cad0624be 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -233,19 +233,19 @@ ModContent: Packages: base: Base Game Files TestFiles: ^Content/ra/allies.mix, ^Content/ra/conquer.mix, ^Content/ra/interior.mix, ^Content/ra/redalert.mix, ^Content/ra/russian.mix, ^Content/ra/snow.mix, ^Content/ra/sounds.mix, ^Content/ra/temperat.mix - Sources: allied, allied-linux, soviet, soviet-linux, origin + Sources: allied, allied-linux, soviet, soviet-linux, tfd, origin Required: true Download: basefiles music: Base Game Music TestFiles: ^Content/ra/scores.mix - Sources: allied, allied-linux, soviet, soviet-linux, origin + Sources: allied, allied-linux, soviet, soviet-linux, tfd, origin Download: music movies-allied: Allied Campaign Briefings TestFiles: ^Content/ra/movies1.mix - Sources: allied, allied-linux, origin + Sources: allied, allied-linux, tfd, origin movies-soviet: Soviet Campaign Briefings TestFiles: ^Content/ra/movies2.mix - Sources: soviet, soviet-linux, origin + Sources: soviet, soviet-linux, tfd, origin music-counterstrike: Counterstrike Music TestFiles: ^Content/ra/music/araziod.aud, ^Content/ra/music/backstab.aud, ^Content/ra/music/chaos2.aud, ^Content/ra/music/shut_it.aud, ^Content/ra/music/2nd_hand.aud, ^Content/ra/music/twinmix1.aud, ^Content/ra/music/under3.aud, ^Content/ra/music/vr2.aud, Sources: counterstrike, counterstrike-linux, origin @@ -536,6 +536,51 @@ ModContent: ^Content/ra/music/wastelnd.aud: Offset: 263929718 Length: 2721563 + tfd: C&C The First Decade (English) + IDFiles: + data1.hdr: bef3a08c3fc1b1caf28ca0dbb97c1f900005930e + data1.cab: 12ad6113a6890a1b4d5651a75378c963eaf513b9 + Install: + extract-iscab: data1.hdr + Volumes: + 3: data3.cab + 4: data4.cab + 5: data5.cab + Extract: + ^Content/ra/main.mix: Red Alert\\MAIN.MIX + ^Content/ra/redalert.mix: Red Alert\\REDALERT.MIX + extract-raw: ^Content/ra/main.mix + ^Content/ra/movies1.mix: + Offset: 417051805 + Length: 404691306 + ^Content/ra/interior.mix: + Offset: 821743111 + Length: 249490 + ^Content/ra/conquer.mix: + Offset: 840028549 + Length: 2192279 + ^Content/ra/allies.mix: + Offset: 842220828 + Length: 319181 + ^Content/ra/temperat.mix: + Offset: 842540009 + Length: 1043672 + ^Content/ra/sounds.mix: + Offset: 843583681 + Length: 1385637 + ^Content/ra/snow.mix: + Offset: 844969318 + Length: 1035716 + ^Content/ra/scores.mix: + Offset: 846005034 + Length: 67742203 + ^Content/ra/russian.mix: + Offset: 913747237 + Length: 274732 + ^Content/ra/movies2.mix: + Offset: 914022190 + Length: 417051731 + delete: ^Content/ra/main.mix origin: C&C The Ultimate Collection (Origin version, English) Type: Install RegistryKey: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\EA Games\Command and Conquer Red Alert diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index e8eca77538..886896e9c8 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -265,12 +265,12 @@ ModContent: Packages: base: Base Game Files TestFiles: ^Content/ts/cache.mix, ^Content/ts/conquer.mix, ^Content/ts/isosnow.mix, ^Content/ts/isotemp.mix, ^Content/ts/local.mix, ^Content/ts/sidec01.mix, ^Content/ts/sidec02.mix, ^Content/ts/sno.mix, ^Content/ts/snow.mix, ^Content/ts/sounds.mix, ^Content/ts/speech01.mix, ^Content/ts/tem.mix, ^Content/ts/temperat.mix - Sources: tibsun, tibsun-linux, origin + Sources: tibsun, tibsun-linux, tfd, origin Required: true Download: basefiles music: Base Game Music TestFiles: ^Content/ts/scores.mix - Sources: tibsun, tibsun-linux, origin + Sources: tibsun, tibsun-linux, tfd, origin Download: music Downloads: basefiles: Base Freeware Content @@ -395,6 +395,62 @@ ModContent: ^Content/ts/temperat.mix: Offset: 73056940 Length: 2037606 + tfd: C&C The First Decade (English) + IDFiles: + data1.hdr: bef3a08c3fc1b1caf28ca0dbb97c1f900005930e + data1.cab: 12ad6113a6890a1b4d5651a75378c963eaf513b9 + Install: + extract-iscab: data1.hdr + Volumes: + 6: data6.cab + 7: data7.cab + Extract: + ^Content/ts/scores.mix: Tiberian Sun\SUN\SCORES.MIX + ^Content/ts/tibsun.mix: Tiberian Sun\SUN\TIBSUN.MIX + extract-raw: ^Content/ts/tibsun.mix + ^Content/ts/cache.mix: + Offset: 300 + Length: 169176 + ^Content/ts/conquer.mix: + Offset: 169484 + Length: 5700088 + ^Content/ts/isosnow.mix: + Offset: 5869580 + Length: 7624750 + ^Content/ts/isotemp.mix: + Offset: 13494332 + Length: 8617646 + ^Content/ts/local.mix: + Offset: 22111980 + Length: 18044736 + ^Content/ts/sidec01.mix: + Offset: 40156716 + Length: 998476 + ^Content/ts/sidec02.mix: + Offset: 41155196 + Length: 1039996 + ^Content/ts/snow.mix: + Offset: 56104508 + Length: 2087806 + ^Content/ts/sno.mix: + Offset: 58192316 + Length: 7826 + ^Content/ts/sounds.mix: + Offset: 58200156 + Length: 3224136 + ^Content/ts/speech01.mix: + Offset: 61424300 + Length: 6028236 + ^Content/ts/speech02.mix: + Offset: 67452540 + Length: 5596628 + ^Content/ts/tem.mix: + Offset: 73049180 + Length: 7746 + ^Content/ts/temperat.mix: + Offset: 73056940 + Length: 2037606 + delete: ^Content/ts/tibsun.mix origin: C&C The Ultimate Collection (Origin version, English) Type: Install RegistryKey: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\EA Games\Command and Conquer Tiberian Sun