| /******************************************************************************* |
| * 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; |
| } |
| } |
| } |