fix SZDD archive parsing thanks to GPLv2-licensed code :)

This commit is contained in:
Michael Becker 2020-08-07 02:16:12 -04:00
parent b63842ab15
commit 2f720bd471
No known key found for this signature in database
GPG Key ID: 506F54899E2BFED7
3 changed files with 616 additions and 167 deletions

View File

@ -0,0 +1,414 @@
using System;
using System.IO;
/*
* SZDDComp.impl.cs: Microsoft "compress.exe/expand.exe" compatible compressor
*
* Copyright (c) 2000 Martin Hinner <mhi@penguin.cz>
* Algorithm & data structures by M. Winterhoff <100326.2776@compuserve.com>
* C# port Copyright (c) 2011 Francis Gagné <fragag@hotmail.com> (Compressor) / Michael Ratzlaff <sonicmike2@yahoo.com> (Decompressor)
*
* 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 2, 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.MSCompressed.Internal
{
public static class SZDDComp
{
private const int N = 0x1000;
private const int F = 0x10;
private const int THRESHOLD = 3;
private const int NIL = -1;
private static readonly byte[] Magic = new byte[] { 0x53, 0x5A, 0x44, 0x44, 0x88, 0xF0, 0x27, 0x33, 0x41, 0x00 };
public static void Encode(Stream input, Stream output)
{
int ch, i, run, len, match, size, mask;
byte[] buf = new byte[17];
byte[] byteBuffer = new byte[8];
Buffer buffer = new Buffer();
output.Write(Magic, 0, Magic.Length);
Int32ToBytesLE(checked((int)(input.Length - input.Position)), byteBuffer);
output.Write(byteBuffer, 0, 4);
size = mask = 1;
buf[0] = 0;
i = N - F - F;
for (len = 0; len < F && (ch = input.ReadByte()) != -1; len++)
{
buffer.SetHead(i + F, (byte)ch);
i = (i + 1) & (N - 1);
}
run = len;
do
{
ch = input.ReadByte();
if (i >= N - F)
{
buffer.Delete(i + F - N);
buffer.SetHead(i + F, (byte)ch);
buffer.SetHead(i + F - N, (byte)ch);
}
else
{
buffer.Delete(i + F);
buffer.SetHead(i + F, (byte)ch);
}
match = buffer.Insert(i, run);
if (ch == -1)
{
run--;
len--;
}
if (len++ >= run)
{
if (match >= THRESHOLD)
{
buf[size++] = (byte)buffer.Position;
buf[size++] = (byte)(((buffer.Position >> 4) & 0xF0) + (match - 3));
len -= match;
}
else
{
buf[0] |= (byte)mask;
buf[size++] = buffer.GetHead(i);
len--;
}
mask += mask;
if ((mask & 0xFF) == 0)
{
output.Write(buf, 0, size);
size = mask = 1;
buf[0] = 0;
}
}
i = (i + 1) & (N - 1);
} while (len > 0);
if (size > 1)
{
output.Write(buf, 0, size);
}
}
private static void Int32ToBytesLE(int value, byte[] bytes)
{
bytes[0] = (byte)value;
bytes[1] = (byte)(value >> 8);
bytes[2] = (byte)(value >> 16);
bytes[3] = (byte)(value >> 24);
}
private class Buffer
{
private const int DadOffset = 1;
private const int LeftSonOffset = 1 + N;
private const int RightSonOffset = 1 + N + N;
private const int RootOffset = 1 + N + N + N;
int pos;
byte[] head = new byte[N + F];
int[] node = new int[N + 1 + N + N + 256];
/// <summary>
/// Initializes a new instance of the <see cref="Buffer" /> class.
/// </summary>
public Buffer()
{
for (int i = 0; i < 256; i++)
{
SetRoot(i, NIL);
}
for (int i = NIL; i < N; i++)
{
SetDad(i, NIL);
}
}
public int Position
{
get { return this.pos; }
}
public int Insert(int i, int run)
{
int c = 0, j, k, l, n, match;
int idx;
k = l = 1;
match = THRESHOLD - 1;
idx = RootOffset + head[i];
SetLeftSon(i, NIL);
SetRightSon(i, NIL);
while ((j = node[idx]) != NIL)
{
n = Math.Min(k, l);
while (n < run && (c = (head[j + n] - head[i + n])) == 0)
{
n++;
}
if (n > match)
{
match = n;
pos = j;
}
if (c < 0)
{
idx = LeftSonOffset + j;
k = n;
}
else if (c > 0)
{
idx = RightSonOffset + j;
l = n;
}
else
{
SetDad(j, NIL);
SetDad(GetLeftSon(j), LeftSonOffset + i);
SetDad(GetRightSon(j), RightSonOffset + i);
SetLeftSon(i, GetLeftSon(j));
SetRightSon(i, GetRightSon(j));
break;
}
}
SetDad(i, idx);
node[idx] = i;
return match;
}
public void Delete(int z)
{
if (GetDad(z) != NIL)
{
int j;
if (GetRightSon(z) == NIL)
{
j = GetLeftSon(z);
}
else if (GetLeftSon(z) == NIL)
{
j = GetRightSon(z);
}
else
{
j = GetLeftSon(z);
if (GetRightSon(j) != NIL)
{
do
{
j = GetRightSon(j);
} while (GetRightSon(j) != NIL);
node[GetDad(j)] = GetLeftSon(j);
SetDad(GetLeftSon(j), GetDad(j));
SetLeftSon(j, GetLeftSon(z));
SetDad(GetLeftSon(z), LeftSonOffset + j);
}
SetRightSon(j, GetRightSon(z));
SetDad(GetRightSon(z), RightSonOffset + j);
}
SetDad(j, GetDad(z));
node[GetDad(z)] = j;
SetDad(z, NIL);
}
}
public byte GetHead(int index)
{
return head[index];
}
public void SetHead(int index, byte value)
{
head[index] = value;
}
private int GetDad(int index)
{
return node[DadOffset + index];
}
private int GetLeftSon(int index)
{
return node[LeftSonOffset + index];
}
private int GetRightSon(int index)
{
return node[RightSonOffset + index];
}
private void SetDad(int index, int value)
{
node[DadOffset + index] = value;
}
private void SetLeftSon(int index, int value)
{
node[LeftSonOffset + index] = value;
}
private void SetRightSon(int index, int value)
{
node[RightSonOffset + index] = value;
}
private void SetRoot(int index, int value)
{
node[RootOffset + index] = value;
}
}
public static int Decode(Stream infile, Stream outfile)
{
int bits, ch, i, j, len, mask;
byte[] tmpbuf;
byte[] buffer;
uint magic1;
uint magic2;
uint magic3;
ushort reserved;
uint filesize;
tmpbuf = new byte[4];
if (infile.Read(tmpbuf, 0, 4) == -1)
{
throw new Exception();
}
magic1 = BitConverter.ToUInt32(tmpbuf, 0);
if (magic1 == 0x44445A53U)
{
if (infile.Read(tmpbuf, 0, 4) == -1)
{
throw new Exception();
}
magic2 = BitConverter.ToUInt32(tmpbuf, 0);
if (infile.Read(tmpbuf, 0, 2) == -1)
{
throw new Exception();
}
reserved = BitConverter.ToUInt16(tmpbuf, 0);
if (infile.Read(tmpbuf, 0, 4) == -1)
{
throw new Exception();
}
filesize = BitConverter.ToUInt32(tmpbuf, 0);
if (magic2 != 0x3327F088L)
{
throw new Exception("This is not a MS-compressed file!");
}
}
else if (magic1 == 0x4A41574BU)
{
if (infile.Read(tmpbuf, 0, 4) == -1)
{
throw new Exception();
}
magic2 = BitConverter.ToUInt32(tmpbuf, 0);
if (infile.Read(tmpbuf, 0, 4) == -1)
{
throw new Exception();
}
magic3 = BitConverter.ToUInt32(tmpbuf, 0);
if (infile.Read(tmpbuf, 0, 2) == -1)
{
throw new Exception();
}
reserved = BitConverter.ToUInt16(tmpbuf, 0);
if (magic2 != 0xD127F088L || magic3 != 0x00120003L)
{
throw new Exception("This is not a MS-compressed file!");
}
throw new Exception("Unsupported version 6.22!");
}
else
{
throw new Exception("This is not a MS-compressed file!");
}
buffer = new byte[N];
for (int q = 0; q < buffer.Length; q++)
buffer[q] = 0x20;
i = N - F;
while (true)
{
bits = infile.ReadByte();
if (bits == -1)
break;
for (mask = 0x01; (mask & 0xFF) != 0; mask <<= 1)
{
if ((bits & mask) == 0)
{
j = infile.ReadByte();
if (j == -1)
break;
len = infile.ReadByte();
j += (len & 0xF0) << 4;
len = (len & 15) + 3;
while (len-- != 0)
{
buffer[i] = buffer[j];
outfile.WriteByte(buffer[i]);
j++;
j %= N;
i++;
i %= N;
}
}
else
{
ch = infile.ReadByte();
if (ch == -1)
break;
buffer[i] = (byte)ch;
outfile.WriteByte(buffer[i]);
i++;
i %= N;
}
}
}
return 0;
}
}
}

View File

@ -6,6 +6,9 @@
//
// Copyright (c) 2011-2020 Mike Becker's Software
//
// Portions of this program use SZDDComp, licensed under GPLv2 or later.
// See Internal/SZDDComp.cs for more information.
//
// 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
@ -20,6 +23,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using MBS.Framework;
using UniversalEditor.Accessors;
using UniversalEditor.ObjectModels.FileSystem;
@ -29,111 +35,117 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.MSCompressed
/// Provides a <see cref="DataFormat" /> for manipulating MSCompressed archive files.
/// </summary>
public class MSCompressedDataFormat : DataFormat
{
private readonly byte?[] KWAJsignal = new byte?[] { (byte)'K', (byte)'W', (byte)'A', (byte)'J', (byte)0x88, (byte)0xF0, (byte)0x27, (byte)0xD1 };
private readonly byte?[] SZDDsignal = new byte?[] { (byte)'S', (byte)'Z', (byte)'D', (byte)'D', (byte)0x88, (byte)0xF0, (byte)0x27, (byte)0x33 };
private readonly byte?[] SZsignal = new byte?[] { (byte)'S', (byte)'Z', (byte)' ', (byte)0x88, (byte)0xF0, (byte)0x27, (byte)0x33, (byte)0xD1 };
{
private readonly byte?[] KWAJsignal = new byte?[] { (byte)'K', (byte)'W', (byte)'A', (byte)'J', (byte)0x88, (byte)0xF0, (byte)0x27, (byte)0xD1 };
private readonly byte?[] SZDDsignal = new byte?[] { (byte)'S', (byte)'Z', (byte)'D', (byte)'D', (byte)0x88, (byte)0xF0, (byte)0x27, (byte)0x33 };
private readonly byte?[] SZsignal = new byte?[] { (byte)'S', (byte)'Z', (byte)' ', (byte)0x88, (byte)0xF0, (byte)0x27, (byte)0x33, (byte)0xD1 };
private static DataFormatReference _dfr = null;
protected override DataFormatReference MakeReferenceInternal()
{
protected override DataFormatReference MakeReferenceInternal()
{
if (_dfr == null)
{
_dfr = base.MakeReferenceInternal();
_dfr.Capabilities.Add(typeof(FileSystemObjectModel), DataFormatCapabilities.All);
}
return _dfr;
}
return _dfr;
}
protected override void LoadInternal(ref ObjectModel objectModel)
{
FileSystemObjectModel fsom = (objectModel as FileSystemObjectModel);
if (fsom == null) throw new ObjectModelNotSupportedException();
protected override void LoadInternal(ref ObjectModel objectModel)
{
FileSystemObjectModel fsom = (objectModel as FileSystemObjectModel);
if (fsom == null) throw new ObjectModelNotSupportedException();
IO.Reader br = base.Accessor.Reader;
MemoryAccessor ma = new MemoryAccessor();
IO.Writer bw = new IO.Writer(ma);
IO.Reader br = base.Accessor.Reader;
byte[] signal = br.ReadBytes(8);
if (signal.Match(SZDDsignal))
{
// Compression mode: only "A" (0x41) is valid here
byte compressonMode = br.ReadByte();
MemoryAccessor ma = new MemoryAccessor();
IO.Writer bw = new IO.Writer(ma);
// The character missing from the end of the filename (0=unknown)
char fileNameExt = br.ReadChar();
string fileName = Accessor.GetFileTitle();
char fileNameExt = '\0';
// The integer length of the file when unpacked
int unpackedLength = br.ReadInt32();
byte[] signal = br.ReadBytes(8);
if (signal.Match(SZDDsignal))
{
// Compression mode: only "A" (0x41) is valid here
byte compressonMode = br.ReadByte();
Decompress(br, bw, MSCompressedCompressionMethod.SZDD);
}
else if (signal.Match(SZsignal))
{
// The integer length of the file when unpacked
int unpackedLength = br.ReadInt32();
// The character missing from the end of the filename (0=unknown)
fileNameExt = br.ReadChar();
Decompress(br, bw, MSCompressedCompressionMethod.SZ);
}
else if (signal.Match(KWAJsignal))
{
// compression method (0-4)
MSCompressedKWAJCompressionMethod compressionMethod = (MSCompressedKWAJCompressionMethod)br.ReadInt16();
if (fileNameExt != '\0')
fileName = fileName.Substring(fileName.Length - 1) + fileNameExt;
// file offset of compressed data
short compressionFileOffset = br.ReadInt16();
// The integer length of the file when unpacked
int unpackedLength = br.ReadInt32();
MSCompressedKWAJHeaderFlags headerFlags = (MSCompressedKWAJHeaderFlags)br.ReadInt16();
if ((headerFlags & MSCompressedKWAJHeaderFlags.HasDecompressedLength) == MSCompressedKWAJHeaderFlags.HasDecompressedLength)
{
int decompressedLength = br.ReadInt32();
}
if ((headerFlags & MSCompressedKWAJHeaderFlags.UnknownBit1) == MSCompressedKWAJHeaderFlags.UnknownBit1)
{
short unknown1 = br.ReadInt16();
}
if ((headerFlags & MSCompressedKWAJHeaderFlags.HasExtraData) == MSCompressedKWAJHeaderFlags.HasExtraData)
{
short dataLength = br.ReadInt16();
byte[] extraData = br.ReadBytes(dataLength);
}
if ((headerFlags & MSCompressedKWAJHeaderFlags.HasFileName) == MSCompressedKWAJHeaderFlags.HasFileName)
{
string fileName = br.ReadNullTerminatedString();
}
if ((headerFlags & MSCompressedKWAJHeaderFlags.HasFileExtension) == MSCompressedKWAJHeaderFlags.HasFileExtension)
{
string fileExt = br.ReadNullTerminatedString();
}
if ((headerFlags & MSCompressedKWAJHeaderFlags.HasExtraText) == MSCompressedKWAJHeaderFlags.HasExtraText)
{
short dataLength = br.ReadInt16();
string extraData = br.ReadFixedLengthString(dataLength);
}
Decompress(br, bw, MSCompressedCompressionMethod.SZDD);
}
else if (signal.Match(SZsignal))
{
// The integer length of the file when unpacked
int unpackedLength = br.ReadInt32();
switch (compressionMethod)
{
case MSCompressedKWAJCompressionMethod.None:
{
Decompress(br, bw, MSCompressedCompressionMethod.None);
break;
}
case MSCompressedKWAJCompressionMethod.XOR:
{
Decompress(br, bw, MSCompressedCompressionMethod.XOR);
break;
}
case MSCompressedKWAJCompressionMethod.SZDD:
{
Decompress(br, bw, MSCompressedCompressionMethod.SZDD);
break;
}
case MSCompressedKWAJCompressionMethod.JeffJohnson:
{
int ringBufferPos = 4096 - 17;
Decompress(br, bw, MSCompressedCompressionMethod.SZ);
}
else if (signal.Match(KWAJsignal))
{
// compression method (0-4)
MSCompressedKWAJCompressionMethod compressionMethod = (MSCompressedKWAJCompressionMethod)br.ReadInt16();
/*
// file offset of compressed data
short compressionFileOffset = br.ReadInt16();
MSCompressedKWAJHeaderFlags headerFlags = (MSCompressedKWAJHeaderFlags)br.ReadInt16();
if ((headerFlags & MSCompressedKWAJHeaderFlags.HasDecompressedLength) == MSCompressedKWAJHeaderFlags.HasDecompressedLength)
{
int decompressedLength = br.ReadInt32();
}
if ((headerFlags & MSCompressedKWAJHeaderFlags.UnknownBit1) == MSCompressedKWAJHeaderFlags.UnknownBit1)
{
short unknown1 = br.ReadInt16();
}
if ((headerFlags & MSCompressedKWAJHeaderFlags.HasExtraData) == MSCompressedKWAJHeaderFlags.HasExtraData)
{
short dataLength = br.ReadInt16();
byte[] extraData = br.ReadBytes(dataLength);
}
if ((headerFlags & MSCompressedKWAJHeaderFlags.HasFileName) == MSCompressedKWAJHeaderFlags.HasFileName)
{
fileName = br.ReadNullTerminatedString();
}
if ((headerFlags & MSCompressedKWAJHeaderFlags.HasFileExtension) == MSCompressedKWAJHeaderFlags.HasFileExtension)
{
string fileExt = br.ReadNullTerminatedString();
}
if ((headerFlags & MSCompressedKWAJHeaderFlags.HasExtraText) == MSCompressedKWAJHeaderFlags.HasExtraText)
{
short dataLength = br.ReadInt16();
string extraData = br.ReadFixedLengthString(dataLength);
}
switch (compressionMethod)
{
case MSCompressedKWAJCompressionMethod.None:
{
Decompress(br, bw, MSCompressedCompressionMethod.None);
break;
}
case MSCompressedKWAJCompressionMethod.XOR:
{
Decompress(br, bw, MSCompressedCompressionMethod.XOR);
break;
}
case MSCompressedKWAJCompressionMethod.SZDD:
{
Decompress(br, bw, MSCompressedCompressionMethod.SZDD);
break;
}
case MSCompressedKWAJCompressionMethod.JeffJohnson:
{
int ringBufferPos = 4096 - 17;
/*
selected table = MATCHLEN
LOOP:
code = read huffman code using selected table (MATCHLEN or MATCHLEN2)
@ -150,88 +162,109 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.MSCompressed
if x != 31, selected table = MATCHLEN2
read {x+1} literals using LITERAL huffman table, copy as output and into the ring buffer
*/
break;
}
case MSCompressedKWAJCompressionMethod.MSZIP:
{
break;
}
}
}
break;
}
case MSCompressedKWAJCompressionMethod.MSZIP:
{
break;
}
}
}
}
bw.Flush();
bw.Close();
private void Decompress(IO.Reader br, IO.Writer bw, MSCompressedCompressionMethod method)
{
switch (method)
{
case MSCompressedCompressionMethod.None:
case MSCompressedCompressionMethod.XOR:
{
byte[] rest = br.ReadToEnd();
if (method == MSCompressedCompressionMethod.XOR)
{
for (int i = 0; i < rest.Length; i++) rest[i] ^= 0xFF;
}
bw.WriteBytes(rest);
break;
}
case MSCompressedCompressionMethod.SZDD:
case MSCompressedCompressionMethod.SZ:
{
byte[] window = new byte[4096];
int pos = 4096 - 16;
if (method == MSCompressedCompressionMethod.SZ)
{
pos = 4096 - 18;
}
for (int i = 0; i < 4096; i++)
{
window[i] = 0x20;
}
fsom.Files.Add(fileName, ma.ToArray());
}
System.IO.MemoryStream ms = new System.IO.MemoryStream();
while (!br.EndOfStream)
{
byte control = br.ReadByte();
for (int cbit = 0x01; ((cbit & 0xFF) == 0xFF); cbit <<= 1)
{
if ((control & cbit) == control)
{
// literal
bw.WriteByte((byte)(window[pos++] = br.ReadByte()));
}
else
{
// match
int matchpos = br.ReadByte();
int matchlen = br.ReadByte();
matchpos |= ((matchlen & 0xF0) << 4);
matchlen = ((matchlen & 0x0F) + 3);
while ((matchlen--) != 0)
{
bw.WriteByte((byte)(window[pos++] = window[matchpos++]));
pos &= 4095; matchpos &= 4095;
}
}
}
}
break;
}
case MSCompressedCompressionMethod.JeffJohnson:
{
break;
}
case MSCompressedCompressionMethod.MSZIP:
{
break;
}
}
}
private void Decompress(IO.Reader br, IO.Writer bw, MSCompressedCompressionMethod method)
{
switch (method)
{
case MSCompressedCompressionMethod.None:
case MSCompressedCompressionMethod.XOR:
{
byte[] rest = br.ReadToEnd();
if (method == MSCompressedCompressionMethod.XOR)
{
for (int i = 0; i < rest.Length; i++) rest[i] ^= 0xFF;
}
bw.WriteBytes(rest);
break;
}
case MSCompressedCompressionMethod.SZDD:
case MSCompressedCompressionMethod.SZ:
{
br.Seek(-14, IO.SeekOrigin.Current); // SZDDComp expects to start reading at the beginning of the 'SZDD' signature
protected override void SaveInternal(ObjectModel objectModel)
{
throw new NotImplementedException();
}
}
byte[] data = br.ReadToEnd();
System.IO.MemoryStream msin = new System.IO.MemoryStream(data);
System.IO.MemoryStream msout = new System.IO.MemoryStream();
Internal.SZDDComp.Decode(msin, msout);
bw.WriteBytes(msout.ToArray());
/*
byte[] window = new byte[4096];
window.Clear(0x20);
int pos = 4096 - 16;
if (method == MSCompressedCompressionMethod.SZ)
{
pos = 4096 - 18;
}
while (!br.EndOfStream)
{
byte control = br.ReadByte();
if (br.EndOfStream)
break;
for (int cbit = 0x01; ((cbit & 0xFF) != 0); cbit = (byte)(cbit << 1))
{
if ((control & cbit) != 0)
{
// literal
try
{
bw.WriteByte((byte)(window[(pos++ ^ window.Length)] = br.ReadByte()));
}
catch (Exception ex)
{
break;
}
}
else
{
// match
int matchpos = br.ReadByte();
int matchlen = br.ReadByte();
matchpos |= (byte)((matchlen & 0xF0) << 4);
matchlen = (byte)((matchlen & 0x0F) + 3);
while ((matchlen--) != 0)
{
bw.WriteByte((byte)(window[(pos++ ^ window.Length)] = window[matchpos++]));
pos &= 4095; matchpos &= 4095;
}
}
}
}
*/
break;
}
case MSCompressedCompressionMethod.JeffJohnson:
{
break;
}
case MSCompressedCompressionMethod.MSZIP:
{
break;
}
}
}
protected override void SaveInternal(ObjectModel objectModel)
{
throw new NotImplementedException();
}
}
}

View File

@ -152,6 +152,7 @@
<Compile Include="DataFormats\FileSystem\Microsoft\NTFS\Attributes\NTFSFileNameAttribute.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\NTFS\Attributes\NTFSStandardInformationAttribute.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\NTFS\Attributes\NTFSDataAttribute.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\MSCompressed\Internal\SZDDComp.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\UniversalEditor.Compression\UniversalEditor.Compression.csproj">
@ -203,5 +204,6 @@
<Folder Include="DataFormats\FileSystem\Microsoft\OLE\OLE1\" />
<Folder Include="DataFormats\FileSystem\Microsoft\NTFS\" />
<Folder Include="DataFormats\FileSystem\Microsoft\NTFS\Attributes\" />
<Folder Include="DataFormats\FileSystem\Microsoft\MSCompressed\Internal\" />
</ItemGroup>
</Project>