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 @@
-
+