| /******************************************************************************* | |
| * You may amend and distribute as you like, but don't remove this header! | |
| * | |
| * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. | |
| * See http://www.codeplex.com/EPPlus for details. | |
| * | |
| * Copyright (C) 2011 Jan Källman | |
| * | |
| * All code and executables are provided "as is" with no warranty either express or implied. | |
| * The author accepts no liability for any damage or loss of business that this product may cause. | |
| * | |
| * If you want to understand this code have a look at the Office VBA File Format Structure Specification (MS-OVBA.PDF) or | |
| * http://msdn.microsoft.com/en-us/library/cc313094(v=office.12).aspx | |
| * | |
| * * Code change notes: | |
| * | |
| * Author Change Date | |
| ******************************************************************************* | |
| * Jan Källman Added 26-MAR-2012 | |
| *******************************************************************************/ | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Globalization; | |
| using System.Linq; | |
| using System.Text; | |
| using System.IO; | |
| using OfficeOpenXml.Utils; | |
| using System.Security.Cryptography.Pkcs; | |
| using System.Security.Cryptography.X509Certificates; | |
| using System.Security.Cryptography; | |
| using System.Text.RegularExpressions; | |
| namespace OfficeOpenXml.VBA | |
| { | |
| /// <summary> | |
| /// Represents the VBA project part of the package | |
| /// </summary> | |
| public class ExcelVbaProject | |
| { | |
| const string schemaRelVba = "http://schemas.microsoft.com/office/2006/relationships/vbaProject"; | |
| internal const string PartUri = @"/xl/vbaProject.bin"; | |
| #region Classes & Enums | |
| /// <summary> | |
| /// Type of system where the VBA project was created. | |
| /// </summary> | |
| public enum eSyskind | |
| { | |
| Win16 = 0, | |
| Win32 = 1, | |
| Macintosh = 2, | |
| Win64 = 3 | |
| } | |
| #endregion | |
| internal ExcelVbaProject(ExcelWorkbook wb) | |
| { | |
| _wb = wb; | |
| _pck = _wb._package.Package; | |
| References = new ExcelVbaReferenceCollection(); | |
| Modules = new ExcelVbaModuleCollection(this); | |
| var rel = _wb.Part.GetRelationshipsByType(schemaRelVba).FirstOrDefault(); | |
| if (rel != null) | |
| { | |
| Uri = UriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri); | |
| Part = _pck.GetPart(Uri); | |
| #if !MONO | |
| GetProject(); | |
| #endif | |
| } | |
| else | |
| { | |
| Lcid = 0; | |
| Part = null; | |
| } | |
| } | |
| internal ExcelWorkbook _wb; | |
| internal Packaging.ZipPackage _pck; | |
| #region Dir Stream Properties | |
| /// <summary> | |
| /// System kind. Default Win32. | |
| /// </summary> | |
| public eSyskind SystemKind { get; set; } | |
| /// <summary> | |
| /// Name of the project | |
| /// </summary> | |
| public string Name { get; set; } | |
| /// <summary> | |
| /// A description of the project | |
| /// </summary> | |
| public string Description { get; set; } | |
| /// <summary> | |
| /// A helpfile | |
| /// </summary> | |
| public string HelpFile1 { get; set; } | |
| /// <summary> | |
| /// Secondary helpfile | |
| /// </summary> | |
| public string HelpFile2 { get; set; } | |
| /// <summary> | |
| /// Context if refering the helpfile | |
| /// </summary> | |
| public int HelpContextID { get; set; } | |
| /// <summary> | |
| /// Conditional compilation constants | |
| /// </summary> | |
| public string Constants { get; set; } | |
| /// <summary> | |
| /// Codepage for encoding. Default is current regional setting. | |
| /// </summary> | |
| public int CodePage { get; internal set; } | |
| internal int LibFlags { get; set; } | |
| internal int MajorVersion { get; set; } | |
| internal int MinorVersion { get; set; } | |
| internal int Lcid { get; set; } | |
| internal int LcidInvoke { get; set; } | |
| internal string ProjectID { get; set; } | |
| internal string ProjectStreamText { get; set; } | |
| /// <summary> | |
| /// Project references | |
| /// </summary> | |
| public ExcelVbaReferenceCollection References { get; set; } | |
| /// <summary> | |
| /// Code Modules (Modules, classes, designer code) | |
| /// </summary> | |
| public ExcelVbaModuleCollection Modules { get; set; } | |
| ExcelVbaSignature _signature = null; | |
| /// <summary> | |
| /// The digital signature | |
| /// </summary> | |
| public ExcelVbaSignature Signature | |
| { | |
| get | |
| { | |
| if (_signature == null) | |
| { | |
| _signature=new ExcelVbaSignature(Part); | |
| } | |
| return _signature; | |
| } | |
| } | |
| ExcelVbaProtection _protection=null; | |
| /// <summary> | |
| /// VBA protection | |
| /// </summary> | |
| public ExcelVbaProtection Protection | |
| { | |
| get | |
| { | |
| if (_protection == null) | |
| { | |
| _protection = new ExcelVbaProtection(this); | |
| } | |
| return _protection; | |
| } | |
| } | |
| #endregion | |
| #if !MONO | |
| #region Read Project | |
| private void GetProject() | |
| { | |
| var stream = Part.GetStream(); | |
| byte[] vba; | |
| vba = new byte[stream.Length]; | |
| stream.Read(vba, 0, (int)stream.Length); | |
| Document = new CompoundDocument(vba); | |
| ReadDirStream(); | |
| ProjectStreamText = Encoding.GetEncoding(CodePage).GetString(Document.Storage.DataStreams["PROJECT"]); | |
| ReadModules(); | |
| ReadProjectProperties(); | |
| } | |
| private void ReadModules() | |
| { | |
| foreach (var modul in Modules) | |
| { | |
| var stream = Document.Storage.SubStorage["VBA"].DataStreams[modul.streamName]; | |
| var byCode = CompoundDocument.DecompressPart(stream, (int)modul.ModuleOffset); | |
| string code = Encoding.GetEncoding(CodePage).GetString(byCode); | |
| int pos=0; | |
| while(pos+9<code.Length && code.Substring(pos,9)=="Attribute") | |
| { | |
| int linePos=code.IndexOf("\r\n",pos); | |
| string[] lineSplit; | |
| if(linePos>0) | |
| { | |
| lineSplit = code.Substring(pos + 9, linePos - pos - 9).Split('='); | |
| } | |
| else | |
| { | |
| lineSplit=code.Substring(pos+9).Split(new char[]{'='},1); | |
| } | |
| if (lineSplit.Length > 1) | |
| { | |
| lineSplit[1] = lineSplit[1].Trim(); | |
| var attr = | |
| new ExcelVbaModuleAttribute() | |
| { | |
| Name = lineSplit[0].Trim(), | |
| DataType = lineSplit[1].StartsWith("\"") ? eAttributeDataType.String : eAttributeDataType.NonString, | |
| Value = lineSplit[1].StartsWith("\"") ? lineSplit[1].Substring(1, lineSplit[1].Length - 2) : lineSplit[1] | |
| }; | |
| modul.Attributes._list.Add(attr); | |
| } | |
| pos = linePos + 2; | |
| } | |
| modul.Code=code.Substring(pos); | |
| } | |
| } | |
| private void ReadProjectProperties() | |
| { | |
| _protection = new ExcelVbaProtection(this); | |
| string prevPackage = ""; | |
| var lines = Regex.Split(ProjectStreamText, "\r\n"); | |
| foreach (string line in lines) | |
| { | |
| if (line.StartsWith("[")) | |
| { | |
| } | |
| else | |
| { | |
| var split = line.Split('='); | |
| if (split.Length > 1 && split[1].Length > 1 && split[1].StartsWith("\"")) //Remove any double qouates | |
| { | |
| split[1] = split[1].Substring(1, split[1].Length - 2); | |
| } | |
| switch (split[0]) | |
| { | |
| case "ID": | |
| ProjectID = split[1]; | |
| break; | |
| case "Document": | |
| string mn = split[1].Substring(0, split[1].IndexOf("/&H")); | |
| Modules[mn].Type = eModuleType.Document; | |
| break; | |
| case "Package": | |
| prevPackage = split[1]; | |
| break; | |
| case "BaseClass": | |
| Modules[split[1]].Type = eModuleType.Designer; | |
| Modules[split[1]].ClassID = prevPackage; | |
| break; | |
| case "Module": | |
| Modules[split[1]].Type = eModuleType.Module; | |
| break; | |
| case "Class": | |
| Modules[split[1]].Type = eModuleType.Class; | |
| break; | |
| case "HelpFile": | |
| case "Name": | |
| case "HelpContextID": | |
| case "Description": | |
| case "VersionCompatible32": | |
| break; | |
| //393222000" | |
| case "CMG": | |
| byte[] cmg = Decrypt(split[1]); | |
| _protection.UserProtected = (cmg[0] & 1) != 0; | |
| _protection.HostProtected = (cmg[0] & 2) != 0; | |
| _protection.VbeProtected = (cmg[0] & 4) != 0; | |
| break; | |
| case "DPB": | |
| byte[] dpb = Decrypt(split[1]); | |
| if (dpb.Length >= 28) | |
| { | |
| byte reserved = dpb[0]; | |
| var flags = new byte[3]; | |
| Array.Copy(dpb, 1, flags, 0, 3); | |
| var keyNoNulls = new byte[4]; | |
| _protection.PasswordKey = new byte[4]; | |
| Array.Copy(dpb, 4, keyNoNulls, 0, 4); | |
| var hashNoNulls = new byte[20]; | |
| _protection.PasswordHash = new byte[20]; | |
| Array.Copy(dpb, 8, hashNoNulls, 0, 20); | |
| //Handle 0x00 bitwise 2.4.4.3 | |
| for (int i = 0; i < 24; i++) | |
| { | |
| int bit = 128 >> (int)((i % 8)); | |
| if (i < 4) | |
| { | |
| if ((int)(flags[0] & bit) == 0) | |
| { | |
| _protection.PasswordKey[i] = 0; | |
| } | |
| else | |
| { | |
| _protection.PasswordKey[i] = keyNoNulls[i]; | |
| } | |
| } | |
| else | |
| { | |
| int flagIndex = (i - i % 8) / 8; | |
| if ((int)(flags[flagIndex] & bit) == 0) | |
| { | |
| _protection.PasswordHash[i - 4] = 0; | |
| } | |
| else | |
| { | |
| _protection.PasswordHash[i - 4] = hashNoNulls[i - 4]; | |
| } | |
| } | |
| } | |
| } | |
| break; | |
| case "GC": | |
| _protection.VisibilityState = Decrypt(split[1])[0] == 0xFF; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// 2.4.3.3 Decryption | |
| /// </summary> | |
| /// <param name="value">Byte hex string</param> | |
| /// <returns>The decrypted value</returns> | |
| private byte[] Decrypt(string value) | |
| { | |
| byte[] enc = GetByte(value); | |
| byte[] dec = new byte[(value.Length - 1)]; | |
| byte seed, version, projKey, ignoredLength; | |
| seed = enc[0]; | |
| dec[0] = (byte)(enc[1] ^ seed); | |
| dec[1] = (byte)(enc[2] ^ seed); | |
| for (int i = 2; i < enc.Length - 1; i++) | |
| { | |
| dec[i] = (byte)(enc[i + 1] ^ (enc[i - 1] + dec[i - 1])); | |
| } | |
| version = dec[0]; | |
| projKey = dec[1]; | |
| ignoredLength = (byte)((seed & 6) / 2); | |
| int datalength = BitConverter.ToInt32(dec, ignoredLength + 2); | |
| var data = new byte[datalength]; | |
| Array.Copy(dec, 6 + ignoredLength, data, 0, datalength); | |
| return data; | |
| } | |
| /// <summary> | |
| /// 2.4.3.2 Encryption | |
| /// </summary> | |
| /// <param name="value"></param> | |
| /// <returns>Byte hex string</returns> | |
| private string Encrypt(byte[] value) | |
| { | |
| byte[] seed = new byte[1]; | |
| var rn = RandomNumberGenerator.Create(); | |
| rn.GetBytes(seed); | |
| BinaryWriter br = new BinaryWriter(new MemoryStream()); | |
| byte[] enc = new byte[value.Length + 10]; | |
| enc[0] = seed[0]; | |
| enc[1] = (byte)(2 ^ seed[0]); | |
| byte projKey = 0; | |
| foreach (var c in ProjectID) | |
| { | |
| projKey += (byte)c; | |
| } | |
| enc[2] = (byte)(projKey ^ seed[0]); | |
| var ignoredLength = (seed[0] & 6) / 2; | |
| for (int i = 0; i < ignoredLength; i++) | |
| { | |
| br.Write(seed[0]); | |
| } | |
| br.Write(value.Length); | |
| br.Write(value); | |
| int pos = 3; | |
| byte pb = projKey; | |
| foreach (var b in ((MemoryStream)br.BaseStream).ToArray()) | |
| { | |
| enc[pos] = (byte)(b ^ (enc[pos - 2] + pb)); | |
| pos++; | |
| pb = b; | |
| } | |
| return GetString(enc, pos - 1); | |
| } | |
| private string GetString(byte[] value, int max) | |
| { | |
| string ret = ""; | |
| for (int i = 0; i <= max; i++) | |
| { | |
| if (value[i] < 16) | |
| { | |
| ret += "0" + value[i].ToString("x"); | |
| } | |
| else | |
| { | |
| ret += value[i].ToString("x"); | |
| } | |
| } | |
| return ret.ToUpper(CultureInfo.InvariantCulture); | |
| } | |
| private byte[] GetByte(string value) | |
| { | |
| byte[] ret = new byte[value.Length / 2]; | |
| for (int i = 0; i < ret.Length; i++) | |
| { | |
| ret[i] = byte.Parse(value.Substring(i * 2, 2), System.Globalization.NumberStyles.AllowHexSpecifier); | |
| } | |
| return ret; | |
| } | |
| private void ReadDirStream() | |
| { | |
| byte[] dir = CompoundDocument.DecompressPart(Document.Storage.SubStorage["VBA"].DataStreams["dir"]); | |
| MemoryStream ms = new MemoryStream(dir); | |
| BinaryReader br = new BinaryReader(ms); | |
| ExcelVbaReference currentRef = null; | |
| string referenceName = ""; | |
| ExcelVBAModule currentModule = null; | |
| bool terminate = false; | |
| while (br.BaseStream.Position < br.BaseStream.Length && terminate == false) | |
| { | |
| ushort id = br.ReadUInt16(); | |
| uint size = br.ReadUInt32(); | |
| switch (id) | |
| { | |
| case 0x01: | |
| SystemKind = (eSyskind)br.ReadUInt32(); | |
| break; | |
| case 0x02: | |
| Lcid = (int)br.ReadUInt32(); | |
| break; | |
| case 0x03: | |
| CodePage = (int)br.ReadUInt16(); | |
| break; | |
| case 0x04: | |
| Name = GetString(br, size); | |
| break; | |
| case 0x05: | |
| Description = GetUnicodeString(br, size); | |
| break; | |
| case 0x06: | |
| HelpFile1 = GetString(br, size); | |
| break; | |
| case 0x3D: | |
| HelpFile2 = GetString(br, size); | |
| break; | |
| case 0x07: | |
| HelpContextID = (int)br.ReadUInt32(); | |
| break; | |
| case 0x08: | |
| LibFlags = (int)br.ReadUInt32(); | |
| break; | |
| case 0x09: | |
| MajorVersion = (int)br.ReadUInt32(); | |
| MinorVersion = (int)br.ReadUInt16(); | |
| break; | |
| case 0x0C: | |
| Constants = GetUnicodeString(br, size); | |
| break; | |
| case 0x0D: | |
| uint sizeLibID = br.ReadUInt32(); | |
| var regRef = new ExcelVbaReference(); | |
| regRef.Name = referenceName; | |
| regRef.ReferenceRecordID = id; | |
| regRef.Libid = GetString(br, sizeLibID); | |
| uint reserved1 = br.ReadUInt32(); | |
| ushort reserved2 = br.ReadUInt16(); | |
| References.Add(regRef); | |
| break; | |
| case 0x0E: | |
| var projRef = new ExcelVbaReferenceProject(); | |
| projRef.ReferenceRecordID = id; | |
| projRef.Name = referenceName; | |
| sizeLibID = br.ReadUInt32(); | |
| projRef.Libid = GetString(br, sizeLibID); | |
| sizeLibID = br.ReadUInt32(); | |
| projRef.LibIdRelative = GetString(br, sizeLibID); | |
| projRef.MajorVersion = br.ReadUInt32(); | |
| projRef.MinorVersion = br.ReadUInt16(); | |
| References.Add(projRef); | |
| break; | |
| case 0x0F: | |
| ushort modualCount = br.ReadUInt16(); | |
| break; | |
| case 0x13: | |
| ushort cookie = br.ReadUInt16(); | |
| break; | |
| case 0x14: | |
| LcidInvoke = (int)br.ReadUInt32(); | |
| break; | |
| case 0x16: | |
| referenceName = GetUnicodeString(br, size); | |
| break; | |
| case 0x19: | |
| currentModule = new ExcelVBAModule(); | |
| currentModule.Name = GetUnicodeString(br, size); | |
| Modules.Add(currentModule); | |
| break; | |
| case 0x1A: | |
| currentModule.streamName = GetUnicodeString(br, size); | |
| break; | |
| case 0x1C: | |
| currentModule.Description = GetUnicodeString(br, size); | |
| break; | |
| case 0x1E: | |
| currentModule.HelpContext = (int)br.ReadUInt32(); | |
| break; | |
| case 0x21: | |
| case 0x22: | |
| break; | |
| case 0x2B: //Modul Terminator | |
| break; | |
| case 0x2C: | |
| currentModule.Cookie = br.ReadUInt16(); | |
| break; | |
| case 0x31: | |
| currentModule.ModuleOffset = br.ReadUInt32(); | |
| break; | |
| case 0x10: | |
| terminate = true; | |
| break; | |
| case 0x30: | |
| var extRef = (ExcelVbaReferenceControl)currentRef; | |
| var sizeExt = br.ReadUInt32(); | |
| extRef.LibIdExternal = GetString(br, sizeExt); | |
| uint reserved4 = br.ReadUInt32(); | |
| ushort reserved5 = br.ReadUInt16(); | |
| extRef.OriginalTypeLib = new Guid(br.ReadBytes(16)); | |
| extRef.Cookie = br.ReadUInt32(); | |
| break; | |
| case 0x33: | |
| currentRef = new ExcelVbaReferenceControl(); | |
| currentRef.ReferenceRecordID = id; | |
| currentRef.Name = referenceName; | |
| currentRef.Libid = GetString(br, size); | |
| References.Add(currentRef); | |
| break; | |
| case 0x2F: | |
| var contrRef = (ExcelVbaReferenceControl)currentRef; | |
| contrRef.ReferenceRecordID = id; | |
| var sizeTwiddled = br.ReadUInt32(); | |
| contrRef.LibIdTwiddled = GetString(br, sizeTwiddled); | |
| var r1 = br.ReadUInt32(); | |
| var r2 = br.ReadUInt16(); | |
| break; | |
| case 0x25: | |
| currentModule.ReadOnly = true; | |
| break; | |
| case 0x28: | |
| currentModule.Private = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| #endregion | |
| #region Save Project | |
| internal void Save() | |
| { | |
| if (Validate()) | |
| { | |
| CompoundDocument doc = new CompoundDocument(); | |
| doc.Storage = new CompoundDocument.StoragePart(); | |
| var store = new CompoundDocument.StoragePart(); | |
| doc.Storage.SubStorage.Add("VBA", store); | |
| store.DataStreams.Add("_VBA_PROJECT", CreateVBAProjectStream()); | |
| store.DataStreams.Add("dir", CreateDirStream()); | |
| foreach (var module in Modules) | |
| { | |
| store.DataStreams.Add(module.Name, CompoundDocument.CompressPart(Encoding.GetEncoding(CodePage).GetBytes(module.Attributes.GetAttributeText() + module.Code))); | |
| } | |
| //Copy streams from the template, if used. | |
| if (Document != null) | |
| { | |
| foreach (var ss in Document.Storage.SubStorage) | |
| { | |
| if (ss.Key != "VBA") | |
| { | |
| doc.Storage.SubStorage.Add(ss.Key, ss.Value); | |
| } | |
| } | |
| foreach (var s in Document.Storage.DataStreams) | |
| { | |
| if (s.Key != "dir" && s.Key != "PROJECT" && s.Key != "PROJECTwm") | |
| { | |
| doc.Storage.DataStreams.Add(s.Key, s.Value); | |
| } | |
| } | |
| } | |
| doc.Storage.DataStreams.Add("PROJECT", CreateProjectStream()); | |
| doc.Storage.DataStreams.Add("PROJECTwm", CreateProjectwmStream()); | |
| if (Part == null) | |
| { | |
| Uri = new Uri(PartUri, UriKind.Relative); | |
| Part = _pck.CreatePart(Uri, ExcelPackage.schemaVBA); | |
| var rel = _wb.Part.CreateRelationship(Uri, Packaging.TargetMode.Internal, schemaRelVba); | |
| } | |
| var vbaBuffer=doc.Save(); | |
| var st = Part.GetStream(FileMode.Create); | |
| st.Write(vbaBuffer, 0, vbaBuffer.Length); | |
| st.Flush(); | |
| //Save the digital signture | |
| Signature.Save(this); | |
| } | |
| } | |
| private bool Validate() | |
| { | |
| Description = Description ?? ""; | |
| HelpFile1 = HelpFile1 ?? ""; | |
| HelpFile2 = HelpFile2 ?? ""; | |
| Constants = Constants ?? ""; | |
| return true; | |
| } | |
| /// <summary> | |
| /// MS-OVBA 2.3.4.1 | |
| /// </summary> | |
| /// <returns></returns> | |
| private byte[] CreateVBAProjectStream() | |
| { | |
| BinaryWriter bw = new BinaryWriter(new MemoryStream()); | |
| bw.Write((ushort)0x61CC); //Reserved1 | |
| bw.Write((ushort)0xFFFF); //Version | |
| bw.Write((byte)0x0); //Reserved3 | |
| bw.Write((ushort)0x0); //Reserved4 | |
| return ((MemoryStream)bw.BaseStream).ToArray(); | |
| } | |
| /// <summary> | |
| /// MS-OVBA 2.3.4.1 | |
| /// </summary> | |
| /// <returns></returns> | |
| private byte[] CreateDirStream() | |
| { | |
| BinaryWriter bw = new BinaryWriter(new MemoryStream()); | |
| /****** PROJECTINFORMATION Record ******/ | |
| bw.Write((ushort)1); //ID | |
| bw.Write((uint)4); //Size | |
| bw.Write((uint)SystemKind); //SysKind | |
| bw.Write((ushort)2); //ID | |
| bw.Write((uint)4); //Size | |
| bw.Write((uint)Lcid); //Lcid | |
| bw.Write((ushort)0x14); //ID | |
| bw.Write((uint)4); //Size | |
| bw.Write((uint)LcidInvoke); //Lcid Invoke | |
| bw.Write((ushort)3); //ID | |
| bw.Write((uint)2); //Size | |
| bw.Write((ushort)CodePage); //Codepage | |
| //ProjectName | |
| bw.Write((ushort)4); //ID | |
| bw.Write((uint)Name.Length); //Size | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(Name)); //Project Name | |
| //Description | |
| bw.Write((ushort)5); //ID | |
| bw.Write((uint)Description.Length); //Size | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(Description)); //Project Name | |
| bw.Write((ushort)0x40); //ID | |
| bw.Write((uint)Description.Length*2); //Size | |
| bw.Write(Encoding.Unicode.GetBytes(Description)); //Project Description | |
| //Helpfiles | |
| bw.Write((ushort)6); //ID | |
| bw.Write((uint)HelpFile1.Length); //Size | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(HelpFile1)); //HelpFile1 | |
| bw.Write((ushort)0x3D); //ID | |
| bw.Write((uint)HelpFile2.Length); //Size | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(HelpFile2)); //HelpFile2 | |
| //Help context id | |
| bw.Write((ushort)7); //ID | |
| bw.Write((uint)4); //Size | |
| bw.Write((uint)HelpContextID); //Help context id | |
| //Libflags | |
| bw.Write((ushort)8); //ID | |
| bw.Write((uint)4); //Size | |
| bw.Write((uint)0); //Help context id | |
| //Vba Version | |
| bw.Write((ushort)9); //ID | |
| bw.Write((uint)4); //Reserved | |
| bw.Write((uint)MajorVersion); //Reserved | |
| bw.Write((ushort)MinorVersion); //Help context id | |
| //Constants | |
| bw.Write((ushort)0x0C); //ID | |
| bw.Write((uint)Constants.Length); //Size | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(Constants)); //Help context id | |
| bw.Write((ushort)0x3C); //ID | |
| bw.Write((uint)Constants.Length/2); //Size | |
| bw.Write(Encoding.Unicode.GetBytes(Constants)); //HelpFile2 | |
| /****** PROJECTREFERENCES Record ******/ | |
| foreach (var reference in References) | |
| { | |
| WriteNameReference(bw, reference); | |
| if (reference.ReferenceRecordID == 0x2F) | |
| { | |
| WriteControlReference(bw, reference); | |
| } | |
| else if (reference.ReferenceRecordID == 0x33) | |
| { | |
| WriteOrginalReference(bw, reference); | |
| } | |
| else if (reference.ReferenceRecordID == 0x0D) | |
| { | |
| WriteRegisteredReference(bw, reference); | |
| } | |
| else if (reference.ReferenceRecordID == 0x0E) | |
| { | |
| WriteProjectReference(bw, reference); | |
| } | |
| } | |
| bw.Write((ushort)0x0F); | |
| bw.Write((uint)0x02); | |
| bw.Write((ushort)Modules.Count); | |
| bw.Write((ushort)0x13); | |
| bw.Write((uint)0x02); | |
| bw.Write((ushort)0xFFFF); | |
| foreach (var module in Modules) | |
| { | |
| WriteModuleRecord(bw, module); | |
| } | |
| bw.Write((ushort)0x10); //Terminator | |
| bw.Write((uint)0); | |
| return CompoundDocument.CompressPart(((MemoryStream)bw.BaseStream).ToArray()); | |
| } | |
| private void WriteModuleRecord(BinaryWriter bw, ExcelVBAModule module) | |
| { | |
| bw.Write((ushort)0x19); | |
| bw.Write((uint)module.Name.Length); | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(module.Name)); //Name | |
| bw.Write((ushort)0x47); | |
| bw.Write((uint)module.Name.Length*2); | |
| bw.Write(Encoding.Unicode.GetBytes(module.Name)); //Name | |
| bw.Write((ushort)0x1A); | |
| bw.Write((uint)module.Name.Length); | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(module.Name)); //Stream Name | |
| bw.Write((ushort)0x32); | |
| bw.Write((uint)module.Name.Length*2); | |
| bw.Write(Encoding.Unicode.GetBytes(module.Name)); //Stream Name | |
| module.Description = module.Description ?? ""; | |
| bw.Write((ushort)0x1C); | |
| bw.Write((uint)module.Description.Length); | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(module.Description)); //Description | |
| bw.Write((ushort)0x48); | |
| bw.Write((uint)module.Description.Length*2); | |
| bw.Write(Encoding.Unicode.GetBytes(module.Description)); //Description | |
| bw.Write((ushort)0x31); | |
| bw.Write((uint)4); | |
| bw.Write((uint)0); //Module Stream Offset (No PerformanceCache) | |
| bw.Write((ushort)0x1E); | |
| bw.Write((uint)4); | |
| bw.Write((uint)module.HelpContext); //Help context ID | |
| bw.Write((ushort)0x2C); | |
| bw.Write((uint)2); | |
| bw.Write((ushort)0xFFFF); //Help context ID | |
| bw.Write((ushort)(module.Type == eModuleType.Module ? 0x21 : 0x22)); | |
| bw.Write((uint)0); | |
| if (module.ReadOnly) | |
| { | |
| bw.Write((ushort)0x25); | |
| bw.Write((uint)0); //Readonly | |
| } | |
| if (module.Private) | |
| { | |
| bw.Write((ushort)0x28); | |
| bw.Write((uint)0); //Private | |
| } | |
| bw.Write((ushort)0x2B); //Terminator | |
| bw.Write((uint)0); | |
| } | |
| private void WriteNameReference(BinaryWriter bw, ExcelVbaReference reference) | |
| { | |
| //Name record | |
| bw.Write((ushort)0x16); //ID | |
| bw.Write((uint)reference.Name.Length); //Size | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(reference.Name)); //HelpFile1 | |
| bw.Write((ushort)0x3E); //ID | |
| bw.Write((uint)reference.Name.Length * 2); //Size | |
| bw.Write(Encoding.Unicode.GetBytes(reference.Name)); //HelpFile2 | |
| } | |
| private void WriteControlReference(BinaryWriter bw, ExcelVbaReference reference) | |
| { | |
| WriteOrginalReference(bw, reference); | |
| bw.Write((ushort)0x2F); | |
| var controlRef=(ExcelVbaReferenceControl)reference; | |
| bw.Write((uint)(4 + controlRef.LibIdTwiddled.Length + 4 + 2)); // Size of SizeOfLibidTwiddled, LibidTwiddled, Reserved1, and Reserved2. | |
| bw.Write((uint)controlRef.LibIdTwiddled.Length); //Size | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(controlRef.LibIdTwiddled)); //LibID | |
| bw.Write((uint)0); //Reserved1 | |
| bw.Write((ushort)0); //Reserved2 | |
| WriteNameReference(bw, reference); //Name record again | |
| bw.Write((ushort)0x30); //Reserved3 | |
| bw.Write((uint)(4 + controlRef.LibIdExternal.Length + 4 + 2 + 16 + 4)); //Size of SizeOfLibidExtended, LibidExtended, Reserved4, Reserved5, OriginalTypeLib, and Cookie | |
| bw.Write((uint)controlRef.LibIdExternal.Length); //Size | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(controlRef.LibIdExternal)); //LibID | |
| bw.Write((uint)0); //Reserved4 | |
| bw.Write((ushort)0); //Reserved5 | |
| bw.Write(controlRef.OriginalTypeLib.ToByteArray()); | |
| bw.Write((uint)controlRef.Cookie); //Cookie | |
| } | |
| private void WriteOrginalReference(BinaryWriter bw, ExcelVbaReference reference) | |
| { | |
| bw.Write((ushort)0x33); | |
| bw.Write((uint)reference.Libid.Length); | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(reference.Libid)); //LibID | |
| } | |
| private void WriteProjectReference(BinaryWriter bw, ExcelVbaReference reference) | |
| { | |
| bw.Write((ushort)0x0E); | |
| var projRef = (ExcelVbaReferenceProject)reference; | |
| bw.Write((uint)(4 + projRef.Libid.Length + 4 + projRef.LibIdRelative.Length+4+2)); | |
| bw.Write((uint)projRef.Libid.Length); | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(projRef.Libid)); //LibAbsolute | |
| bw.Write((uint)projRef.LibIdRelative.Length); | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(projRef.LibIdRelative)); //LibIdRelative | |
| bw.Write(projRef.MajorVersion); | |
| bw.Write(projRef.MinorVersion); | |
| } | |
| private void WriteRegisteredReference(BinaryWriter bw, ExcelVbaReference reference) | |
| { | |
| bw.Write((ushort)0x0D); | |
| bw.Write((uint)(4+reference.Libid.Length+4+2)); | |
| bw.Write((uint)reference.Libid.Length); | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(reference.Libid)); //LibID | |
| bw.Write((uint)0); //Reserved1 | |
| bw.Write((ushort)0); //Reserved2 | |
| } | |
| private byte[] CreateProjectwmStream() | |
| { | |
| BinaryWriter bw = new BinaryWriter(new MemoryStream()); | |
| foreach (var module in Modules) | |
| { | |
| bw.Write(Encoding.GetEncoding(CodePage).GetBytes(module.Name)); //Name | |
| bw.Write((byte)0); //Null | |
| bw.Write(Encoding.Unicode.GetBytes(module.Name)); //Name | |
| bw.Write((ushort)0); //Null | |
| } | |
| bw.Write((ushort)0); //Null | |
| return CompoundDocument.CompressPart(((MemoryStream)bw.BaseStream).ToArray()); | |
| } | |
| private byte[] CreateProjectStream() | |
| { | |
| StringBuilder sb = new StringBuilder(); | |
| sb.AppendFormat("ID=\"{0}\"\r\n", ProjectID); | |
| foreach(var module in Modules) | |
| { | |
| if (module.Type == eModuleType.Document) | |
| { | |
| sb.AppendFormat("Document={0}/&H00000000\r\n", module.Name); | |
| } | |
| else if (module.Type == eModuleType.Module) | |
| { | |
| sb.AppendFormat("Module={0}\r\n", module.Name); | |
| } | |
| else if (module.Type == eModuleType.Class) | |
| { | |
| sb.AppendFormat("Class={0}\r\n", module.Name); | |
| } | |
| else | |
| { | |
| //Designer | |
| sb.AppendFormat("Package={0}\r\n", module.ClassID); | |
| sb.AppendFormat("BaseClass={0}\r\n", module.Name); | |
| } | |
| } | |
| if (HelpFile1 != "") | |
| { | |
| sb.AppendFormat("HelpFile={0}\r\n", HelpFile1); | |
| } | |
| sb.AppendFormat("Name=\"{0}\"\r\n", Name); | |
| sb.AppendFormat("HelpContextID={0}\r\n", HelpContextID); | |
| if (!string.IsNullOrEmpty(Description)) | |
| { | |
| sb.AppendFormat("Description=\"{0}\"\r\n", Description); | |
| } | |
| sb.AppendFormat("VersionCompatible32=\"393222000\"\r\n"); | |
| sb.AppendFormat("CMG=\"{0}\"\r\n", WriteProtectionStat()); | |
| sb.AppendFormat("DPB=\"{0}\"\r\n", WritePassword()); | |
| sb.AppendFormat("GC=\"{0}\"\r\n\r\n", WriteVisibilityState()); | |
| sb.Append("[Host Extender Info]\r\n"); | |
| sb.Append("&H00000001={3832D640-CF90-11CF-8E43-00A0C911005A};VBE;&H00000000\r\n"); | |
| sb.Append("\r\n"); | |
| sb.Append("[Workspace]\r\n"); | |
| foreach(var module in Modules) | |
| { | |
| sb.AppendFormat("{0}=0, 0, 0, 0, C \r\n",module.Name); | |
| } | |
| string s = sb.ToString(); | |
| return Encoding.GetEncoding(CodePage).GetBytes(s); | |
| } | |
| private string WriteProtectionStat() | |
| { | |
| int stat=(_protection.UserProtected ? 1:0) | | |
| (_protection.HostProtected ? 2:0) | | |
| (_protection.VbeProtected ? 4:0); | |
| return Encrypt(BitConverter.GetBytes(stat)); | |
| } | |
| private string WritePassword() | |
| { | |
| byte[] nullBits=new byte[3]; | |
| byte[] nullKey = new byte[4]; | |
| byte[] nullHash = new byte[20]; | |
| if (Protection.PasswordKey == null) | |
| { | |
| return Encrypt(new byte[] { 0 }); | |
| } | |
| else | |
| { | |
| Array.Copy(Protection.PasswordKey, nullKey, 4); | |
| Array.Copy(Protection.PasswordHash, nullHash, 20); | |
| //Set Null bits | |
| for (int i = 0; i < 24; i++) | |
| { | |
| byte bit = (byte)(128 >> (int)((i % 8))); | |
| if (i < 4) | |
| { | |
| if (nullKey[i] == 0) | |
| { | |
| nullKey[i] = 1; | |
| } | |
| else | |
| { | |
| nullBits[0] |= bit; | |
| } | |
| } | |
| else | |
| { | |
| if (nullHash[i - 4] == 0) | |
| { | |
| nullHash[i - 4] = 1; | |
| } | |
| else | |
| { | |
| int byteIndex = (i - i % 8) / 8; | |
| nullBits[byteIndex] |= bit; | |
| } | |
| } | |
| } | |
| //Write the Password Hash Data Structure (2.4.4.1) | |
| BinaryWriter bw = new BinaryWriter(new MemoryStream()); | |
| bw.Write((byte)0xFF); | |
| bw.Write(nullBits); | |
| bw.Write(nullKey); | |
| bw.Write(nullHash); | |
| bw.Write((byte)0); | |
| return Encrypt(((MemoryStream)bw.BaseStream).ToArray()); | |
| } | |
| } | |
| private string WriteVisibilityState() | |
| { | |
| return Encrypt(new byte[] { (byte)(Protection.VisibilityState ? 0xFF : 0) }); | |
| } | |
| #endregion | |
| private string GetString(BinaryReader br, uint size) | |
| { | |
| return GetString(br, size, System.Text.Encoding.GetEncoding(CodePage)); | |
| } | |
| private string GetString(BinaryReader br, uint size, Encoding enc) | |
| { | |
| if (size > 0) | |
| { | |
| byte[] byteTemp = new byte[size]; | |
| byteTemp = br.ReadBytes((int)size); | |
| return enc.GetString(byteTemp); | |
| } | |
| else | |
| { | |
| return ""; | |
| } | |
| } | |
| private string GetUnicodeString(BinaryReader br, uint size) | |
| { | |
| string s = GetString(br, size); | |
| int reserved = br.ReadUInt16(); | |
| uint sizeUC = br.ReadUInt32(); | |
| string sUC = GetString(br, sizeUC, System.Text.Encoding.Unicode); | |
| return sUC.Length == 0 ? s : sUC; | |
| } | |
| internal CompoundDocument Document { get; set; } | |
| #endif | |
| internal Packaging.ZipPackagePart Part { get; set; } | |
| internal Uri Uri { get; private set; } | |
| #if !MONO | |
| /// <summary> | |
| /// Create a new VBA Project | |
| /// </summary> | |
| internal void Create() | |
| { | |
| if(Lcid>0) | |
| { | |
| throw (new InvalidOperationException("Package already contains a VBAProject")); | |
| } | |
| ProjectID = "{5DD90D76-4904-47A2-AF0D-D69B4673604E}"; | |
| Name = "VBAProject"; | |
| SystemKind = eSyskind.Win32; //Default | |
| Lcid = 1033; //English - United States | |
| LcidInvoke = 1033; //English - United States | |
| CodePage = Encoding.Default.CodePage; | |
| MajorVersion = 1361024421; | |
| MinorVersion = 6; | |
| HelpContextID = 0; | |
| Modules.Add(new ExcelVBAModule(_wb.CodeNameChange) { Name = "ThisWorkbook", Code = "", Attributes=GetDocumentAttributes("ThisWorkbook", "0{00020819-0000-0000-C000-000000000046}"), Type = eModuleType.Document, HelpContext = 0 }); | |
| foreach (var sheet in _wb.Worksheets) | |
| { | |
| var name = GetModuleNameFromWorksheet(sheet); | |
| if (!Modules.Exists(name)) | |
| { | |
| Modules.Add(new ExcelVBAModule(sheet.CodeNameChange) { Name = name, Code = "", Attributes = GetDocumentAttributes(sheet.Name, "0{00020820-0000-0000-C000-000000000046}"), Type = eModuleType.Document, HelpContext = 0 }); | |
| } | |
| } | |
| _protection = new ExcelVbaProtection(this) { UserProtected = false, HostProtected = false, VbeProtected = false, VisibilityState = true }; | |
| } | |
| internal string GetModuleNameFromWorksheet(ExcelWorksheet sheet) | |
| { | |
| var name = sheet.Name; | |
| if (name.Any(c => c > 255) || this.Modules[name] != null) | |
| { | |
| int i = sheet.PositionID; | |
| name = "Sheet" + i.ToString(); | |
| while (this.Modules[name] != null) | |
| { | |
| name = "Sheet" + (++i).ToString(); ; | |
| } | |
| } | |
| return name; | |
| } | |
| internal ExcelVbaModuleAttributesCollection GetDocumentAttributes(string name, string clsid) | |
| { | |
| var attr = new ExcelVbaModuleAttributesCollection(); | |
| attr._list.Add(new ExcelVbaModuleAttribute() { Name = "VB_Name", Value = name, DataType = eAttributeDataType.String }); | |
| attr._list.Add(new ExcelVbaModuleAttribute() { Name = "VB_Base", Value = clsid, DataType = eAttributeDataType.String }); | |
| attr._list.Add(new ExcelVbaModuleAttribute() { Name = "VB_GlobalNameSpace", Value = "False", DataType = eAttributeDataType.NonString }); | |
| attr._list.Add(new ExcelVbaModuleAttribute() { Name = "VB_Creatable", Value = "False", DataType = eAttributeDataType.NonString }); | |
| attr._list.Add(new ExcelVbaModuleAttribute() { Name = "VB_PredeclaredId", Value = "True", DataType = eAttributeDataType.NonString }); | |
| attr._list.Add(new ExcelVbaModuleAttribute() { Name = "VB_Exposed", Value = "False", DataType = eAttributeDataType.NonString }); | |
| attr._list.Add(new ExcelVbaModuleAttribute() { Name = "VB_TemplateDerived", Value = "False", DataType = eAttributeDataType.NonString }); | |
| attr._list.Add(new ExcelVbaModuleAttribute() { Name = "VB_Customizable", Value = "True", DataType = eAttributeDataType.NonString }); | |
| return attr; | |
| } | |
| //internal string GetBlankDocumentModule(string name, string clsid) | |
| //{ | |
| // string ret=string.Format("Attribute VB_Name = \"{0}\"\r\n",name); | |
| // ret += string.Format("Attribute VB_Base = \"{0}\"\r\n", clsid); //Microsoft.Office.Interop.Excel.WorksheetClass | |
| // ret += "Attribute VB_GlobalNameSpace = False\r\n"; | |
| // ret += "Attribute VB_Creatable = False\r\n"; | |
| // ret += "Attribute VB_PredeclaredId = True\r\n"; | |
| // ret += "Attribute VB_Exposed = True\r\n"; | |
| // ret += "Attribute VB_TemplateDerived = False\r\n"; | |
| // ret += "Attribute VB_Customizable = True"; | |
| // return ret; | |
| //} | |
| //internal string GetBlankModule(string name) | |
| //{ | |
| // return string.Format("Attribute VB_Name = \"{0}\"\r\n", name); | |
| //} | |
| //internal string GetBlankClassModule(string name, bool exposed) | |
| //{ | |
| // string ret=string.Format("Attribute VB_Name = \"{0}\"\r\n",name); | |
| // ret += string.Format("Attribute VB_Base = \"{0}\"\r\n", "0{FCFB3D2A-A0FA-1068-A738-08002B3371B5}"); | |
| // ret += "Attribute VB_GlobalNameSpace = False\r\n"; | |
| // ret += "Attribute VB_Creatable = False\r\n"; | |
| // ret += "Attribute VB_PredeclaredId = False\r\n"; | |
| // ret += string.Format("Attribute VB_Exposed = {0}\r\n", exposed ? "True" : "False"); | |
| // ret += "Attribute VB_TemplateDerived = False\r\n"; | |
| // ret += "Attribute VB_Customizable = False\r\n"; | |
| // return ret; | |
| //} | |
| #endif | |
| /// <summary> | |
| /// Remove the project from the package | |
| /// </summary> | |
| public void Remove() | |
| { | |
| if (Part == null) return; | |
| foreach (var rel in Part.GetRelationships()) | |
| { | |
| _pck.DeleteRelationship(rel.Id); | |
| } | |
| if (_pck.PartExists(Uri)) | |
| { | |
| _pck.DeletePart(Uri); | |
| } | |
| Part = null; | |
| Modules.Clear(); | |
| References.Clear(); | |
| Lcid = 0; | |
| LcidInvoke = 0; | |
| CodePage = 0; | |
| MajorVersion = 0; | |
| MinorVersion = 0; | |
| HelpContextID = 0; | |
| } | |
| public override string ToString() | |
| { | |
| return Name; | |
| } | |
| } | |
| } |