Files
OpenRA/OpenRA.Mods.Common/FileSystem/InstallShieldPackage.cs
RoosterDragon 36660b89e9 Silence IDE0290.
This rule recommends use of primary constructors. Apply the suggestions on simple POCOs, adjusting some to readonly/structs/records at the same time. For more complex classes, the use of primary constructors is more distracting than helpful, so silence the rule. IDEs can still offer fixes for using primary constructors, but it will not show up as a build issue.
2025-04-02 10:18:32 +03:00

156 lines
3.8 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using OpenRA.FileSystem;
using OpenRA.Mods.Common.FileFormats;
using FS = OpenRA.FileSystem.FileSystem;
namespace OpenRA.Mods.Common.FileSystem
{
public class InstallShieldLoader : IPackageLoader
{
public sealed class InstallShieldPackage : IReadOnlyPackage
{
public readonly record struct Entry(uint Offset, uint Length);
public string Name { get; }
public IEnumerable<string> Contents => index.Keys;
readonly Dictionary<string, Entry> index;
readonly Stream s;
readonly long dataStart = 255;
public InstallShieldPackage(Stream s, string filename)
{
Name = filename;
this.s = s;
try
{
// Parse package header
s.ReadUInt32(); // signature
s.Position += 8;
s.ReadUInt16(); // FileCount
s.Position += 4;
s.ReadUInt32(); // ArchiveSize
s.Position += 19;
var tocAddress = s.ReadInt32();
s.Position += 4;
var dirCount = s.ReadUInt16();
// Parse the directory list
s.Position = tocAddress;
// Parse directories
var directories = new Dictionary<string, uint>(dirCount);
var totalFileCount = 0;
for (var i = 0; i < dirCount; i++)
{
// Parse directory header
var fileCount = s.ReadUInt16();
var chunkSize = s.ReadUInt16();
var nameLength = s.ReadUInt16();
var dirName = s.ReadASCII(nameLength);
// Skip to the end of the chunk
s.Position += chunkSize - nameLength - 6;
directories.Add(dirName, fileCount);
totalFileCount += fileCount;
}
// Parse files
index = new Dictionary<string, Entry>(totalFileCount);
foreach (var dir in directories)
for (var i = 0; i < dir.Value; i++)
ParseFile(dir.Key);
index.TrimExcess();
}
catch
{
Dispose();
throw;
}
}
uint accumulatedData = 0;
void ParseFile(string dirName)
{
s.Position += 7;
var compressedSize = s.ReadUInt32();
s.Position += 12;
var chunkSize = s.ReadUInt16();
s.Position += 4;
var nameLength = s.ReadUInt8();
var fileName = dirName + "\\" + s.ReadASCII(nameLength);
// Use index syntax to overwrite any duplicate entries with the last value
index[fileName] = new Entry(accumulatedData, compressedSize);
accumulatedData += compressedSize;
// Skip to the end of the chunk
s.Position += chunkSize - nameLength - 30;
}
public Stream GetStream(string filename)
{
if (!index.TryGetValue(filename, out var e))
return null;
s.Seek(dataStart + e.Offset, SeekOrigin.Begin);
var ret = new MemoryStream();
Blast.Decompress(s, ret);
ret.Seek(0, SeekOrigin.Begin);
return ret;
}
public IReadOnlyPackage OpenPackage(string filename, FS context)
{
// Not implemented
return null;
}
public bool Contains(string filename)
{
return index.ContainsKey(filename);
}
public IReadOnlyDictionary<string, Entry> Index => new ReadOnlyDictionary<string, Entry>(index);
public void Dispose()
{
s.Dispose();
}
}
bool IPackageLoader.TryParsePackage(Stream s, string filename, FS context, out IReadOnlyPackage package)
{
// Take a peek at the file signature
var signature = s.ReadUInt32();
s.Position -= 4;
if (signature != 0x8C655D13)
{
package = null;
return false;
}
package = new InstallShieldPackage(s, filename);
return true;
}
}
}