From a0828e6142a29885fc726120fe3d999d7dcf1a4d Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sun, 17 May 2026 20:13:56 +0200 Subject: [PATCH 01/10] update dependencies --- PG.Benchmarks/PG.Benchmarks.csproj | 10 +++++++--- PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj | 5 ++++- PG.Commons/PG.Commons/PG.Commons.csproj | 11 +++++++---- .../PG.StarWarsGame.Files.DAT.Test.csproj | 5 ++++- .../PG.StarWarsGame.Files.DAT.csproj | 5 ++++- .../PG.StarWarsGame.Files.MEG.Test.csproj | 5 ++++- .../PG.StarWarsGame.Files.MEG.csproj | 9 ++++++--- .../PG.StarWarsGame.Files.MTD.Test.csproj | 5 ++++- .../PG.StarWarsGame.Files.MTD.csproj | 5 ++++- .../PG.StarWarsGame.Files.Xml.Test.csproj | 3 +++ .../PG.StarWarsGame.Files.Xml.csproj | 3 +++ .../PG.StarWarsGame.Files.Test.csproj | 5 ++++- .../PG.StarWarsGame.Files.Testing.csproj | 3 +++ .../PG.StarWarsGame.Files.csproj | 3 +++ PG.Testing/PG.Testing.csproj | 5 ++++- 15 files changed, 64 insertions(+), 18 deletions(-) diff --git a/PG.Benchmarks/PG.Benchmarks.csproj b/PG.Benchmarks/PG.Benchmarks.csproj index 55ca31a2f..87ebb1378 100644 --- a/PG.Benchmarks/PG.Benchmarks.csproj +++ b/PG.Benchmarks/PG.Benchmarks.csproj @@ -19,10 +19,10 @@ - - + + - + @@ -30,4 +30,8 @@ + + + + diff --git a/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj b/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj index e18063d1d..381822b52 100644 --- a/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj +++ b/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj @@ -14,7 +14,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -36,4 +36,7 @@ + + + diff --git a/PG.Commons/PG.Commons/PG.Commons.csproj b/PG.Commons/PG.Commons/PG.Commons.csproj index 554fe90d1..793b48269 100644 --- a/PG.Commons/PG.Commons/PG.Commons.csproj +++ b/PG.Commons/PG.Commons/PG.Commons.csproj @@ -21,16 +21,19 @@ - - - + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj index 364145737..d12fcac99 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj @@ -36,7 +36,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -54,4 +54,7 @@ + + + diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj index 38048ae4e..e53938d69 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj @@ -30,10 +30,13 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj index 6094fbb22..f31e6d468 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj @@ -22,7 +22,7 @@ - + all @@ -52,4 +52,7 @@ + + + diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj index a72cc2922..97c7588c2 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj @@ -22,14 +22,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -42,4 +42,7 @@ + + + diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj index f073d5297..dc89a5ee5 100644 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj @@ -16,7 +16,7 @@ - + all @@ -39,4 +39,7 @@ + + + diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.csproj b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.csproj index 8329ec9c7..bdf2df9c3 100644 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.csproj +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.csproj @@ -17,7 +17,7 @@ snupkg - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -31,4 +31,7 @@ + + + diff --git a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj index 5beecf928..5774226ba 100644 --- a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj +++ b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj @@ -15,4 +15,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj index 9ff294031..2d74e0718 100644 --- a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj +++ b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj @@ -24,4 +24,7 @@ + + + diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj index 0999433e0..3b1de281a 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj @@ -12,7 +12,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -34,4 +34,7 @@ + + + \ No newline at end of file diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/PG.StarWarsGame.Files.Testing.csproj b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/PG.StarWarsGame.Files.Testing.csproj index 742e0f975..dec70c061 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/PG.StarWarsGame.Files.Testing.csproj +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/PG.StarWarsGame.Files.Testing.csproj @@ -9,4 +9,7 @@ + + + diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PG.StarWarsGame.Files.csproj b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PG.StarWarsGame.Files.csproj index ba555a89e..3a2308dd5 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PG.StarWarsGame.Files.csproj +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PG.StarWarsGame.Files.csproj @@ -33,4 +33,7 @@ + + + \ No newline at end of file diff --git a/PG.Testing/PG.Testing.csproj b/PG.Testing/PG.Testing.csproj index 46fbbb00b..7a3e97b6c 100644 --- a/PG.Testing/PG.Testing.csproj +++ b/PG.Testing/PG.Testing.csproj @@ -8,9 +8,12 @@ - + + + + From 34c3c9ac5b822283e751bf17e6aeabb231a16425 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Mon, 18 May 2026 00:27:43 +0200 Subject: [PATCH 02/10] MEGs can be built from byte arrays --- .../MegDataEntryOriginInfoTest.cs | 63 ++++++++- .../Data/MegDataEntryBuilderInfoTest.cs | 57 ++++++++ .../Builder/Builders/MegBuilderTestBase.cs | 122 ++++++++++++++++++ .../Services/MegDataStreamFactoryTest.cs | 38 ++++++ .../EntryLocations/MegDataEntryOriginInfo.cs | 50 +++++-- .../Data/MegDataEntryBuilderInfo.cs | 40 +++++- .../Services/Builder/IMegBuilder.cs | 17 ++- .../Services/Builder/MegBuilderBase.cs | 17 ++- .../Services/Internal/MegDataStreamFactory.cs | 5 +- 9 files changed, 388 insertions(+), 21 deletions(-) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryOriginInfoTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryOriginInfoTest.cs index b3f2494cb..c6ee14771 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryOriginInfoTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryOriginInfoTest.cs @@ -16,6 +16,7 @@ public void Ctor_InvalidArgs_Throws() { Assert.Throws(() => new MegDataEntryOriginInfo((IFileInfo)null!)); Assert.Throws(() => new MegDataEntryOriginInfo((MegDataEntryLocationReference)null!)); + Assert.Throws(() => new MegDataEntryOriginInfo((byte[])null!)); } [Fact] @@ -26,9 +27,11 @@ public void Ctor_FileInfo() Assert.Same(fi, originInfo.FileInfo); Assert.Null(originInfo.MegFileLocation); + Assert.Null(originInfo.Bytes); Assert.True(originInfo.IsLocalFile); Assert.False(originInfo.IsEntryReference); + Assert.False(originInfo.IsBytes); } [Fact] @@ -44,9 +47,48 @@ public void Ctor_ReferenceLocation() Assert.Equal(location, originInfo.MegFileLocation); Assert.Null(originInfo.FileInfo); + Assert.Null(originInfo.Bytes); Assert.True(originInfo.IsEntryReference); Assert.False(originInfo.IsLocalFile); + Assert.False(originInfo.IsBytes); + } + + [Fact] + public void Ctor_Bytes() + { + var bytes = new byte[] { 1, 2, 3, 4 }; + var originInfo = new MegDataEntryOriginInfo(bytes); + + Assert.NotSame(bytes, originInfo.Bytes); + Assert.Equal(bytes, originInfo.Bytes); + Assert.Null(originInfo.FileInfo); + Assert.Null(originInfo.MegFileLocation); + + Assert.True(originInfo.IsBytes); + Assert.False(originInfo.IsLocalFile); + Assert.False(originInfo.IsEntryReference); + } + + [Fact] + public void Ctor_Bytes_Empty_OK() + { + var bytes = Array.Empty(); + var originInfo = new MegDataEntryOriginInfo(bytes); + + Assert.True(originInfo.IsBytes); + Assert.Empty(originInfo.Bytes!); + } + + [Fact] + public void Ctor_Bytes_DefensiveCopy() + { + var bytes = new byte[] { 1, 2, 3 }; + var originInfo = new MegDataEntryOriginInfo(bytes); + + bytes[0] = 99; + + Assert.Equal([1, 2, 3], originInfo.Bytes); } [Fact] @@ -92,4 +134,23 @@ public void EqualsHashCode() Assert.Equal(originLoc.GetHashCode(), otherOriginLoc.GetHashCode()); Assert.Equal(originPath.GetHashCode(), otherOriginPath.GetHashCode()); } -} \ No newline at end of file + + [Fact] + public void EqualsHashCode_Bytes() + { + var bytes = new byte[] { 1, 2, 3 }; + var origin = new MegDataEntryOriginInfo(bytes); + + // Self-equality. + Assert.Equal(origin, origin); + Assert.Equal(origin, (object)origin); + Assert.False(origin.Equals(null)); + + // Each ctor call clones the buffer, so two origins from the same input are NOT equal + // (they own distinct internal buffers). + var otherOrigin = new MegDataEntryOriginInfo(bytes); + Assert.NotEqual(origin, otherOrigin); + + Assert.NotEqual(origin, new MegDataEntryOriginInfo(FileSystem.FileInfo.New("test.xml"))); + } +} diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs index dd99c3afa..d6f0ad4b1 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs @@ -276,8 +276,65 @@ public void FromEntryReference_OriginIsEntryReference_EntryPathEmpty_ThrowsArgum #endregion + #region Factory FromBytes + + [Theory] + [InlineData("entry.bin", true)] + [InlineData("entry.bin", false)] + public void FromBytes(string entryPath, bool encrypted) + { + var bytes = new byte[] { 1, 2, 3, 4, 5 }; + + var info = MegDataEntryBuilderInfo.FromBytes(bytes, entryPath, encrypted); + + Assert.True(info.OriginInfo.IsBytes); + Assert.NotSame(bytes, info.OriginInfo.Bytes); + Assert.Equal(bytes, info.OriginInfo.Bytes); + Assert.Equal(entryPath, info.EntryPath); + Assert.Equal(5u, info.Size); + Assert.Equal(encrypted, info.Encrypted); + } + + [Fact] + public void FromBytes_NullBytes_Throws() + { + Assert.Throws(() => MegDataEntryBuilderInfo.FromBytes(null!, "entry")); + } + + [Fact] + public void FromBytes_NullOrEmptyEntryPath_Throws() + { + var bytes = new byte[] { 1, 2, 3 }; + Assert.Throws(() => MegDataEntryBuilderInfo.FromBytes(bytes, null!)); + Assert.Throws(() => MegDataEntryBuilderInfo.FromBytes(bytes, "")); + } + + [Fact] + public void FromBytes_Empty_OK() + { + var info = MegDataEntryBuilderInfo.FromBytes(Array.Empty(), "entry.bin"); + + Assert.True(info.OriginInfo.IsBytes); + Assert.Equal(0u, info.Size); + } + + #endregion + #region RefreshSize + [Fact] + public void RefreshSize_FromBytes_Unchanged() + { + var bytes = new byte[] { 1, 2, 3, 4 }; + var info = MegDataEntryBuilderInfo.FromBytes(bytes, "entry.bin"); + + Assert.Equal(4u, info.Size); + + info.RefreshSize(); + + Assert.Equal(4u, info.Size); + } + [Fact] public void RefreshSize_FromMegEntry() { diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs index ea06bbea9..8be825d4f 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using AnakinRaW.CommonUtilities.Extensions; using AnakinRaW.CommonUtilities.Testing.Extensions; @@ -512,6 +513,84 @@ public void AddEntry_AddFileSize_FileTooLarge_Throws() #endregion + #region AddBytes + + [Fact] + public void AddBytes_Throws() + { + var builder = CreateBuilder(); + var bytes = new byte[] { 1, 2, 3 }; + + Assert.Throws(() => builder.AddBytes(null!, "path")); + Assert.Throws(() => builder.AddBytes(bytes, null!)); + Assert.Throws(() => builder.AddBytes(bytes, "")); + } + + [Fact] + public void AddBytes_AddsEntry() + { + var builder = CreateBuilder(); + var bytes = new byte[] { 1, 2, 3, 4, 5 }; + + var result = builder.AddBytes(bytes, "entry.bin"); + + Assert.True(result.Added, $"Actual: {result.Status}"); + Assert.Single(builder.DataEntries); + + var entry = builder.DataEntries.First(); + Assert.True(entry.OriginInfo.IsBytes); + Assert.NotSame(bytes, entry.OriginInfo.Bytes); + Assert.Equal(bytes, entry.OriginInfo.Bytes); + Assert.Equal(5u, entry.Size); + Assert.False(entry.Encrypted); + } + + [Fact] + public void AddBytes_MutationAfterAdd_DoesNotAffectEntry() + { + var builder = CreateBuilder(); + var bytes = new byte[] { 1, 2, 3 }; + + builder.AddBytes(bytes, "entry.bin"); + + bytes[0] = 99; + + Assert.Equal([1, 2, 3], builder.DataEntries.First().OriginInfo.Bytes); + } + + [Fact] + public void AddBytes_Empty_AddsEntry() + { + var builder = CreateBuilder(); + + var result = builder.AddBytes([], "entry.bin"); + + Assert.True(result.Added, $"Actual: {result.Status}"); + Assert.Equal(0u, builder.DataEntries.First().Size); + } + + [Fact] + public void AddBytes_TooLarge_ReturnsEntryFileTooLarge() + { + var builder = new MaxFileSizeMegBuilder(3, ServiceProvider); + + var result = builder.AddBytes([1, 2, 3, 4], "entry.bin"); + + Assert.Equal(MegDataEntryAddStatus.EntryFileTooLarge, result.Status); + Assert.Empty(builder.DataEntries); + } + + [Fact] + public void AddBytes_DisposedBuilder_Throws() + { + var builder = CreateBuilder(); + builder.Dispose(); + + Assert.Throws(() => builder.AddBytes([1, 2, 3], "entry.bin")); + } + + #endregion + #region GetMinRequiredMegFiles [Fact] @@ -554,6 +633,49 @@ public void GetMinRequiredMegFiles_MultipleFiles() #endregion + #region Build_FromBytes + + [Fact] + public void Build_FromBytes_RoundTrip() + { + var contents = new byte[] { 9, 8, 7, 6, 5, 4, 3, 2, 1 }; + + var builder = CreateBuilder(); + var addResult = builder.AddBytes(contents, "bytes-entry.bin"); + Assert.True(addResult.Added, $"Actual: {addResult.Status}"); + + var fileInfo = CreateFileInfo(true, "out.meg"); + builder.Build(fileInfo, true); + + Assert.True(FileSystem.File.Exists("out.meg")); + + var loaded = ServiceProvider.GetRequiredService().Load("out.meg"); + Assert.Single(loaded.Archive); + + var extractor = ServiceProvider.GetRequiredService(); + using var extracted = extractor.GetData(new MegDataEntryLocationReference(loaded, loaded.Archive[0])); + var sink = new MemoryStream(); + extracted.CopyTo(sink); + Assert.Equal(contents, sink.ToArray()); + } + + [Fact] + public void Build_FromBytes_Twice_Works() + { + var contents = new byte[] { 1, 2, 3, 4 }; + + var builder = CreateBuilder(); + builder.AddBytes(contents, "entry.bin"); + + builder.Build(CreateFileInfo(true, "first.meg"), true); + builder.Build(CreateFileInfo(true, "second.meg"), true); + + Assert.True(FileSystem.File.Exists("first.meg")); + Assert.True(FileSystem.File.Exists("second.meg")); + } + + #endregion + #region BuildMany [Fact] diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataStreamFactoryTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataStreamFactoryTest.cs index f6f596466..0ea93984b 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataStreamFactoryTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataStreamFactoryTest.cs @@ -127,6 +127,44 @@ public void GetFileData_LocationReference_EmptyDataFile() Assert.Equal(0, stream.Length); } + [Fact] + public void GetFileData_OriginInfo_Bytes() + { + var bytes = new byte[] { 10, 20, 30, 40, 50 }; + var originInfo = new MegDataEntryOriginInfo(bytes); + + using var resultStream = _streamFactory.GetStream(originInfo); + Assert.Equal(5, resultStream.Length); + + var sink = new MemoryStream(); + resultStream.CopyTo(sink); + Assert.Equal(bytes, sink.ToArray()); + } + + [Fact] + public void GetFileData_OriginInfo_Bytes_Replayable() + { + var bytes = new byte[] { 10, 20, 30, 40, 50 }; + var originInfo = new MegDataEntryOriginInfo(bytes); + + using (var first = _streamFactory.GetStream(originInfo)) + first.CopyTo(new MemoryStream()); + + using var second = _streamFactory.GetStream(originInfo); + var sink = new MemoryStream(); + second.CopyTo(sink); + Assert.Equal(bytes, sink.ToArray()); + } + + [Fact] + public void GetFileData_OriginInfo_Bytes_Empty() + { + var originInfo = new MegDataEntryOriginInfo(Array.Empty()); + + using var resultStream = _streamFactory.GetStream(originInfo); + Assert.Equal(0, resultStream.Length); + } + [Fact] public void GetFileData_LocationReference_File() { diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs index eda45ed08..8f7e63b96 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs @@ -8,7 +8,7 @@ namespace PG.StarWarsGame.Files.MEG.Data.EntryLocations; /// -/// The origin of a MEG data entry which is either packed in a MEG archive or present on the file system. +/// The origin of a MEG data entry which is either packed in a MEG archive, present on the file system, or backed by an in-memory byte buffer. /// public sealed class MegDataEntryOriginInfo : IDataEntryLocation, IEquatable { @@ -36,6 +36,15 @@ public sealed class MegDataEntryOriginInfo : IDataEntryLocation, IEquatable MegFileLocation != null; + /// + /// Gets a value indicating whether the data entry originates from an in-memory byte buffer. + /// + /// + /// If this property returns , the property is guaranteed to be non-. + /// + [MemberNotNullWhen(true, nameof(Bytes))] + public bool IsBytes => Bytes != null; + /// /// Gets the MEG file's data entry. if not present. /// @@ -46,6 +55,15 @@ public sealed class MegDataEntryOriginInfo : IDataEntryLocation, IEquatable public IFileInfo? FileInfo { get; } + /// + /// Gets the in-memory byte buffer that holds this data entry's bytes. if not present. + /// + /// + /// This is a defensive copy of the buffer passed to the constructor. Mutations to the constructor's + /// input after construction do not affect this property. + /// + public byte[]? Bytes { get; } + /// /// Initializes a new instance of the structure to the specified file. /// @@ -66,6 +84,19 @@ public MegDataEntryOriginInfo(MegDataEntryLocationReference locationReference) MegFileLocation = locationReference ?? throw new ArgumentNullException(nameof(locationReference)); } + /// + /// Initializes a new instance of the structure backed by the specified byte buffer. + /// The buffer is copied; subsequent mutations to do not affect this origin. + /// + /// The buffer containing the entry bytes. + /// is . + public MegDataEntryOriginInfo(byte[] bytes) + { + if (bytes == null) + throw new ArgumentNullException(nameof(bytes)); + Bytes = (byte[])bytes.Clone(); + } + /// public bool Equals(MegDataEntryOriginInfo? other) { @@ -73,8 +104,9 @@ public bool Equals(MegDataEntryOriginInfo? other) return false; if (ReferenceEquals(this, other)) return true; - return Equals(MegFileLocation, other.MegFileLocation) && - string.Equals(FileInfo?.FullName, other.FileInfo?.FullName, StringComparison.Ordinal); + return Equals(MegFileLocation, other.MegFileLocation) && + string.Equals(FileInfo?.FullName, other.FileInfo?.FullName, StringComparison.Ordinal) && + ReferenceEquals(Bytes, other.Bytes); } /// @@ -86,14 +118,16 @@ public override bool Equals(object? obj) /// public override int GetHashCode() { - return HashCode.Combine(MegFileLocation, FileInfo?.FullName); + return HashCode.Combine(MegFileLocation, FileInfo?.FullName, Bytes); } /// public override string ToString() { - return IsLocalFile - ? $"Local File: '{FileInfo.FullName}'" - : $"MEG Entry: '{MegFileLocation}'"; + if (IsLocalFile) + return $"Local File: '{FileInfo.FullName}'"; + if (IsEntryReference) + return $"MEG Entry: '{MegFileLocation}'"; + return $"Bytes: {Bytes!.Length} bytes"; } -} \ No newline at end of file +} diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs index 20091b70a..71f1a97f9 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs @@ -119,7 +119,7 @@ public static MegDataEntryBuilderInfo FromEntryReference(MegDataEntryLocationRef /// public static MegDataEntryBuilderInfo FromFile(IFileInfo file, string entryPath, bool encrypt = false) { - if (file == null) + if (file == null) throw new ArgumentNullException(nameof(file)); if (!file.Exists) throw new ArgumentException($"The specified file '{file.FullName}' does not exist.", nameof(file)); @@ -128,6 +128,23 @@ public static MegDataEntryBuilderInfo FromFile(IFileInfo file, string entryPath, new MegDataEntryOriginInfo(file), entryPath, encrypt); } + /// + /// Creates a new instance of the class from an in-memory byte buffer. + /// The buffer is copied; subsequent mutations to do not affect the resulting entry. + /// + /// The buffer containing the entry bytes. + /// The path of the entry within the MEG archive. + /// Sets whether the data shall be encrypted or not. Default is . + /// or is . + /// is empty. + public static MegDataEntryBuilderInfo FromBytes(byte[] bytes, string entryPath, bool encrypt = false) + { + if (bytes == null) + throw new ArgumentNullException(nameof(bytes)); + ThrowHelper.ThrowIfNullOrEmpty(entryPath); + return new MegDataEntryBuilderInfo(new MegDataEntryOriginInfo(bytes), entryPath, encrypt); + } + /// /// Updates the size of the data entry associated with this instance. /// @@ -136,7 +153,13 @@ public static MegDataEntryBuilderInfo FromFile(IFileInfo file, string entryPath, public void RefreshSize() { if (OriginInfo.IsEntryReference) + { Size = OriginInfo.MegFileLocation.DataEntry.Location.Size; + } + else if (OriginInfo.IsBytes) + { + Size = (uint)OriginInfo.Bytes.Length; + } else { var fileInfo = OriginInfo.FileInfo!; @@ -154,17 +177,20 @@ private static string GetEntryPath(MegDataEntryOriginInfo originInfo, string? ov { if (overrideEntryPath is not null) return overrideEntryPath; - return originInfo.IsLocalFile - ? originInfo.FileInfo.FullName - : originInfo.MegFileLocation!.DataEntry.Path; + if (originInfo.IsLocalFile) + return originInfo.FileInfo.FullName; + if (originInfo.IsEntryReference) + return originInfo.MegFileLocation.DataEntry.Path; + throw new ArgumentException( + "A byte-buffer-backed entry requires an explicit entry path.", nameof(overrideEntryPath)); } private static bool GetEncryption(MegDataEntryOriginInfo originInfo, bool? overrideEncrypted) { if (overrideEncrypted is not null) - return overrideEncrypted.Value; - // Fallback for the case, origin is a file system path but overrideEncrypted was forgotten to set explicitly. - if (originInfo.IsLocalFile) + return overrideEncrypted.Value; + // Fallback for the case, origin is a file system path or byte buffer but overrideEncrypted was forgotten to set explicitly. + if (originInfo.IsLocalFile || originInfo.IsBytes) return false; return originInfo.MegFileLocation!.DataEntry.Encrypted; } diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs index dad1ce749..830e01bfa 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs @@ -70,7 +70,7 @@ public interface IMegBuilder : IFileBuilder - /// Adds an existing data entry to the and returns a status information to indicate whether the entry was successfully added. + /// Adds an existing data entry to the and returns a status information to indicate whether the entry was successfully added. /// /// /// The actual data entry's file path might be different to depending whether this normalizes path before adding, or not. @@ -84,6 +84,21 @@ public interface IMegBuilder : IFileBuilder + /// Adds an in-memory byte buffer as a data entry to the . + /// The buffer is copied; subsequent mutations to do not affect the resulting entry. + /// + /// + /// The actual data entry's file path might be different to due to optional normalization and mandatory encoding. + /// + /// The buffer containing the entry bytes. + /// The desired file path of the data entry inside the MEG archive. + /// Indicates whether the data entry shall be encrypted. + /// The result of this operation. + /// or is . + /// is empty. + MegDataEntryAddResult AddBytes(byte[] bytes, string entryPath, bool encrypt = false); + /// /// Removes the first occurrence of a specific object from the . /// diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegBuilderBase.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegBuilderBase.cs index 13d1f2a35..70ff5daf8 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegBuilderBase.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegBuilderBase.cs @@ -108,7 +108,7 @@ public MegDataEntryAddResult AddEntry( bool? overrideEncrypt = null) { ThrowIfDisposed(); - + if (overridePathInMeg is not null && string.IsNullOrWhiteSpace(overridePathInMeg)) throw new ArgumentException("Override path in MEG cannot be empty or whitespace.", nameof(overridePathInMeg)); if (entryReference == null) @@ -119,13 +119,24 @@ public MegDataEntryAddResult AddEntry( if (!entryReference.Exists) return MegDataEntryAddResult.FromEntryNotFound(entryReference); - + return AddBuilderInfo( - new MegDataEntryOriginInfo(entryReference), + new MegDataEntryOriginInfo(entryReference), entryPath, encrypt); } + /// + public MegDataEntryAddResult AddBytes(byte[] bytes, string entryPath, bool encrypt = false) + { + ThrowIfDisposed(); + if (bytes == null) + throw new ArgumentNullException(nameof(bytes)); + ThrowHelper.ThrowIfNullOrEmpty(entryPath); + + return AddBuilderInfo(new MegDataEntryOriginInfo(bytes), entryPath, encrypt); + } + /// public bool Remove(MegDataEntryBuilderInfo info) { diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegDataStreamFactory.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegDataStreamFactory.cs index 4927289df..c0f2decb0 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegDataStreamFactory.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegDataStreamFactory.cs @@ -17,9 +17,12 @@ internal sealed class MegDataStreamFactory(IServiceProvider serviceProvider) { public Stream GetStream(MegDataEntryOriginInfo originInfo) { - if (originInfo == null) + if (originInfo == null) throw new ArgumentNullException(nameof(originInfo)); + if (originInfo.IsBytes) + return new MemoryStream(originInfo.Bytes, writable: false); + if (originInfo.FileInfo is not null) return originInfo.FileInfo.OpenRead(); From 4ee721810522e35177345b623b2e06976780accc Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Tue, 19 May 2026 23:08:49 +0200 Subject: [PATCH 03/10] adress own feedback --- .../Data/MegDataEntryBuilderInfoTest.cs | 2 +- .../Builder/Builders/MegBuilderTestBase.cs | 15 --------------- .../Services/MegDataStreamFactoryTest.cs | 2 +- .../Data/MegDataEntryBuilderInfo.cs | 5 +++-- 4 files changed, 5 insertions(+), 19 deletions(-) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs index d6f0ad4b1..30dcc00a2 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs @@ -312,7 +312,7 @@ public void FromBytes_NullOrEmptyEntryPath_Throws() [Fact] public void FromBytes_Empty_OK() { - var info = MegDataEntryBuilderInfo.FromBytes(Array.Empty(), "entry.bin"); + var info = MegDataEntryBuilderInfo.FromBytes([], "entry.bin"); Assert.True(info.OriginInfo.IsBytes); Assert.Equal(0u, info.Size); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs index 8be825d4f..d7e30b1a7 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs @@ -659,21 +659,6 @@ public void Build_FromBytes_RoundTrip() Assert.Equal(contents, sink.ToArray()); } - [Fact] - public void Build_FromBytes_Twice_Works() - { - var contents = new byte[] { 1, 2, 3, 4 }; - - var builder = CreateBuilder(); - builder.AddBytes(contents, "entry.bin"); - - builder.Build(CreateFileInfo(true, "first.meg"), true); - builder.Build(CreateFileInfo(true, "second.meg"), true); - - Assert.True(FileSystem.File.Exists("first.meg")); - Assert.True(FileSystem.File.Exists("second.meg")); - } - #endregion #region BuildMany diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataStreamFactoryTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataStreamFactoryTest.cs index 0ea93984b..030e8a6cd 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataStreamFactoryTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataStreamFactoryTest.cs @@ -159,7 +159,7 @@ public void GetFileData_OriginInfo_Bytes_Replayable() [Fact] public void GetFileData_OriginInfo_Bytes_Empty() { - var originInfo = new MegDataEntryOriginInfo(Array.Empty()); + var originInfo = new MegDataEntryOriginInfo([]); using var resultStream = _streamFactory.GetStream(originInfo); Assert.Equal(0, resultStream.Length); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs index 71f1a97f9..a30d55a85 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs @@ -5,6 +5,7 @@ using PG.StarWarsGame.Files.MEG.Data.EntryLocations; using PG.StarWarsGame.Files.MEG.Files; using System; +using System.Diagnostics; using System.IO; using System.IO.Abstractions; using AnakinRaW.CommonUtilities; @@ -181,8 +182,8 @@ private static string GetEntryPath(MegDataEntryOriginInfo originInfo, string? ov return originInfo.FileInfo.FullName; if (originInfo.IsEntryReference) return originInfo.MegFileLocation.DataEntry.Path; - throw new ArgumentException( - "A byte-buffer-backed entry requires an explicit entry path.", nameof(overrideEntryPath)); + Debug.Fail("A byte-buffer-backed entry requires an explicit entry path. Callers must validate before invoking the internal constructor."); + throw new InvalidOperationException("A byte-buffer-backed entry requires an explicit entry path."); } private static bool GetEncryption(MegDataEntryOriginInfo originInfo, bool? overrideEncrypted) From c90c6dc934a9c811b7fb9de01b53a22203bcb107 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Tue, 19 May 2026 23:26:43 +0200 Subject: [PATCH 04/10] fix deps --- Directory.Build.props | 2 +- PG.Benchmarks/PG.Benchmarks.csproj | 4 ---- PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj | 3 --- PG.Commons/PG.Commons/PG.Commons.csproj | 3 --- .../PG.StarWarsGame.Files.DAT.Test.csproj | 3 --- .../PG.StarWarsGame.Files.DAT.csproj | 3 --- .../PG.StarWarsGame.Files.MEG.Test.csproj | 3 --- .../PG.StarWarsGame.Files.MEG.csproj | 3 --- .../PG.StarWarsGame.Files.MTD.Test.csproj | 3 --- .../PG.StarWarsGame.Files.MTD.csproj | 3 --- .../PG.StarWarsGame.Files.Xml.Test.csproj | 3 --- .../PG.StarWarsGame.Files.Xml.csproj | 3 --- .../PG.StarWarsGame.Files.Test.csproj | 3 --- .../PG.StarWarsGame.Files.Testing.csproj | 3 --- .../PG.StarWarsGame.Files/PG.StarWarsGame.Files.csproj | 3 --- PG.Testing/PG.Testing.csproj | 3 --- 16 files changed, 1 insertion(+), 47 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index af7e034c8..9a9ba6324 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -35,7 +35,7 @@ 3.9.50 - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/PG.Benchmarks/PG.Benchmarks.csproj b/PG.Benchmarks/PG.Benchmarks.csproj index 87ebb1378..2d0225c52 100644 --- a/PG.Benchmarks/PG.Benchmarks.csproj +++ b/PG.Benchmarks/PG.Benchmarks.csproj @@ -30,8 +30,4 @@ - - - - diff --git a/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj b/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj index 381822b52..b5b8f83a1 100644 --- a/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj +++ b/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj @@ -36,7 +36,4 @@ - - - diff --git a/PG.Commons/PG.Commons/PG.Commons.csproj b/PG.Commons/PG.Commons/PG.Commons.csproj index 793b48269..24b6cd478 100644 --- a/PG.Commons/PG.Commons/PG.Commons.csproj +++ b/PG.Commons/PG.Commons/PG.Commons.csproj @@ -33,7 +33,4 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj index d12fcac99..aa99a0390 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj @@ -54,7 +54,4 @@ - - - diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj index e53938d69..e0cafb007 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj @@ -36,7 +36,4 @@ - - - diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj index f31e6d468..d14167059 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj @@ -52,7 +52,4 @@ - - - diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj index 97c7588c2..917fce0b1 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj @@ -42,7 +42,4 @@ - - - diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj index dc89a5ee5..57051902c 100644 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj @@ -39,7 +39,4 @@ - - - diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.csproj b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.csproj index bdf2df9c3..5b877d339 100644 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.csproj +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.csproj @@ -31,7 +31,4 @@ - - - diff --git a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj index 5774226ba..5beecf928 100644 --- a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj +++ b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj @@ -15,7 +15,4 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - diff --git a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj index 2d74e0718..9ff294031 100644 --- a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj +++ b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj @@ -24,7 +24,4 @@ - - - diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj index 3b1de281a..68ec1899d 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj @@ -34,7 +34,4 @@ - - - \ No newline at end of file diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/PG.StarWarsGame.Files.Testing.csproj b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/PG.StarWarsGame.Files.Testing.csproj index dec70c061..742e0f975 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/PG.StarWarsGame.Files.Testing.csproj +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/PG.StarWarsGame.Files.Testing.csproj @@ -9,7 +9,4 @@ - - - diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PG.StarWarsGame.Files.csproj b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PG.StarWarsGame.Files.csproj index 3a2308dd5..ba555a89e 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PG.StarWarsGame.Files.csproj +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PG.StarWarsGame.Files.csproj @@ -33,7 +33,4 @@ - - - \ No newline at end of file diff --git a/PG.Testing/PG.Testing.csproj b/PG.Testing/PG.Testing.csproj index 7a3e97b6c..b2e520b16 100644 --- a/PG.Testing/PG.Testing.csproj +++ b/PG.Testing/PG.Testing.csproj @@ -13,7 +13,4 @@ - - - From 942c01ae800ca251b1a72609c69d8bf5360d3983 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Tue, 19 May 2026 23:35:57 +0200 Subject: [PATCH 05/10] update coverlet versions to 10.0.1 --- PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj | 4 ++-- .../PG.StarWarsGame.Files.DAT.Test.csproj | 4 ++-- .../PG.StarWarsGame.Files.MEG.Test.csproj | 4 ++-- .../PG.StarWarsGame.Files.MTD.Test.csproj | 4 ++-- .../PG.StarWarsGame.Files.Xml.Test.csproj | 2 +- .../PG.StarWarsGame.Files.Test.csproj | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj b/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj index b5b8f83a1..72c7c2445 100644 --- a/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj +++ b/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj @@ -19,7 +19,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -27,7 +27,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj index aa99a0390..ec3c98a61 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj @@ -37,7 +37,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -45,7 +45,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj index d14167059..5538311f3 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -32,7 +32,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj index 57051902c..f8c69bc94 100644 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/PG.StarWarsGame.Files.MTD.Test.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -30,7 +30,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj index 5beecf928..fec7f5e52 100644 --- a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj +++ b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj @@ -10,7 +10,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj index 68ec1899d..870cd0a1e 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Test/PG.StarWarsGame.Files.Test.csproj @@ -17,11 +17,11 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive From a8918b3e22dd11187889209d6df13c39f2f7c9f7 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 23 May 2026 00:09:28 +0200 Subject: [PATCH 06/10] support ReadOnlySpan Meg creation --- .../MegDataEntryOriginInfoTest.cs | 49 +++++++++++++++ .../Builder/Builders/MegBuilderTestBase.cs | 60 ++++++++++++++++++- .../EntryLocations/MegDataEntryOriginInfo.cs | 16 ++++- .../Services/Builder/IMegBuilder.cs | 15 +++++ .../Services/Builder/MegBuilderBase.cs | 9 +++ 5 files changed, 143 insertions(+), 6 deletions(-) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryOriginInfoTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryOriginInfoTest.cs index c6ee14771..bfb95ade5 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryOriginInfoTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryOriginInfoTest.cs @@ -54,6 +54,8 @@ public void Ctor_ReferenceLocation() Assert.False(originInfo.IsBytes); } + #region CTOR_Byte[] + [Fact] public void Ctor_Bytes() { @@ -91,6 +93,47 @@ public void Ctor_Bytes_DefensiveCopy() Assert.Equal([1, 2, 3], originInfo.Bytes); } + #endregion + + #region CTOR_ReadOnlySpan + + [Fact] + public void Ctor_Bytes_Span() + { + Span bytes = [1, 2, 3, 4]; + var originInfo = new MegDataEntryOriginInfo(bytes); + + Assert.Equal(bytes.ToArray(), originInfo.Bytes); + Assert.Null(originInfo.FileInfo); + Assert.Null(originInfo.MegFileLocation); + + Assert.True(originInfo.IsBytes); + Assert.False(originInfo.IsLocalFile); + Assert.False(originInfo.IsEntryReference); + } + + [Fact] + public void Ctor_Bytes_Span_Empty_OK() + { + var originInfo = new MegDataEntryOriginInfo(ReadOnlySpan.Empty); + + Assert.True(originInfo.IsBytes); + Assert.Empty(originInfo.Bytes!); + } + + [Fact] + public void Ctor_Bytes_Span_DefensiveCopy() + { + Span bytes = [1, 2, 3]; + var originInfo = new MegDataEntryOriginInfo(bytes); + + bytes[0] = 99; + + Assert.Equal([1, 2, 3], originInfo.Bytes); + } + + #endregion + [Fact] public void EqualsHashCode() { @@ -152,5 +195,11 @@ public void EqualsHashCode_Bytes() Assert.NotEqual(origin, otherOrigin); Assert.NotEqual(origin, new MegDataEntryOriginInfo(FileSystem.FileInfo.New("test.xml"))); + + var spanOrigin = new MegDataEntryOriginInfo((ReadOnlySpan)bytes); + Assert.Equal(spanOrigin, spanOrigin); + Assert.Equal(spanOrigin, (object)spanOrigin); + + Assert.NotEqual(origin, otherOrigin); } } diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs index d7e30b1a7..6060f7540 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/MegBuilderTestBase.cs @@ -545,6 +545,24 @@ public void AddBytes_AddsEntry() Assert.False(entry.Encrypted); } + [Fact] + public void AddBytes_Span_AddsEntry() + { + var builder = CreateBuilder(); + Span bytes = [1, 2, 3, 4, 5]; + + var result = builder.AddBytes(bytes, "entry.bin"); + + Assert.True(result.Added, $"Actual: {result.Status}"); + Assert.Single(builder.DataEntries); + + var entry = builder.DataEntries.First(); + Assert.True(entry.OriginInfo.IsBytes); + Assert.Equal(bytes.ToArray(), entry.OriginInfo.Bytes); + Assert.Equal(5u, entry.Size); + Assert.False(entry.Encrypted); + } + [Fact] public void AddBytes_MutationAfterAdd_DoesNotAffectEntry() { @@ -558,12 +576,36 @@ public void AddBytes_MutationAfterAdd_DoesNotAffectEntry() Assert.Equal([1, 2, 3], builder.DataEntries.First().OriginInfo.Bytes); } + [Fact] + public void AddBytes_Span_MutationAfterAdd_DoesNotAffectEntry() + { + var builder = CreateBuilder(); + Span bytes = [1, 2, 3]; + + builder.AddBytes(bytes, "entry.bin"); + + bytes[0] = 99; + + Assert.Equal([1, 2, 3], builder.DataEntries.First().OriginInfo.Bytes); + } + [Fact] public void AddBytes_Empty_AddsEntry() { var builder = CreateBuilder(); - var result = builder.AddBytes([], "entry.bin"); + var result = builder.AddBytes((byte[])[], "entry.bin"); + + Assert.True(result.Added, $"Actual: {result.Status}"); + Assert.Equal(0u, builder.DataEntries.First().Size); + } + + [Fact] + public void AddBytes_Span_Empty_AddsEntry() + { + var builder = CreateBuilder(); + + var result = builder.AddBytes(ReadOnlySpan.Empty, "entry.bin"); Assert.True(result.Added, $"Actual: {result.Status}"); Assert.Equal(0u, builder.DataEntries.First().Size); @@ -574,7 +616,18 @@ public void AddBytes_TooLarge_ReturnsEntryFileTooLarge() { var builder = new MaxFileSizeMegBuilder(3, ServiceProvider); - var result = builder.AddBytes([1, 2, 3, 4], "entry.bin"); + var result = builder.AddBytes((byte[])[1, 2, 3, 4], "entry.bin"); + + Assert.Equal(MegDataEntryAddStatus.EntryFileTooLarge, result.Status); + Assert.Empty(builder.DataEntries); + } + + [Fact] + public void AddBytes_Span_TooLarge_ReturnsEntryFileTooLarge() + { + var builder = new MaxFileSizeMegBuilder(3, ServiceProvider); + + var result = builder.AddBytes((ReadOnlySpan)[1, 2, 3, 4], "entry.bin"); Assert.Equal(MegDataEntryAddStatus.EntryFileTooLarge, result.Status); Assert.Empty(builder.DataEntries); @@ -586,7 +639,8 @@ public void AddBytes_DisposedBuilder_Throws() var builder = CreateBuilder(); builder.Dispose(); - Assert.Throws(() => builder.AddBytes([1, 2, 3], "entry.bin")); + Assert.Throws(() => builder.AddBytes((byte[])[1, 2, 3], "entry.bin")); + Assert.Throws(() => builder.AddBytes((ReadOnlySpan)[1, 2, 3], "entry.bin")); } #endregion diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs index 8f7e63b96..9d0b9475a 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs @@ -65,7 +65,7 @@ public sealed class MegDataEntryOriginInfo : IDataEntryLocation, IEquatable - /// Initializes a new instance of the structure to the specified file. + /// Initializes a new instance of the class to the specified file. /// /// The origin file. /// is . @@ -75,7 +75,7 @@ public MegDataEntryOriginInfo(IFileInfo fileInfo) } /// - /// Initializes a new instance of the structure to the specified MEG file's data entry. + /// Initializes a new instance of the class to the specified MEG file's data entry. /// /// The MEG file's data entry. /// If is . @@ -85,7 +85,17 @@ public MegDataEntryOriginInfo(MegDataEntryLocationReference locationReference) } /// - /// Initializes a new instance of the structure backed by the specified byte buffer. + /// Initializes a new instance of the class from the specified byte span. + /// The span is copied; subsequent mutations to do not affect this origin. + /// + /// The buffer containing the entry bytes. + public MegDataEntryOriginInfo(ReadOnlySpan bytes) + { + Bytes = bytes.ToArray(); + } + + /// + /// Initializes a new instance of the class from the specified byte buffer. /// The buffer is copied; subsequent mutations to do not affect this origin. /// /// The buffer containing the entry bytes. diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs index 830e01bfa..6e50fbff2 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs @@ -84,6 +84,21 @@ public interface IMegBuilder : IFileBuilder + /// Adds an in-memory byte buffer copied from the specified span as a data entry to the . + /// The buffer is copied; subsequent mutations to do not affect the resulting entry. + /// + /// + /// The actual data entry's file path might be different to due to optional normalization and mandatory encoding. + /// + /// The buffer containing the entry bytes. + /// The desired file path of the data entry inside the MEG archive. + /// Indicates whether the data entry shall be encrypted. + /// The result of this operation. + /// is . + /// is empty. + MegDataEntryAddResult AddBytes(ReadOnlySpan bytes, string entryPath, bool encrypt = false); + /// /// Adds an in-memory byte buffer as a data entry to the . /// The buffer is copied; subsequent mutations to do not affect the resulting entry. diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegBuilderBase.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegBuilderBase.cs index 70ff5daf8..b1db398a9 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegBuilderBase.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegBuilderBase.cs @@ -126,6 +126,15 @@ public MegDataEntryAddResult AddEntry( encrypt); } + /// + public MegDataEntryAddResult AddBytes(ReadOnlySpan bytes, string entryPath, bool encrypt = false) + { + ThrowIfDisposed(); + ThrowHelper.ThrowIfNullOrEmpty(entryPath); + + return AddBuilderInfo(new MegDataEntryOriginInfo(bytes), entryPath, encrypt); + } + /// public MegDataEntryAddResult AddBytes(byte[] bytes, string entryPath, bool encrypt = false) { From d5c6b811adb54df584c9a3e80da3ee115099b189 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 23 May 2026 00:17:48 +0200 Subject: [PATCH 07/10] more readonlyspan support --- .../Data/MegDataEntryBuilderInfoTest.cs | 54 ++++++++++++++++++- .../EntryLocations/MegDataEntryOriginInfo.cs | 2 +- .../Data/MegDataEntryBuilderInfo.cs | 15 ++++++ .../Services/Builder/IMegBuilder.cs | 2 +- 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs index 30dcc00a2..ffc125124 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/MegDataEntryBuilderInfoTest.cs @@ -311,6 +311,43 @@ public void FromBytes_NullOrEmptyEntryPath_Throws() [Fact] public void FromBytes_Empty_OK() + { + var info = MegDataEntryBuilderInfo.FromBytes((byte[])[], "entry.bin"); + + Assert.True(info.OriginInfo.IsBytes); + Assert.Equal(0u, info.Size); + } + + #endregion + + #region Factory FromReadOnlySpan + + [Theory] + [InlineData("entry.bin", true)] + [InlineData("entry.bin", false)] + public void FromBytes_Span(string entryPath, bool encrypted) + { + Span bytes = [1, 2, 3, 4, 5]; + + var info = MegDataEntryBuilderInfo.FromBytes(bytes, entryPath, encrypted); + + Assert.True(info.OriginInfo.IsBytes); + Assert.Equal(bytes.ToArray(), info.OriginInfo.Bytes); + Assert.Equal(entryPath, info.EntryPath); + Assert.Equal(5u, info.Size); + Assert.Equal(encrypted, info.Encrypted); + } + + [Fact] + public void FromBytes_Soan_NullOrEmptyEntryPath_Throws() + { + byte[] bytes = [1, 2, 3]; + Assert.Throws(() => MegDataEntryBuilderInfo.FromBytes(bytes.AsSpan(), null!)); + Assert.Throws(() => MegDataEntryBuilderInfo.FromBytes(bytes.AsSpan(), "")); + } + + [Fact] + public void FromBytes_Span_Empty_OK() { var info = MegDataEntryBuilderInfo.FromBytes([], "entry.bin"); @@ -335,6 +372,19 @@ public void RefreshSize_FromBytes_Unchanged() Assert.Equal(4u, info.Size); } + [Fact] + public void RefreshSize_FromBytes_Span_Unchanged() + { + Span bytes = [1, 2, 3, 4]; + var info = MegDataEntryBuilderInfo.FromBytes(bytes, "entry.bin"); + + Assert.Equal(4u, info.Size); + + info.RefreshSize(); + + Assert.Equal(4u, info.Size); + } + [Fact] public void RefreshSize_FromMegEntry() { @@ -381,7 +431,7 @@ public void RefreshSize_FromFile_FileDeleted_ThrowsFileNotFoundException() FileSystem.File.Delete("test.xml"); - Assert.Throws(() => info.RefreshSize()); + Assert.Throws(info.RefreshSize); } [Fact] @@ -396,7 +446,7 @@ public void RefreshSize_FileTooLarge_ThrowsMegEntrySizeException() mockFileInfo.Length = (long)uint.MaxValue + 1; - Assert.Throws(() => info.RefreshSize()); + Assert.Throws(info.RefreshSize); } #endregion diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs index 9d0b9475a..3456b8555 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryOriginInfo.cs @@ -88,7 +88,7 @@ public MegDataEntryOriginInfo(MegDataEntryLocationReference locationReference) /// Initializes a new instance of the class from the specified byte span. /// The span is copied; subsequent mutations to do not affect this origin. /// - /// The buffer containing the entry bytes. + /// The read-only span containing the entry bytes. public MegDataEntryOriginInfo(ReadOnlySpan bytes) { Bytes = bytes.ToArray(); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs index a30d55a85..ca1b8fc3a 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs @@ -129,6 +129,21 @@ public static MegDataEntryBuilderInfo FromFile(IFileInfo file, string entryPath, new MegDataEntryOriginInfo(file), entryPath, encrypt); } + /// + /// Creates a new instance of the class from an read-only span of bytes. + /// The span is copied; subsequent mutations to do not affect the resulting entry. + /// + /// The read-only span containing the entry bytes. + /// The path of the entry within the MEG archive. + /// Sets whether the data shall be encrypted or not. Default is . + /// or is . + /// is empty. + public static MegDataEntryBuilderInfo FromBytes(ReadOnlySpan bytes, string entryPath, bool encrypt = false) + { + ThrowHelper.ThrowIfNullOrEmpty(entryPath); + return new MegDataEntryBuilderInfo(new MegDataEntryOriginInfo(bytes), entryPath, encrypt); + } + /// /// Creates a new instance of the class from an in-memory byte buffer. /// The buffer is copied; subsequent mutations to do not affect the resulting entry. diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs index 6e50fbff2..062d6a6d1 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/IMegBuilder.cs @@ -91,7 +91,7 @@ MegDataEntryAddResult AddEntry(MegDataEntryLocationReference entryReference, str /// /// The actual data entry's file path might be different to due to optional normalization and mandatory encoding. /// - /// The buffer containing the entry bytes. + /// The read-only span containing the entry bytes. /// The desired file path of the data entry inside the MEG archive. /// Indicates whether the data entry shall be encrypted. /// The result of this operation. From 24cc0bbe0aff0c3b7b6aac06907d12a453d024a5 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 23 May 2026 00:18:48 +0200 Subject: [PATCH 08/10] fix doc --- .../PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs index ca1b8fc3a..e0ac2b583 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegDataEntryBuilderInfo.cs @@ -136,7 +136,7 @@ public static MegDataEntryBuilderInfo FromFile(IFileInfo file, string entryPath, /// The read-only span containing the entry bytes. /// The path of the entry within the MEG archive. /// Sets whether the data shall be encrypted or not. Default is . - /// or is . + /// is . /// is empty. public static MegDataEntryBuilderInfo FromBytes(ReadOnlySpan bytes, string entryPath, bool encrypt = false) { From e6a843480ac08182b757f69f741f1c698050ab21 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 23 May 2026 14:11:24 +0200 Subject: [PATCH 09/10] Update version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index ed5df3e05..a55aab6a9 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "3.0", + "version": "3.1", "assemblyVersion": { "precision": "major" }, From 2f17f262abcce48e7d50869c610b555184b45490 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 23 May 2026 15:13:17 +0200 Subject: [PATCH 10/10] update deps --- PG.Commons/PG.Commons/PG.Commons.csproj | 4 ++-- PG.Testing/PG.Testing.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PG.Commons/PG.Commons/PG.Commons.csproj b/PG.Commons/PG.Commons/PG.Commons.csproj index 24b6cd478..4003737fd 100644 --- a/PG.Commons/PG.Commons/PG.Commons.csproj +++ b/PG.Commons/PG.Commons/PG.Commons.csproj @@ -19,8 +19,8 @@ true - - + + diff --git a/PG.Testing/PG.Testing.csproj b/PG.Testing/PG.Testing.csproj index b2e520b16..ef2f0c356 100644 --- a/PG.Testing/PG.Testing.csproj +++ b/PG.Testing/PG.Testing.csproj @@ -7,7 +7,7 @@ false - +