Files
OpenRA/OpenRA.Mods.Common/FileSystem/InstallShieldPackage.cs
RoosterDragon a4bb58007f Trim memory usage of IReadOnlyPackage implementations.
These implementations are often backed by a Dictionary, and tend to live a long time after being loaded. Ensure TrimExcess is called on the backing dictionaries to reduce the long term memory usage. In some cases, we can also preallocate the dictionary size for efficiency.
2024-04-06 10:47:19 +03:00

166 lines
4.0 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 struct Entry
{
public readonly uint Offset;
public readonly uint Length;
public Entry(uint offset, uint length)
{
Offset = offset;
Length = 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;
}
}
}