diff --git a/src/VbaCompiler/Vba/ModuleStream.cs b/src/VbaCompiler/Vba/ModuleStream.cs index f14f021..ee95eb4 100644 --- a/src/VbaCompiler/Vba/ModuleStream.cs +++ b/src/VbaCompiler/Vba/ModuleStream.cs @@ -16,16 +16,16 @@ public ModuleStream(ModuleUnit module) public ModuleUnit Module { get; } - public void WriteTo(CFStorage storage) + public void WriteTo(Storage storage) { var streamName = this.Module.Name; - var stream = storage.AddStream(streamName); + using var stream = storage.CreateStream(streamName); var content = this.Module.ToModuleCode(); var contentBytes = VbaEncodings.Default.GetBytes(content); var compressedBytes = VbaCompression.Compress(contentBytes); - stream.SetData(compressedBytes); + stream.Write(compressedBytes, 0, compressedBytes.Length); } } } diff --git a/src/VbaCompiler/VbaCompiler.cs b/src/VbaCompiler/VbaCompiler.cs index d228653..86fd617 100644 --- a/src/VbaCompiler/VbaCompiler.cs +++ b/src/VbaCompiler/VbaCompiler.cs @@ -64,12 +64,9 @@ public MemoryStream CompileVbaProject() public void CompileVbaProject(Stream stream) { var moduleNames = this.modules.OrderBy(m => m.Type).Select(m => m.Name).ToList(); - - var storage = new CompoundFile(); var projectId = this.ProjectId.ToString("B").ToUpperInvariant(); - // PROJECT stream - var projectStream = storage.RootStorage.AddStream(StreamId.Project); + // Build project record once var project = new ProjectRecord(); project.Id = projectId; project.Name = this.ProjectName; @@ -83,30 +80,43 @@ public void CompileVbaProject(Stream stream) project.ProjectPassword = projectPassword.ToEncryptedString(); project.VisibilityState = visibilityState.ToEncryptedString(); - var projectContent = project.Generate(); - projectStream.SetData(projectContent); + // Create RootStorage directly with the output stream + using var storage = RootStorage.Create(stream, OpenMcdf.Version.V3, StorageModeFlags.LeaveOpen); + + // PROJECT stream + using (var projectStream = storage.CreateStream(StreamId.Project)) + { + var projectContent = project.Generate(); + projectStream.Write(projectContent, 0, projectContent.Length); + } // PROJECTwm stream - var projectWmStream = storage.RootStorage.AddStream(StreamId.ProjectWm); - var projectWm = new ProjectWmRecord(moduleNames); - var projectWmContent = projectWm.Generate(); - projectWmStream.SetData(projectWmContent); + using (var projectWmStream = storage.CreateStream(StreamId.ProjectWm)) + { + var projectWm = new ProjectWmRecord(moduleNames); + var projectWmContent = projectWm.Generate(); + projectWmStream.Write(projectWmContent, 0, projectWmContent.Length); + } // VBA storage - var vbaStorage = storage.RootStorage.AddStorage(StorageId.VBA); + var vbaStorage = storage.CreateStorage(StorageId.VBA); // _VBA_PROJECT stream - var vbaProjectStream = vbaStorage.AddStream(StreamId.VbaProject); - var vbaProject = new VbaProjectStream(); - var vbaProjectContent = vbaProject.Generate(); - vbaProjectStream.SetData(vbaProjectContent); + using (var vbaProjectStream = vbaStorage.CreateStream(StreamId.VbaProject)) + { + var vbaProject = new VbaProjectStream(); + var vbaProjectContent = vbaProject.Generate(); + vbaProjectStream.Write(vbaProjectContent, 0, vbaProjectContent.Length); + } // dir stream - var dirStream = vbaStorage.AddStream(StreamId.Dir); - var dir = new DirStream(); - var dirContent = dir.GetData(project); - var compressed = VbaCompression.Compress(dirContent); - dirStream.SetData(compressed); + using (var dirStream = vbaStorage.CreateStream(StreamId.Dir)) + { + var dir = new DirStream(); + var dirContent = dir.GetData(project); + var compressed = VbaCompression.Compress(dirContent); + dirStream.Write(compressed, 0, compressed.Length); + } // module streams foreach (var module in this.modules) @@ -115,7 +125,7 @@ public void CompileVbaProject(Stream stream) moduleStream.WriteTo(vbaStorage); } - storage.Save(stream); + // Auto-flush on storage dispose } public void CompilePowerPointMacroFile(Stream outputMacroFileStream, Stream vbaProjectStream, PresentationDocumentType documentType, string? customSourcePath = null) diff --git a/src/VbaCompiler/VbaCompiler.csproj b/src/VbaCompiler/VbaCompiler.csproj index f1648f0..b481072 100644 --- a/src/VbaCompiler/VbaCompiler.csproj +++ b/src/VbaCompiler/VbaCompiler.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/VbaCompiler.Tests/Streams/VbaProjectRoundTripTests.cs b/tests/VbaCompiler.Tests/Streams/VbaProjectRoundTripTests.cs index 5564494..1a73876 100644 --- a/tests/VbaCompiler.Tests/Streams/VbaProjectRoundTripTests.cs +++ b/tests/VbaCompiler.Tests/Streams/VbaProjectRoundTripTests.cs @@ -68,38 +68,44 @@ private static VbaCompiler CreateTestCompiler(string projectName) }; } + private static byte[] ReadStreamData(CfbStream stream) + { + var data = new byte[stream.Length]; + stream.ReadExactly(data); + return data; + } + [Test] public void CompileVbaProject_ShouldProduceDecompilableCompoundFile() { - using var compoundFile = new CompoundFile(new MemoryStream(_compiledVbaProject)); - var root = compoundFile.RootStorage; + using var root = RootStorage.Open(new MemoryStream(_compiledVbaProject), StorageModeFlags.LeaveOpen); // Verify PROJECT stream exists and has content // Empty stream data indicates directory metadata wasn't flushed (broken OpenMcdf) - var projectStream = root.GetStream("PROJECT"); - var projectData = projectStream.GetData(); + using var projectStream = root.OpenStream("PROJECT"); + var projectData = ReadStreamData(projectStream); ClassicAssert.Greater(projectData.Length, 0, "PROJECT stream is empty - stream may not have been disposed"); // Verify PROJECTwm stream exists and has content - var projectWmStream = root.GetStream("PROJECTwm"); - var projectWmData = projectWmStream.GetData(); + using var projectWmStream = root.OpenStream("PROJECTwm"); + var projectWmData = ReadStreamData(projectWmStream); ClassicAssert.Greater(projectWmData.Length, 0, "PROJECTwm stream is empty - stream may not have been disposed"); // Verify VBA storage exists - var vbaStorage = root.GetStorage("VBA"); + var vbaStorage = root.OpenStorage("VBA"); ClassicAssert.IsNotNull(vbaStorage, "VBA storage should exist"); // Verify _VBA_PROJECT stream exists and has content - var vbaProjectSubStream = vbaStorage.GetStream("_VBA_PROJECT"); - var vbaProjectData = vbaProjectSubStream.GetData(); + using var vbaProjectSubStream = vbaStorage.OpenStream("_VBA_PROJECT"); + var vbaProjectData = ReadStreamData(vbaProjectSubStream); ClassicAssert.Greater(vbaProjectData.Length, 0, "_VBA_PROJECT stream is empty - stream may not have been disposed"); // Verify dir stream exists, has content, and can be decompressed - var dirStream = vbaStorage.GetStream("dir"); - var dirData = dirStream.GetData(); + using var dirStream = vbaStorage.OpenStream("dir"); + var dirData = ReadStreamData(dirStream); ClassicAssert.Greater(dirData.Length, 0, "dir stream is empty - stream may not have been disposed"); @@ -110,11 +116,12 @@ public void CompileVbaProject_ShouldProduceDecompilableCompoundFile() [Test] public void CompileVbaProject_ShouldProduceReadableModules() { - using var compoundFile = new CompoundFile(new MemoryStream(_compiledVbaProject)); - var vbaStorage = compoundFile.RootStorage.GetStorage("VBA"); + using var root = RootStorage.Open(new MemoryStream(_compiledVbaProject), StorageModeFlags.LeaveOpen); + var vbaStorage = root.OpenStorage("VBA"); // Verify dir stream has content before decompression - var dirStreamData = vbaStorage.GetStream("dir").GetData(); + using var dirStreamObj = vbaStorage.OpenStream("dir"); + var dirStreamData = ReadStreamData(dirStreamObj); ClassicAssert.Greater(dirStreamData.Length, 0, "dir stream is empty - stream may not have been disposed"); @@ -127,8 +134,8 @@ public void CompileVbaProject_ShouldProduceReadableModules() { ClassicAssert.IsNotEmpty(module.Name, "Module name should not be empty"); - var moduleStream = vbaStorage.GetStream(module.Name); - var moduleData = moduleStream.GetData(); + using var moduleStream = vbaStorage.OpenStream(module.Name!); + var moduleData = ReadStreamData(moduleStream); // Check raw stream data is not empty before processing ClassicAssert.Greater(moduleData.Length, 0, @@ -151,11 +158,12 @@ public void CompileVbaProject_ShouldProduceReadableModules() [Test] public void CompileVbaProject_DecompiledCodeShouldMatchOriginalSource() { - using var compoundFile = new CompoundFile(new MemoryStream(_compiledVbaProject)); - var vbaStorage = compoundFile.RootStorage.GetStorage("VBA"); + using var root = RootStorage.Open(new MemoryStream(_compiledVbaProject), StorageModeFlags.LeaveOpen); + var vbaStorage = root.OpenStorage("VBA"); // Verify dir stream has content before decompression - var dirStreamData = vbaStorage.GetStream("dir").GetData(); + using var dirStreamObj = vbaStorage.OpenStream("dir"); + var dirStreamData = ReadStreamData(dirStreamObj); ClassicAssert.Greater(dirStreamData.Length, 0, "dir stream is empty - stream may not have been disposed"); @@ -164,8 +172,8 @@ public void CompileVbaProject_DecompiledCodeShouldMatchOriginalSource() var modules = VbadDecompiler.DirStream.GetModules(dirData).ToList(); var testModule = modules.First(m => m.Name == "TestModule"); - var moduleStream = vbaStorage.GetStream(testModule.Name!); - var moduleData = moduleStream.GetData(); + using var moduleStream = vbaStorage.OpenStream(testModule.Name!); + var moduleData = ReadStreamData(moduleStream); // Check raw stream data is not empty ClassicAssert.Greater(moduleData.Length, 0, @@ -184,11 +192,12 @@ public void CompileVbaProject_DecompiledCodeShouldMatchOriginalSource() [Test] public void CompileVbaProject_WithMultipleModules_AllShouldMatchOriginalSource() { - using var compoundFile = new CompoundFile(new MemoryStream(_compiledMultiModuleVbaProject)); - var vbaStorage = compoundFile.RootStorage.GetStorage("VBA"); + using var root = RootStorage.Open(new MemoryStream(_compiledMultiModuleVbaProject), StorageModeFlags.LeaveOpen); + var vbaStorage = root.OpenStorage("VBA"); // Verify dir stream has content before decompression - var dirStreamData = vbaStorage.GetStream("dir").GetData(); + using var dirStreamObj = vbaStorage.OpenStream("dir"); + var dirStreamData = ReadStreamData(dirStreamObj); ClassicAssert.Greater(dirStreamData.Length, 0, "dir stream is empty - stream may not have been disposed"); @@ -206,8 +215,8 @@ public void CompileVbaProject_WithMultipleModules_AllShouldMatchOriginalSource() foreach (var module in modules) { - var moduleStream = vbaStorage.GetStream(module.Name); - var moduleData = moduleStream.GetData(); + using var moduleStream = vbaStorage.OpenStream(module.Name!); + var moduleData = ReadStreamData(moduleStream); // Check raw stream data is not empty before processing ClassicAssert.Greater(moduleData.Length, 0, diff --git a/tests/VbaCompiler.Tests/VbaCompiler.Tests.csproj b/tests/VbaCompiler.Tests/VbaCompiler.Tests.csproj index 446e095..23a4986 100644 --- a/tests/VbaCompiler.Tests/VbaCompiler.Tests.csproj +++ b/tests/VbaCompiler.Tests/VbaCompiler.Tests.csproj @@ -43,7 +43,7 @@ - + diff --git a/utils/vbad/Program.cs b/utils/vbad/Program.cs index 01fb8be..3a7ef7a 100644 --- a/utils/vbad/Program.cs +++ b/utils/vbad/Program.cs @@ -15,58 +15,62 @@ var source = Path.Combine(sourcePath, "vbaProject.bin"); -var vba = new CompoundFile(source); -var root = vba.RootStorage; +using var root = RootStorage.OpenRead(source); -root.VisitEntries(item => ProcessFile(root, "", item), false); -ProcessDirAndModules(root.GetStorage("VBA")); +ProcessEntriesRecursive(root, ""); +var vbaStorage = root.OpenStorage("VBA"); +ProcessDirAndModules(vbaStorage); Console.WriteLine(); Console.WriteLine($" vbaProject.bin -> {targetPath}"); Console.WriteLine("Project was decompiled."); -void ProcessFile(CFStorage storage, string directory, CFItem item) +void ProcessEntriesRecursive(Storage storage, string directory) { - if (item.IsStream) + foreach (var entry in storage.EnumerateEntries()) { - var name = item.Name; - var target = Path.Combine(targetPath, directory, name + ".bin"); + if (entry.Type == EntryType.Stream) + { + var name = entry.Name; + var target = Path.Combine(targetPath, directory, name + ".bin"); - var stream = storage.GetStream(name); - var data = stream.GetData(); + using var stream = storage.OpenStream(name); + var data = new byte[stream.Length]; + stream.ReadExactly(data); - if (name == "dir") - { - try - { - data = VbaCompression.Decompress(data); - } - catch (Exception ex) + if (name == "dir") { - Console.WriteLine(ex.Message); + try + { + data = VbaCompression.Decompress(data); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } } + + File.WriteAllBytes(target, data); } - - File.WriteAllBytes(target, data); - } - else if (item.IsStorage) - { - var s2 = (CFStorage)item; - var dir = Path.Combine(targetPath, s2.Name); - if (!Directory.Exists(dir)) + else if (entry.Type == EntryType.Storage) { - Directory.CreateDirectory(dir); - } + var dir = Path.Combine(targetPath, entry.Name); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } - s2.VisitEntries(item => ProcessFile(s2, s2.Name, item), false); + var subStorage = storage.OpenStorage(entry.Name); + ProcessEntriesRecursive(subStorage, entry.Name); + } } } - -void ProcessDirAndModules(CFStorage vbaStorage) +void ProcessDirAndModules(Storage vbaStorage) { - var dir = vbaStorage.GetStream("dir"); - var data = dir.GetData(); + using var dirStream = vbaStorage.OpenStream("dir"); + var data = new byte[dirStream.Length]; + dirStream.ReadExactly(data); data = VbaCompression.Decompress(data); var target = Path.Combine(targetPath, "vba", "dir.bin"); @@ -76,18 +80,21 @@ void ProcessDirAndModules(CFStorage vbaStorage) foreach (var module in modules) { var name = module.Name; + if (name == null) continue; + var targetVbBin = Path.Combine(targetPath, "vba", name + ".bin"); var targetVbText = Path.Combine(targetPath, "vba", name + ".vb"); - var stream = vbaStorage.GetStream(name); - Span dataVb = stream.GetData(); - var dataVbBin = dataVb.Slice((int)module.Offset).ToArray(); + using var stream = vbaStorage.OpenStream(name); + var dataVb = new byte[stream.Length]; + stream.ReadExactly(dataVb); + var dataVbBin = dataVb.AsSpan().Slice((int)module.Offset).ToArray(); File.WriteAllBytes(targetVbBin, dataVbBin); - - dataVb = VbaCompression.Decompress(dataVbBin); - var sourceCode = Encoding.GetEncoding(1252).GetString(dataVb); + var decompressed = VbaCompression.Decompress(dataVbBin); + + var sourceCode = Encoding.GetEncoding(1252).GetString(decompressed); File.WriteAllText(targetVbText, sourceCode); } -} \ No newline at end of file +} diff --git a/utils/vbad/vbad.csproj b/utils/vbad/vbad.csproj index 1af4e52..a1c44f1 100644 --- a/utils/vbad/vbad.csproj +++ b/utils/vbad/vbad.csproj @@ -8,7 +8,7 @@ - +