Add Microsoft LZ-compressed files support

This commit is contained in:
Matt Nadareski
2022-12-28 22:54:56 -08:00
parent 555dbd592c
commit 9dc21c01f1
6 changed files with 145 additions and 42 deletions

View File

@@ -85,6 +85,44 @@ namespace BurnOutSharp.Compression
return decompressed;
}
/// <summary>
/// Reconstructs the full filename of the compressed file
/// </summary>
public static string GetExpandedName(string input, out LZERROR error)
{
// Try to open the file as a compressed stream
var fileStream = File.Open(input, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var state = new LZ().Open(fileStream, out error);
if (state?.Window == null)
return null;
// Get the extension for modification
string inputExtension = Path.GetExtension(input).TrimStart('.');
// If we have no extension
if (string.IsNullOrWhiteSpace(inputExtension))
return Path.GetFileNameWithoutExtension(input);
// If we have an extension of length 1
if (inputExtension.Length == 1)
{
if (inputExtension == "_")
return $"{Path.GetFileNameWithoutExtension(input)}.{char.ToLower(state.LastChar)}";
else
return Path.GetFileNameWithoutExtension(input);
}
// If we have an extension that doesn't end in an underscore
if (!inputExtension.EndsWith("_"))
return Path.GetFileNameWithoutExtension(input);
// Build the new filename
bool isLowerCase = char.IsUpper(input[0]);
char replacementChar = isLowerCase ? char.ToLower(state.LastChar) : char.ToUpper(state.LastChar);
string outputExtension = inputExtension.Substring(0, inputExtension.Length - 1) + replacementChar;
return $"{Path.GetFileNameWithoutExtension(input)}.{outputExtension}";
}
#endregion
#region State Management
@@ -175,48 +213,6 @@ namespace BurnOutSharp.Compression
#endregion
#region File Name Handling
/// <summary>
/// Reconstructs the full filename of the compressed file
/// </summary>
public string GetExpandedName(string input, out LZERROR error)
{
// Try to open the file as a compressed stream
var fileStream = File.Open(input, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var state = Open(fileStream, out error);
if (state?.Window == null)
return null;
// Get the extension for modification
string inputExtension = Path.GetExtension(input).TrimStart('.');
// If we have no extension
if (string.IsNullOrWhiteSpace(inputExtension))
return Path.GetFileNameWithoutExtension(input);
// If we have an extension of length 1
if (inputExtension.Length == 1)
{
if (inputExtension == "_")
return $"{Path.GetFileNameWithoutExtension(input)}.{char.ToLower(state.LastChar)}";
else
return Path.GetFileNameWithoutExtension(input);
}
// If we have an extension that doesn't end in an underscore
if (!inputExtension.EndsWith("_"))
return Path.GetFileNameWithoutExtension(input);
// Build the new filename
bool isLowerCase = char.IsUpper(input[0]);
char replacementChar = isLowerCase ? char.ToLower(state.LastChar) : char.ToUpper(state.LastChar);
string outputExtension = inputExtension.Substring(0, inputExtension.Length - 1) + replacementChar;
return $"{Path.GetFileNameWithoutExtension(input)}.{outputExtension}";
}
#endregion
#region Stream Functionality
/// <summary>

View File

@@ -60,6 +60,11 @@
/// </summary>
MicrosoftCAB,
/// <summary>
/// Microsoft LZ-compressed file
/// </summary>
MicrosoftLZ,
/// <summary>
/// MPQ game data archive
/// </summary>

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using BurnOutSharp.Compression;
using BurnOutSharp.Interfaces;
using static BurnOutSharp.Utilities.Dictionary;
namespace BurnOutSharp.FileType
{
/// <summary>
/// Microsoft LZ-compressed Files (LZ32)
/// </summary>
/// <remarks>This is treated like an archive type due to the packing style</remarks>
public class MicrosoftLZ : IScannable
{
/// <inheritdoc/>
public ConcurrentDictionary<string, ConcurrentQueue<string>> Scan(Scanner scanner, string file)
{
if (!File.Exists(file))
return null;
using (var fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))
{
return Scan(scanner, fs, file);
}
}
/// <inheritdoc/>
public ConcurrentDictionary<string, ConcurrentQueue<string>> Scan(Scanner scanner, Stream stream, string file)
{
// If the LZ file itself fails
try
{
string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempPath);
byte[] data = LZ.Decompress(stream);
// Create the temp filename
string tempFile = "temp.bin";
if (!string.IsNullOrEmpty(file))
{
string expandedFilePath = LZ.GetExpandedName(file, out _);
tempFile = Path.GetFileName(expandedFilePath).TrimEnd('\0');
if (tempFile.EndsWith(".ex"))
tempFile += "e";
else if (tempFile.EndsWith(".dl"))
tempFile += "l";
}
tempFile = Path.Combine(tempPath, tempFile);
// Write the file data to a temp file
using (Stream tempStream = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
{
tempStream.Write(data, 0, data.Length);
}
// Collect and format all found protections
var protections = scanner.GetProtections(tempPath);
// If temp directory cleanup fails
try
{
Directory.Delete(tempPath, true);
}
catch (Exception ex)
{
if (scanner.IncludeDebug) Console.WriteLine(ex);
}
// Remove temporary path references
StripFromKeys(protections, tempPath);
return protections;
}
catch (Exception ex)
{
if (scanner.IncludeDebug) Console.WriteLine(ex);
}
return null;
}
}
}

View File

@@ -433,6 +433,14 @@ namespace BurnOutSharp
AppendToDictionary(protections, subProtections);
}
// Microsoft LZ
if (fileName != null && scannable is MicrosoftLZ)
{
var subProtections = scannable.Scan(this, fileName);
PrependToKeys(subProtections, fileName);
AppendToDictionary(protections, subProtections);
}
// MSI
if (fileName != null && scannable is MSI)
{

View File

@@ -120,6 +120,13 @@ namespace BurnOutSharp.Tools
#endregion
#region MicrosoftLZ
if (magic.StartsWith(new byte?[] { 0x53, 0x5a, 0x44, 0x44, 0x88, 0xf0, 0x27, 0x33 }))
return SupportedFileType.MicrosoftLZ;
#endregion
#region MPQ
if (magic.StartsWith(new byte?[] { 0x4d, 0x50, 0x51, 0x1a }))
@@ -633,6 +640,7 @@ namespace BurnOutSharp.Tools
case SupportedFileType.InstallShieldArchiveV3: return new FileType.InstallShieldArchiveV3();
case SupportedFileType.InstallShieldCAB: return new FileType.InstallShieldCAB();
case SupportedFileType.MicrosoftCAB: return new FileType.MicrosoftCAB();
case SupportedFileType.MicrosoftLZ: return new FileType.MicrosoftLZ();
case SupportedFileType.MPQ: return new FileType.MPQ();
case SupportedFileType.MSI: return new FileType.MSI();
case SupportedFileType.PAK: return new FileType.PAK();

View File

@@ -158,6 +158,7 @@ Below is a list of container formats that are supported in some way:
| InstallShield CAB | No | Yes | Yes | Via `UnshieldSharp` |
| Linear Executable | No | No | No | Skeleton only |
| Microsoft cabinet file | Yes | Yes | Yes | Via `WixToolset.Dtf` / ~~`LibMSPackSharp`~~ (Currently disabled) |
| Microsoft LZ-compressed files | No | Yes | Yes | |
| MoPaQ game data archive (MPQ) | No | Yes | Yes | Via `StormLibSharp` |
| Microsoft installation package (MSI) | No | Yes | Yes | Via `OpenMcdf` |
| MS-DOS Executable | Yes | Yes | No | Incomplete |