using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;
using OfficeOpenXml.Utils;
using OpenMcdf;
using System;
using System.Collections.Generic;
using System.IO;
using System.Globalization;
using System.Linq;
using System.Text;
using VBASync.Localization;
namespace VBASync.Model
{
internal class VbaTemporaryFolder : VbaFolder, IDisposable
{
private readonly ISystemOperations _so;
private bool _disposed;
private List<Module> _modules;
internal VbaTemporaryFolder() : this(new RealSystemOperations())
{
}
internal VbaTemporaryFolder(ISystemOperations so) : base(so, GetTemporaryDirectoryPath(so))
{
_so = so;
so.DirectoryCreateDirectory(FolderPath);
}
~VbaTemporaryFolder()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void FixCase(IDictionary<string, string> compareModules)
{
foreach (var m in _modules)
{
if (compareModules.ContainsKey(m.Name))
{
var path = _so.PathCombine(FolderPath, m.FileName);
_so.FileWriteAllText(path, ModuleProcessing.FixCase(compareModules[m.Name],
_so.FileReadAllText(path, ProjectEncoding)), ProjectEncoding);
}
}
}
public void Read(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("Path to file cannot be null or empty.", nameof(path));
}
Stream fs = null;
try
{
try
{
fs = _so.OpenFileForRead(path);
}
catch (IOException)
{
var dest = _so.PathCombine(FolderPath, "FileToExtract" + _so.PathGetExtension(path));
_so.FileCopy(path, dest);
path = dest;
fs = _so.OpenFileForRead(path);
}
CFStorage vbaProject;
if (_so.PathGetFileName(path).Equals("vbaProject.bin", StringComparison.InvariantCultureIgnoreCase))
{
vbaProject = new CompoundFile(fs).RootStorage;
}
else
{
var sig = new byte[4];
fs.Read(sig, 0, 4);
if (sig.SequenceEqual(new byte[] { 0x50, 0x4b, 0x03, 0x04 }))
{
var zipFile = new ZipFile(fs);
var zipEntry = zipFile.Cast<ZipEntry>().FirstOrDefault(e => e.Name.EndsWith("vbaProject.bin", StringComparison.InvariantCultureIgnoreCase));
if (zipEntry == null)
{
throw new ApplicationException("Cannot find 'vbaProject.bin' in ZIP archive.");
}
using (var sw = _so.CreateNewFile(_so.PathCombine(FolderPath, "vbaProject.bin")))
{
StreamUtils.Copy(zipFile.GetInputStream(zipEntry), sw, new byte[4096]);
}
fs.Dispose();
fs = _so.OpenFileForRead(_so.PathCombine(FolderPath, "vbaProject.bin"));
vbaProject = new CompoundFile(fs).RootStorage;
}
else
{
fs.Seek(0, SeekOrigin.Begin);
vbaProject = new CompoundFile(fs).GetAllNamedEntries("_VBA_PROJECT_CUR").FirstOrDefault() as CFStorage
?? new CompoundFile(fs).GetAllNamedEntries("OutlookVbaData").FirstOrDefault() as CFStorage
?? new CompoundFile(fs).GetAllNamedEntries("Macros").FirstOrDefault() as CFStorage;
}
}
if (vbaProject == null)
{
throw new ApplicationException("Cannot find VBA project storage in file.");
}
var projectLk = vbaProject.TryGetStream("PROJECTlk");
if (projectLk != null)
{
_so.FileWriteAllBytes(_so.PathCombine(FolderPath, "LicenseKeys.bin"), projectLk.GetData());
}
var projEncoding = Encoding.Default;
uint projSysKind = 1;
var projVersion = new Version(1, 1);
var projConstants = new List<string>();
string currentRefName = null;
var references = new List<Reference>();
var originalLibIds = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
_modules = new List<Module>();
Module currentModule = null;
var projStrings = new List<string>();
using (var br = new BinaryReader(new MemoryStream(DecompressStream(vbaProject.GetStorage("VBA").GetStream("dir")))))
{
Action<int> seek = i => br.BaseStream.Seek(i, SeekOrigin.Current);
while (br.BaseStream.Position < br.BaseStream.Length)
{
switch (br.ReadUInt16())
{
case 0x0001:
seek(4);
projSysKind = br.ReadUInt32();
break;
case 0x0002:
case 0x0014:
case 0x0008:
case 0x0007:
case 0x004a:
seek(8);
break;
case 0x0003:
seek(4);
projEncoding = Encoding.GetEncoding(br.ReadInt16());
break;
case 0x0004:
seek(br.ReadInt32());
break;
case 0x0005:
case 0x0006:
seek(br.ReadInt32() + 2);
seek(br.ReadInt32());
break;
case 0x0009:
seek(4);
projVersion = new Version(br.ReadInt32(), br.ReadInt16());
break;
case 0x000c:
seek(br.ReadInt32() + 2);
projConstants.AddRange(Encoding.Unicode.GetString(br.ReadBytes(br.ReadInt32())).Split(':').Select(s => s.Trim()));
if (projConstants.Count == 1 && string.IsNullOrEmpty(projConstants[0]))
{
projConstants.RemoveAt(0);
}
break;
case 0x0016:
seek(br.ReadInt32() + 2);
currentRefName = Encoding.Unicode.GetString(br.ReadBytes(br.ReadInt32()));
break;
case 0x0033:
originalLibIds.Add(currentRefName, projEncoding.GetString(br.ReadBytes(br.ReadInt32())));
break;
case 0x002f:
seek(4);
var libIdTwiddled = projEncoding.GetString(br.ReadBytes(br.ReadInt32()));
seek(6);
string nameRecordExtended = null;
if (br.PeekChar() == 0x16)
{
seek(2);
seek(br.ReadInt32() + 2);
nameRecordExtended = Encoding.Unicode.GetString(br.ReadBytes(br.ReadInt32()));
}
seek(6);
var libIdExtended = projEncoding.GetString(br.ReadBytes(br.ReadInt32()));
seek(6);
var originalTypeLib = new Guid(br.ReadBytes(16));
var cookie = br.ReadUInt32();
var refCtl = new ReferenceControl
{
Name = currentRefName,
Cookie = cookie,
LibIdExtended = libIdExtended,
LibIdTwiddled = libIdTwiddled,
NameRecordExtended = nameRecordExtended,
OriginalTypeLib = originalTypeLib
};
if (originalLibIds.ContainsKey(currentRefName))
{
refCtl.OriginalLibId = originalLibIds[currentRefName];
}
references.Add(refCtl);
break;
case 0x000d:
seek(4);
references.Add(new ReferenceRegistered
{
Name = currentRefName,
LibId = projEncoding.GetString(br.ReadBytes(br.ReadInt32()))
});
seek(6);
break;
case 0x000e:
seek(4);
references.Add(new ReferenceProject
{
Name = currentRefName,
LibIdAbsolute = projEncoding.GetString(br.ReadBytes(br.ReadInt32())),
LibIdRelative = projEncoding.GetString(br.ReadBytes(br.ReadInt32())),
Version = new Version(br.ReadInt32(), br.ReadInt16())
});
break;
case 0x000f:
seek(6);
break;
case 0x0013:
seek(6);
break;
case 0x0019:
seek(br.ReadInt32());
break;
case 0x0047:
_modules.Add(currentModule = new Module
{
Name = Encoding.Unicode.GetString(br.ReadBytes(br.ReadInt32()))
});
break;
case 0x001a:
seek(br.ReadInt32() + 2);
currentModule.StreamName = Encoding.Unicode.GetString(br.ReadBytes(br.ReadInt32()));
break;
case 0x001c:
seek(br.ReadInt32() + 2);
seek(br.ReadInt32());
break;
case 0x0031:
seek(4);
currentModule.Offset = br.ReadUInt32();
break;
case 0x001e:
seek(8);
break;
case 0x002c:
seek(6);
break;
case 0x0021:
seek(4);
break;
case 0x0022:
seek(4);
break;
case 0x0025:
currentModule.ReadOnly = true;
seek(4);
break;
case 0x0028:
currentModule.Private = true;
seek(4);
break;
case 0x002b:
currentModule = null;
seek(4);
break;
case 0x0010:
seek(4);
break;
default:
seek(-2);
throw new ApplicationException($"Unknown record id '0x{br.ReadInt16().ToString("X4")}'.");
}
}
}
ProjectEncoding = projEncoding;
using (var sr = new StreamReader(new MemoryStream(vbaProject.GetStream("PROJECT").GetData()), projEncoding))
{
string line;
while ((line = sr.ReadLine()) != null)
{
var split = line.Split('=');
var breakLoop = false;
switch (split[0]?.ToUpperInvariant())
{
case "MODULE":
_modules.First(m => string.Equals(m.Name, split[1], StringComparison.InvariantCultureIgnoreCase))
.Type = ModuleType.Standard;
break;
case "DOCUMENT":
var split2 = split[1].Split('/');
var mod = _modules.First(m => string.Equals(m.Name, split2[0], StringComparison.InvariantCultureIgnoreCase));
mod.Type = ModuleType.StaticClass;
mod.Version = uint.Parse(split2[1].Substring(2), NumberStyles.HexNumber);
break;
case "CLASS":
_modules.First(m => string.Equals(m.Name, split[1], StringComparison.InvariantCultureIgnoreCase))
.Type = ModuleType.Class;
break;
case "BASECLASS":
_modules.First(m => string.Equals(m.Name, split[1], StringComparison.InvariantCultureIgnoreCase))
.Type = ModuleType.Form;
break;
default:
if (line.Equals("[Workspace]", StringComparison.InvariantCultureIgnoreCase))
{
breakLoop = true;
}
else
{
projStrings.Add(line);
}
break;
}
if (breakLoop)
{
break;
}
}
}
DeleteBlankLinesFromEnd(projStrings);
projStrings.Insert(0, $"Version={projVersion}");
projStrings.Insert(0, $"SysKind={projSysKind.ToString(CultureInfo.InvariantCulture)}");
projStrings.Insert(0, $"CodePage={projEncoding.CodePage.ToString(CultureInfo.InvariantCulture)}");
projStrings.Add("");
projStrings.Add("[Constants]");
projStrings.AddRange(projConstants.Select(s => string.Join("=", s.Split('=').Select(t => t.Trim()))));
if (_modules.Any(m => m.Version > 0))
{
projStrings.Add("");
projStrings.Add("[DocTLibVersions]");
projStrings.AddRange(_modules.Where(m => m.Version > 0).Select(m => $"{m.Name}={m.Version.ToString(CultureInfo.InvariantCulture)}"));
}
foreach (var refer in references)
{
projStrings.Add("");
projStrings.Add($"[Reference {refer.Name}]");
projStrings.AddRange(refer.GetConfigStrings());
}
_so.FileWriteAllLines(_so.PathCombine(FolderPath, "Project.ini"), projStrings, Encoding.UTF8);
foreach (var m in _modules)
{
var moduleText = projEncoding.GetString(DecompressStream(vbaProject.GetStorage("VBA").GetStream(m.StreamName), m.Offset));
moduleText = (GetPrepend(m, vbaProject, projEncoding) + moduleText).TrimEnd('\r', '\n') + "\r\n";
_so.FileWriteAllText(_so.PathCombine(FolderPath, m.Name + ModuleProcessing.ExtensionFromType(m.Type)), moduleText, projEncoding);
}
foreach (var m in _modules.Where(mod => mod.Type == ModuleType.Form))
{
var cf = new CompoundFile();
CopyCfStreamsExcept(vbaProject.GetStorage(m.StreamName), cf.RootStorage, "\x0003VBFrame");
cf.RootStorage.CLSID = new Guid("c62a69f0-16dc-11ce-9e98-00aa00574a4f");
var frxPath = _so.PathCombine(FolderPath, m.Name + ".frx");
cf.Save(frxPath);
var bytes = _so.FileReadAllBytes(frxPath);
var size = bytes.Length;
using (var bw = new BinaryWriter(_so.OpenFileForWrite(frxPath)))
{
bw.Write((short)0x424c);
bw.Write(new byte[3]);
bw.Write(size >> 8);
bw.Write(new byte[15]);
bw.Write(bytes);
}
}
}
finally
{
fs.Dispose();
}
}
public void Write(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentException(VBASyncResources.ErrorPathCannotbeNullOrEmpty, nameof(filePath));
}
_so.FileDelete(_so.PathCombine(FolderPath, "vbaProject.bin"));
var cf = new CompoundFile();
var vbaProject = cf.RootStorage;
var projIni = new ProjectIni(_so.PathCombine(FolderPath, "Project.ini"));
projIni.AddFile(_so.PathCombine(FolderPath, "Project.ini.local"));
var projEncoding = Encoding.GetEncoding(projIni.GetInt("General", "CodePage") ?? Encoding.Default.CodePage);
if (!projEncoding.Equals(Encoding.Default))
{
projIni = new ProjectIni(_so.PathCombine(FolderPath, "Project.ini"), projEncoding);
projIni.AddFile(_so.PathCombine(FolderPath, "Project.ini.local"));
}
var projSysKind = (uint)(projIni.GetInt("General", "SysKind") ?? 1);
var projVersion = projIni.GetVersion("General", "Version") ?? new Version(1, 1);
var refs = new List<Reference>();
foreach (var refName in projIni.GetReferenceNames())
{
var refSubj = $"Reference {refName}";
if (projIni.GetString(refSubj, "LibIdTwiddled") != null)
{
refs.Add(new ReferenceControl
{
Cookie = (uint)(projIni.GetInt(refSubj, "Cookie") ?? 0),
LibIdExtended = projIni.GetString(refSubj, "LibIdExtended"),
LibIdTwiddled = projIni.GetString(refSubj, "LibIdTwiddled"),
Name = refName,
NameRecordExtended = projIni.GetString(refSubj, "NameRecordExtended"),
OriginalLibId = projIni.GetString(refSubj, "OriginalLibId"),
OriginalTypeLib = projIni.GetGuid(refSubj, "OriginalTypeLib") ?? Guid.Empty
});
}
else if (projIni.GetString(refSubj, "LibIdAbsolute") != null)
{
refs.Add(new ReferenceProject
{
LibIdAbsolute = projIni.GetString(refSubj, "LibIdAbsolute"),
LibIdRelative = projIni.GetString(refSubj, "LibIdRelative"),
Name = refName,
Version = projIni.GetVersion(refSubj, "Version")
});
}
else
{
refs.Add(new ReferenceRegistered
{
LibId = projIni.GetString(refSubj, "LibId"),
Name = refName
});
}
}
var mods = new List<Module>();
var modExts = new[] { ".bas", ".cls", ".frm" };
foreach (var modPath in _so.DirectoryGetFiles(FolderPath).Where(s => modExts.Contains(_so.PathGetExtension(s) ?? "", StringComparer.InvariantCultureIgnoreCase)))
{
var modName = _so.PathGetFileNameWithoutExtension(modPath);
var modText = EnsureCrLfEndings(_so.FileReadAllText(modPath, projEncoding));
var modType = ModuleProcessing.TypeFromText(modText);
projIni.RegisterModule(modName, modType, (uint)(projIni.GetInt("DocTLibVersions", modName) ?? 0));
mods.Add(new Module
{
DocString = GetAttribute(modText, "VB_Description"),
HelpContext = GetAttributeUInt(modText, "VB_HelpID"),
Name = modName,
StreamName = modName,
Type = modType
});
}
vbaProject.AddStream("PROJECT");
vbaProject.GetStream("PROJECT").SetData(projEncoding.GetBytes(projIni.GetProjectText()));
if (_so.FileExists(_so.PathCombine(FolderPath, "LicenseKeys.bin")))
{
vbaProject.AddStream("PROJECTlk");
vbaProject.GetStream("PROJECTlk").SetData(_so.FileReadAllBytes(_so.PathCombine(FolderPath, "LicenseKeys.bin")));
}
var projectWm = new List<byte>();
foreach (var modName in mods.Select(m => m.Name))
{
projectWm.AddRange(projEncoding.GetBytes(modName));
projectWm.Add(0x00);
projectWm.AddRange(Encoding.Unicode.GetBytes(modName));
projectWm.Add(0x00);
projectWm.Add(0x00);
}
projectWm.AddRange(new byte[] { 0x00, 0x00 });
vbaProject.AddStream("PROJECTwm");
vbaProject.GetStream("PROJECTwm").SetData(projectWm.ToArray());
vbaProject.AddStorage("VBA");
var vbaProjectVba = vbaProject.GetStorage("VBA");
vbaProjectVba.AddStream("_VBA_PROJECT");
vbaProjectVba.GetStream("_VBA_PROJECT").SetData(new byte[] { 0xCC, 0x61, 0xFF, 0xFF, 0x00, 0x00, 0x00 });
var dirUc = new List<byte>();
dirUc.AddRange(BitConverter.GetBytes((short)0x0001));
dirUc.AddRange(BitConverter.GetBytes(4));
dirUc.AddRange(BitConverter.GetBytes(projSysKind));
dirUc.AddRange(BitConverter.GetBytes((short)0x0002));
dirUc.AddRange(BitConverter.GetBytes(4));
dirUc.AddRange(BitConverter.GetBytes(0x00000409));
dirUc.AddRange(BitConverter.GetBytes((short)0x0014));
dirUc.AddRange(BitConverter.GetBytes(4));
dirUc.AddRange(BitConverter.GetBytes(0x00000409));
dirUc.AddRange(BitConverter.GetBytes((short)0x0003));
dirUc.AddRange(BitConverter.GetBytes(2));
dirUc.AddRange(BitConverter.GetBytes((short)projEncoding.CodePage));
var projNameBytes = projEncoding.GetBytes(projIni.GetString("General", "Name") ?? "");
dirUc.AddRange(BitConverter.GetBytes((short)0x0004));
dirUc.AddRange(BitConverter.GetBytes(projNameBytes.Length));
dirUc.AddRange(projNameBytes);
var projDocStringBytes = projEncoding.GetBytes(projIni.GetString("General", "Description") ?? "");
var projDocStringUtfBytes = Encoding.Unicode.GetBytes(projIni.GetString("General", "Description") ?? "");
dirUc.AddRange(BitConverter.GetBytes((short)0x0005));
dirUc.AddRange(BitConverter.GetBytes(projDocStringBytes.Length));
dirUc.AddRange(projDocStringBytes);
dirUc.AddRange(BitConverter.GetBytes((short)0x0040));
dirUc.AddRange(BitConverter.GetBytes(projDocStringUtfBytes.Length));
dirUc.AddRange(projDocStringUtfBytes);
var projHelpFileBytes = projEncoding.GetBytes(projIni.GetString("General", "HelpFile") ?? "");
dirUc.AddRange(BitConverter.GetBytes((short)0x0006));
dirUc.AddRange(BitConverter.GetBytes(projHelpFileBytes.Length));
dirUc.AddRange(projHelpFileBytes);
dirUc.AddRange(BitConverter.GetBytes((short)0x003D));
dirUc.AddRange(BitConverter.GetBytes(projHelpFileBytes.Length));
dirUc.AddRange(projHelpFileBytes);
dirUc.AddRange(BitConverter.GetBytes((short)0x0007));
dirUc.AddRange(BitConverter.GetBytes(4));
dirUc.AddRange(BitConverter.GetBytes(projIni.GetInt("General", "HelpContextID") ?? 0));
dirUc.AddRange(BitConverter.GetBytes((short)0x0008));
dirUc.AddRange(BitConverter.GetBytes(4));
dirUc.AddRange(BitConverter.GetBytes(0));
dirUc.AddRange(BitConverter.GetBytes((short)0x0009));
dirUc.AddRange(BitConverter.GetBytes(4));
dirUc.AddRange(BitConverter.GetBytes(projVersion.Major));
dirUc.AddRange(BitConverter.GetBytes((short)projVersion.Minor));
var projConstantsBytes = projEncoding.GetBytes(projIni.GetConstantsString());
var projConstantsUtfBytes = Encoding.Unicode.GetBytes(projIni.GetConstantsString());
dirUc.AddRange(BitConverter.GetBytes((short)0x000C));
dirUc.AddRange(BitConverter.GetBytes(projConstantsBytes.Length));
dirUc.AddRange(projConstantsBytes);
dirUc.AddRange(BitConverter.GetBytes((short)0x003C));
dirUc.AddRange(BitConverter.GetBytes(projConstantsUtfBytes.Length));
dirUc.AddRange(projConstantsUtfBytes);
foreach (var rfc in refs)
{
dirUc.AddRange(rfc.GetBytes(projEncoding));
}
dirUc.AddRange(BitConverter.GetBytes((short)0x000F));
dirUc.AddRange(BitConverter.GetBytes(2));
dirUc.AddRange(BitConverter.GetBytes((short)mods.Count));
dirUc.AddRange(BitConverter.GetBytes((short)0x0013));
dirUc.AddRange(BitConverter.GetBytes(2));
dirUc.AddRange(BitConverter.GetBytes((short)-1));
foreach (var mod in mods)
{
dirUc.AddRange(mod.GetBytes(projEncoding));
}
dirUc.AddRange(BitConverter.GetBytes((short)0x0010));
dirUc.AddRange(BitConverter.GetBytes(0));
vbaProjectVba.AddStream("dir");
vbaProjectVba.GetStream("dir").SetData(CompoundDocumentCompression.CompressPart(dirUc.ToArray()));
foreach (var mod in mods)
{
switch (mod.Type)
{
case ModuleType.Class:
case ModuleType.StaticClass:
vbaProjectVba.AddStream(mod.StreamName);
var fileText = _so.FileReadAllText(_so.PathCombine(FolderPath, mod.Name + ModuleProcessing.ExtensionFromType(mod.Type)), projEncoding);
vbaProjectVba.GetStream(mod.StreamName).SetData(CompoundDocumentCompression.CompressPart(projEncoding.GetBytes(SeparateClassCode(fileText))));
break;
case ModuleType.Form:
string vbFrame;
vbaProjectVba.AddStream(mod.StreamName);
vbaProjectVba.GetStream(mod.StreamName).SetData(CompoundDocumentCompression.CompressPart(
projEncoding.GetBytes(SeparateVbFrame(_so.FileReadAllText(_so.PathCombine(FolderPath, mod.Name + ".frm"), projEncoding), out vbFrame))));
vbaProject.AddStorage(mod.StreamName);
var frmStorage = vbaProject.GetStorage(mod.StreamName);
frmStorage.AddStream("\x03VBFrame");
frmStorage.GetStream("\x03VBFrame").SetData(projEncoding.GetBytes(vbFrame));
var b = _so.FileReadAllBytes(_so.PathCombine(FolderPath, mod.Name + ".frx"));
using (var ms = new MemoryStream(b, 24, b.Length - 24))
{
var frx = new CompoundFile(ms);
CopyCfStreamsExcept(frx.RootStorage, frmStorage, "\x03VBFrame");
}
break;
default:
vbaProjectVba.AddStream(mod.StreamName);
vbaProjectVba.GetStream(mod.StreamName).SetData(CompoundDocumentCompression.CompressPart(
_so.FileReadAllBytes(_so.PathCombine(FolderPath, mod.Name + ModuleProcessing.ExtensionFromType(mod.Type)))));
break;
}
}
using (var projSm = _so.CreateNewFile(_so.PathCombine(FolderPath, "vbaProject.bin")))
{
cf.Save(projSm);
}
if (_so.PathGetFileName(filePath).Equals("vbaProject.bin", StringComparison.InvariantCultureIgnoreCase))
{
_so.FileCopy(_so.PathCombine(FolderPath, "vbaProject.bin"), filePath, true);
}
else
{
Stream fs = null;
try
{
fs = _so.OpenFileForWrite(filePath);
var sig = new byte[4];
fs.Seek(0, SeekOrigin.Begin);
fs.Read(sig, 0, 4);
fs.Seek(0, SeekOrigin.Begin);
if (sig.SequenceEqual(new byte[] { 0x50, 0x4b, 0x03, 0x04 }))
{
var zipFile = new ZipFile(fs);
var zipEntry = zipFile.Cast<ZipEntry>().FirstOrDefault(e => e.Name.EndsWith("vbaProject.bin", StringComparison.InvariantCultureIgnoreCase));
if (zipEntry == null)
{
throw new ApplicationException(VBASyncResources.ErrorCannotFindVbaProject);
}
zipFile.BeginUpdate();
using (var st = _so.OpenFileForRead(_so.PathCombine(FolderPath, "vbaProject.bin")))
{
zipFile.Add(new StreamStaticDataSource(st), zipEntry.Name);
zipFile.CommitUpdate();
}
zipFile.Close();
}
else
{
var destCf = new CompoundFile(fs, CFSUpdateMode.Update, CFSConfiguration.Default);
var destVbaProject = destCf.GetAllNamedEntries("_VBA_PROJECT_CUR").FirstOrDefault() as CFStorage
?? destCf.GetAllNamedEntries("OutlookVbaData").FirstOrDefault() as CFStorage
?? destCf.GetAllNamedEntries("Macros").FirstOrDefault() as CFStorage;
var ls = new List<string>();
destVbaProject.VisitEntries(i => ls.Add(i.Name), true);
while (ls.Count > 0)
{
DeleteStorageChildren(destVbaProject);
ls.Clear();
destVbaProject.VisitEntries(i => ls.Add(i.Name), true);
}
CopyCfStreamsExcept(cf.RootStorage, destVbaProject, null);
destCf.Commit();
destCf.Close();
}
}
finally
{
fs?.Dispose();
}
}
}
private static void CopyCfStreamsExcept(CFStorage src, CFStorage dest, string excludeName)
{
src.VisitEntries(i =>
{
if (i.Name?.Equals(excludeName, StringComparison.InvariantCultureIgnoreCase) ?? false)
{
return;
}
if (i.IsStorage)
{
dest.AddStorage(i.Name);
CopyCfStreamsExcept((CFStorage)i, dest.GetStorage(i.Name), null);
}
else
{
dest.AddStream(i.Name);
dest.GetStream(i.Name).SetData(((CFStream)i).GetData());
}
}, false);
}
private static byte[] DecompressStream(CFStream sm, uint offset = 0)
{
try
{
var cd = new byte[sm.Size - offset];
sm.Read(cd, offset, cd.Length);
return CompoundDocumentCompression.DecompressPart(cd);
}
catch (Exception ex)
{
throw new ApplicationException(string.Format(VBASyncResources.ErrorDecompressingStream, sm.Name, ex.Message), ex);
}
}
private static void DeleteBlankLinesFromEnd(IList<string> ls)
{
for (var i = ls.Count - 1; i >= 0; i--)
{
if (string.IsNullOrEmpty(ls[i]))
{
ls.RemoveAt(i);
}
else
{
return;
}
}
}
private static void DeleteStorageChildren(CFStorage target)
{
target.VisitEntries(i =>
{
if (i.IsStorage)
{
DeleteStorageChildren((CFStorage)i);
}
target.Delete(i.Name);
}, false);
}
private static string EnsureCrLfEndings(string text)
{
return string.Join("\r\n", text?.Split('\n').Select(s => s.Trim('\r')) ?? new string[0]);
}
private static string GetAttribute(string moduleText, string attribName)
{
using (var sr = new StringReader(moduleText))
{
string line;
while ((line = sr.ReadLine()?.TrimEnd('\r')) != null)
{
var start = line.TrimStart();
if (start.Length < $"Attribute {attribName} ".Length)
{
continue;
}
start = start.Substring(0, $"Attribute {attribName} ".Length);
if (start.Equals($"Attribute {attribName} ", StringComparison.InvariantCultureIgnoreCase)
|| start.Equals($"Attribute {attribName}=", StringComparison.InvariantCultureIgnoreCase))
{
var split = line.Split(new[] { '=' }, 2);
if (split.Length == 2)
{
return split[1].Trim();
}
}
}
}
return null;
}
private static uint? GetAttributeUInt(string moduleText, string attribName)
{
if (uint.TryParse(GetAttribute(moduleText, attribName), out var i))
{
return i;
}
return null;
}
private static string GetTemporaryDirectoryPath(ISystemOperations so)
=> $"{so.PathGetTempPath()}VBASync-{Guid.NewGuid().ToString()}";
private static string GetPrepend(Module mod, CFStorage vbaProject, Encoding enc)
{
switch (mod.Type)
{
case ModuleType.Class:
case ModuleType.StaticClass:
return "VERSION 1.0 CLASS\r\nBEGIN\r\n MultiUse = -1 'True\r\nEND\r\n";
case ModuleType.Form:
var vbFrameLines = enc.GetString(vbaProject.GetStorage(mod.StreamName).GetStream("\x0003VBFrame").GetData())
.Split('\n').Select(s => s.TrimEnd('\r')).ToList();
DeleteBlankLinesFromEnd(vbFrameLines);
vbFrameLines.Insert(2, $" OleObjectBlob = \"{mod.Name}.frx\":0000");
return string.Join("\r\n", vbFrameLines) + "\r\n";
case ModuleType.Standard:
return "";
default:
throw new ApplicationException("Unrecognized module type");
}
}
private static string SeparateClassCode(string wholeFile)
{
using (var sr = new StringReader(wholeFile))
{
var firstLine = true;
string line;
while ((line = sr.ReadLine()) != null)
{
if (firstLine && !line.TrimStart().StartsWith("Version", StringComparison.InvariantCultureIgnoreCase))
{
return wholeFile;
}
firstLine = false;
if (line.TrimStart().StartsWith("End", StringComparison.InvariantCultureIgnoreCase))
{
break;
}
}
return sr.ReadToEnd();
}
}
private static string SeparateVbFrame(string wholeFile, out string vbFrame)
{
var frmB = new StringBuilder();
using (var sr = new StringReader(wholeFile))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if (!line.TrimStart().StartsWith("OleObjectBlob", StringComparison.InvariantCultureIgnoreCase))
{
frmB.AppendLine(line);
}
if (line.TrimStart().StartsWith("End", StringComparison.InvariantCultureIgnoreCase))
{
break;
}
}
vbFrame = frmB.ToString();
return sr.ReadToEnd();
}
}
private void Dispose(bool explicitCall)
{
if (_disposed)
{
return;
}
if (explicitCall)
{
}
_so.DirectoryDelete(FolderPath, true);
_disposed = true;
}
internal class StreamStaticDataSource : IStaticDataSource
{
private readonly Stream _source;
internal StreamStaticDataSource(Stream source)
{
_source = source;
}
public Stream GetSource() => _source;
}
private class Module
{
public string DocString { get; set; }
public string FileName => Name + GetExtension();
public uint? HelpContext { get; set; }
public string Name { get; set; }
public uint Offset { get; set; }
public bool Private { get; set; }
public bool ReadOnly { get; set; }
public string StreamName { get; set; }
public ModuleType Type { get; set; }
public uint Version { get; set; }
public IList<byte> GetBytes(Encoding projEncoding)
{
var ret = new List<byte>();
var nameBytes = projEncoding.GetBytes(Name);
ret.AddRange(BitConverter.GetBytes((short)0x0019));
ret.AddRange(BitConverter.GetBytes(nameBytes.Length));
ret.AddRange(nameBytes);
var nameUtfBytes = Encoding.Unicode.GetBytes(Name);
ret.AddRange(BitConverter.GetBytes((short)0x0047));
ret.AddRange(BitConverter.GetBytes(nameUtfBytes.Length));
ret.AddRange(nameUtfBytes);
ret.AddRange(BitConverter.GetBytes((short)0x001A));
ret.AddRange(BitConverter.GetBytes(nameBytes.Length));
ret.AddRange(nameBytes);
ret.AddRange(BitConverter.GetBytes((short)0x0032));
ret.AddRange(BitConverter.GetBytes(nameUtfBytes.Length));
ret.AddRange(nameUtfBytes);
var docStringBytes = projEncoding.GetBytes(DocString ?? "");
var docStringUtfBytes = Encoding.Unicode.GetBytes(DocString ?? "");
ret.AddRange(BitConverter.GetBytes((short)0x001C));
ret.AddRange(BitConverter.GetBytes(docStringBytes.Length));
ret.AddRange(docStringBytes);
ret.AddRange(BitConverter.GetBytes((short)0x0048));
ret.AddRange(BitConverter.GetBytes(docStringUtfBytes.Length));
ret.AddRange(docStringUtfBytes);
ret.AddRange(BitConverter.GetBytes((short)0x0031));
ret.AddRange(BitConverter.GetBytes(4));
ret.AddRange(BitConverter.GetBytes(0));
ret.AddRange(BitConverter.GetBytes((short)0x001E));
ret.AddRange(BitConverter.GetBytes(4));
ret.AddRange(BitConverter.GetBytes(HelpContext ?? 0));
ret.AddRange(BitConverter.GetBytes(Type == ModuleType.Standard ? (short)0x0021 : (short)0x0022));
ret.AddRange(BitConverter.GetBytes(0));
if (ReadOnly)
{
ret.AddRange(BitConverter.GetBytes((short)0x0025));
ret.AddRange(BitConverter.GetBytes(0));
}
if (Private)
{
ret.AddRange(BitConverter.GetBytes((short)0x0028));
ret.AddRange(BitConverter.GetBytes(0));
}
ret.AddRange(BitConverter.GetBytes((short)0x002B));
ret.AddRange(BitConverter.GetBytes(0));
return ret;
}
public override string ToString() => Name;
private string GetExtension()
{
switch (Type)
{
case ModuleType.Standard:
return ".bas";
case ModuleType.StaticClass:
case ModuleType.Class:
return ".cls";
case ModuleType.Form:
return ".frm";
case ModuleType.Ini:
return ".ini";
case ModuleType.Licenses:
return ".bin";
default:
throw new ArgumentOutOfRangeException(nameof(Type));
}
}
}
private abstract class Reference
{
public string Name { get; set; }
public virtual IList<byte> GetBytes(Encoding projEncoding)
{
var ret = new List<byte>();
var nameBytes = projEncoding.GetBytes(Name);
var nameUtfBytes = Encoding.Unicode.GetBytes(Name);
ret.AddRange(BitConverter.GetBytes((short)0x0016));
ret.AddRange(BitConverter.GetBytes(nameBytes.Length));
ret.AddRange(nameBytes);
ret.AddRange(BitConverter.GetBytes((short)0x003E));
ret.AddRange(BitConverter.GetBytes(nameUtfBytes.Length));
ret.AddRange(nameUtfBytes);
return ret;
}
public abstract List<string> GetConfigStrings();
public override string ToString() => Name;
}
private class ReferenceControl : Reference
{
public uint Cookie { get; set; }
public string LibIdExtended { get; set; }
public string LibIdTwiddled { get; set; }
public string NameRecordExtended { get; set; }
public string OriginalLibId { get; set; }
public Guid OriginalTypeLib { get; set; }
public override IList<byte> GetBytes(Encoding projEncoding)
{
var ret = new List<byte>();
ret.AddRange(base.GetBytes(projEncoding));
if (!string.IsNullOrEmpty(OriginalLibId))
{
var originalLibIdBytes = projEncoding.GetBytes(OriginalLibId);
ret.AddRange(BitConverter.GetBytes((short)0x0033));
ret.AddRange(BitConverter.GetBytes(originalLibIdBytes.Length));
ret.AddRange(originalLibIdBytes);
}
var twiddledLibIdBytes = projEncoding.GetBytes(LibIdTwiddled);
ret.AddRange(BitConverter.GetBytes((short)0x002F));
ret.AddRange(BitConverter.GetBytes(twiddledLibIdBytes.Length + 10));
ret.AddRange(BitConverter.GetBytes(twiddledLibIdBytes.Length));
ret.AddRange(twiddledLibIdBytes);
ret.AddRange(BitConverter.GetBytes(0));
ret.AddRange(BitConverter.GetBytes((short)0));
if (!string.IsNullOrEmpty(NameRecordExtended))
{
var nameRecordExtendedBytes = projEncoding.GetBytes(NameRecordExtended);
var nameRecordExtendedUnicodeBytes = Encoding.Unicode.GetBytes(NameRecordExtended);
ret.AddRange(BitConverter.GetBytes((short)0x0016));
ret.AddRange(BitConverter.GetBytes(nameRecordExtendedBytes.Length));
ret.AddRange(nameRecordExtendedBytes);
ret.AddRange(BitConverter.GetBytes((short)0x003e));
ret.AddRange(BitConverter.GetBytes(nameRecordExtendedUnicodeBytes.Length));
ret.AddRange(nameRecordExtendedUnicodeBytes);
}
ret.AddRange(BitConverter.GetBytes((short)0x0030));
var extendedLibIdBytes = projEncoding.GetBytes(LibIdExtended);
ret.AddRange(BitConverter.GetBytes(extendedLibIdBytes.Length + 30));
ret.AddRange(BitConverter.GetBytes(extendedLibIdBytes.Length));
ret.AddRange(extendedLibIdBytes);
ret.AddRange(BitConverter.GetBytes(0));
ret.AddRange(BitConverter.GetBytes((short)0));
ret.AddRange(OriginalTypeLib.ToByteArray());
ret.AddRange(BitConverter.GetBytes(Cookie));
return ret;
}
public override List<string> GetConfigStrings()
{
var ls = new List<string> {
$"LibIdTwiddled={LibIdTwiddled}",
$"LibIdExtended={LibIdExtended}"
};
if (!string.IsNullOrEmpty(NameRecordExtended))
{
ls.Add($"NameRecordExtended={NameRecordExtended}");
}
ls.Add($"OriginalLibId={OriginalLibId}");
if (OriginalTypeLib != Guid.Empty)
{
ls.Add($"OriginalTypeLib={OriginalTypeLib.ToString("B")}");
}
if (Cookie > 0)
{
ls.Add($"Cookie={Cookie.ToString(CultureInfo.InvariantCulture)}");
}
return ls;
}
}
private class ReferenceProject : Reference
{
public string LibIdAbsolute { get; set; }
public string LibIdRelative { get; set; }
public Version Version { get; set; }
public override IList<byte> GetBytes(Encoding projEncoding)
{
var ret = new List<byte>();
ret.AddRange(base.GetBytes(projEncoding));
var libIdAbsoluteBytes = projEncoding.GetBytes(LibIdAbsolute);
var libIdRelativeBytes = projEncoding.GetBytes(LibIdRelative);
ret.AddRange(BitConverter.GetBytes((short)0x000E));
ret.AddRange(BitConverter.GetBytes(libIdAbsoluteBytes.Length + libIdRelativeBytes.Length + 14));
ret.AddRange(BitConverter.GetBytes(libIdAbsoluteBytes.Length));
ret.AddRange(libIdAbsoluteBytes);
ret.AddRange(BitConverter.GetBytes(libIdRelativeBytes.Length));
ret.AddRange(libIdRelativeBytes);
ret.AddRange(BitConverter.GetBytes(Version.Major));
ret.AddRange(BitConverter.GetBytes((short)Version.Minor));
return ret;
}
public override List<string> GetConfigStrings()
{
return new List<string> {
$"LibIdAbsolute={LibIdAbsolute}",
$"LibIdRelative={LibIdRelative}",
$"Version={Version}"
};
}
}
private class ReferenceRegistered : Reference
{
public string LibId { get; set; }
public override IList<byte> GetBytes(Encoding projEncoding)
{
var ret = new List<byte>();
ret.AddRange(base.GetBytes(projEncoding));
var libIdBytes = projEncoding.GetBytes(LibId);
ret.AddRange(BitConverter.GetBytes((short)0x000D));
ret.AddRange(BitConverter.GetBytes(libIdBytes.Length + 10));
ret.AddRange(BitConverter.GetBytes(libIdBytes.Length));
ret.AddRange(libIdBytes);
ret.AddRange(BitConverter.GetBytes(0));
ret.AddRange(BitConverter.GetBytes((short)0));
return ret;
}
public override List<string> GetConfigStrings()
{
return new List<string> {
$"LibId={LibId}"
};
}
}
}
}