From 42d6b369a2b0c38d7de57013eadb340289433eaa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 01:54:25 +0000 Subject: [PATCH 1/4] Bump the all-deps group with 2 updates Bumps Microsoft.NET.Test.Sdk from 18.5.1 to 18.6.0 Bumps Testably.Abstractions.Testing from 6.4.0 to 6.5.0 --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-deps - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-deps - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-deps - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-deps - dependency-name: Testably.Abstractions.Testing dependency-version: 6.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-deps - dependency-name: Testably.Abstractions.Testing dependency-version: 6.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-deps ... Signed-off-by: dependabot[bot] --- PG.Benchmarks/PG.Benchmarks.csproj | 2 +- PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj | 2 +- .../PG.StarWarsGame.Files.DAT.Test.csproj | 2 +- .../PG.StarWarsGame.Files.MEG.Test.csproj | 2 +- .../PG.StarWarsGame.Files.MTD.Test.csproj | 2 +- PG.Testing/PG.Testing.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/PG.Benchmarks/PG.Benchmarks.csproj b/PG.Benchmarks/PG.Benchmarks.csproj index 2d0225c5..6789584a 100644 --- a/PG.Benchmarks/PG.Benchmarks.csproj +++ b/PG.Benchmarks/PG.Benchmarks.csproj @@ -22,7 +22,7 @@ - + diff --git a/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj b/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj index 72c7c244..d86dcc28 100644 --- a/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj +++ b/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj @@ -12,7 +12,7 @@ - + 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 ec3c98a6..24fe103e 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 @@ -34,7 +34,7 @@ - + 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 5538311f..e061056d 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 @@ -23,7 +23,7 @@ - + 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 f8c69bc9..23975d0d 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 @@ -17,7 +17,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PG.Testing/PG.Testing.csproj b/PG.Testing/PG.Testing.csproj index ef2f0c35..6663a291 100644 --- a/PG.Testing/PG.Testing.csproj +++ b/PG.Testing/PG.Testing.csproj @@ -8,7 +8,7 @@ - + From aeff9c54324e54984e580e44317456a76046963e Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Thu, 11 Jun 2026 14:37:24 +0200 Subject: [PATCH 2/4] update deps --- PG.Benchmarks/PG.Benchmarks.csproj | 8 ++++---- PG.Commons/PG.Commons/PG.Commons.csproj | 8 ++++---- .../PG.StarWarsGame.Files.DAT.csproj | 2 +- .../PG.StarWarsGame.Files.MEG.csproj | 6 +++--- .../PG.StarWarsGame.Files.MTD.csproj | 2 +- PG.Testing/PG.Testing.csproj | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/PG.Benchmarks/PG.Benchmarks.csproj b/PG.Benchmarks/PG.Benchmarks.csproj index 6789584a..e6da6f98 100644 --- a/PG.Benchmarks/PG.Benchmarks.csproj +++ b/PG.Benchmarks/PG.Benchmarks.csproj @@ -1,4 +1,4 @@ - + false net10.0 @@ -19,10 +19,10 @@ - - + + - + diff --git a/PG.Commons/PG.Commons/PG.Commons.csproj b/PG.Commons/PG.Commons/PG.Commons.csproj index 4003737f..77aeba12 100644 --- a/PG.Commons/PG.Commons/PG.Commons.csproj +++ b/PG.Commons/PG.Commons/PG.Commons.csproj @@ -21,13 +21,13 @@ - - - + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive 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 e0cafb00..f6eb7bf2 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,7 +30,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + 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 917fce0b..4f21ca43 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 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 5b877d33..07ba7cdd 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 diff --git a/PG.Testing/PG.Testing.csproj b/PG.Testing/PG.Testing.csproj index 6663a291..093b568b 100644 --- a/PG.Testing/PG.Testing.csproj +++ b/PG.Testing/PG.Testing.csproj @@ -8,7 +8,7 @@ - + From 68d31337616cecdc21b6b512c092fab031efd512 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 27 Jun 2026 13:12:37 +0200 Subject: [PATCH 3/4] Models from streams (#604) --- CLAUDE.md | 58 ++ Directory.Build.props | 2 +- .../PG.Commons.Test/PG.Commons.Test.csproj | 2 +- .../Data/CrcBasedEqualityComparer.cs | 6 +- PG.Commons/PG.Commons/Data/IHasCrc32.cs | 2 +- PG.Commons/PG.Commons/Hashing/Crc32.cs | 13 +- PG.Commons/PG.Commons/Numerics/Vector2Int.cs | 8 +- PG.Commons/PG.Commons/Numerics/Vector3Int.cs | 8 +- PG.Commons/PG.Commons/Numerics/Vector4Int.cs | 8 +- PG.Commons/PG.Commons/PG.Commons.csproj | 4 +- PG.Commons/PG.Commons/Services/ServiceBase.cs | 2 +- .../PG.Commons/Utilities/CrcUtilities.cs | 8 +- .../Utilities/EncodingExtensions.cs | 2 +- .../Utilities/PGFileNameUtilities.cs | 9 +- .../PG.Commons/Utilities/StreamExtensions.cs | 9 +- .../Converter/DatBinaryConverterTest.cs | 9 +- .../Binary/Reader/DatFileReaderTest.cs | 44 +- .../Data/DatModelTest.cs | 7 +- .../Data/SortedDatModelTest.cs | 7 +- .../Data/UnsortedDatModelTest.cs | 7 +- .../PG.StarWarsGame.Files.DAT.Test.csproj | 2 +- .../Services/Builder/DatBuilderBaseTest.cs | 10 +- .../EmpireAtWarCreditsTextBuilderTest.cs | 9 +- .../EmpireAtWarMasterTextBuilderTest.cs | 5 +- .../Services/DatFileServiceTest.cs | 293 -------- .../Services/DatServiceTest.cs | 408 +++++++++++ .../DatModelServiceTest.RemoveDuplicates.cs | 5 +- .../DatModelServiceTest.SortModel.cs | 7 +- .../Binary/Converters/DatBinaryConverter.cs | 3 +- .../Binary/DatFileConstants.cs | 4 +- .../Binary/Reader/DatFileReader.cs | 8 +- .../Binary/Reader/IDatFileReader.cs | 4 +- .../DatServiceContribution.cs | 2 +- .../Data/ConstructingDatModel.cs | 9 +- .../Data/DatLayoutKind.cs | 28 + .../Data/DatModel.cs | 5 +- .../Data/DatStringEntry.cs | 7 +- .../Data/IDatModel.cs | 17 +- .../Data/SortedDatModel.cs | 6 +- .../Data/UnsortedDatModel.cs | 5 +- .../Files/DatFileType.cs | 29 - .../Services/Builder/AddEntryResult.cs | 12 +- .../Builders/EmpireAtWarCreditsTextBuilder.cs | 10 +- .../Builders/EmpireAtWarMasterTextBuilder.cs | 16 +- .../PetroglyphStarWarsGameDatBuilder.cs | 2 +- .../Services/Builder/DatBuilderBase.cs | 14 +- .../Services/Builder/IDatBuilder.cs | 14 +- .../Validation/EmpireAtWarKeyValidator.cs | 4 +- .../Builder/Validation/IDatKeyValidator.cs | 2 +- .../Builder/Validation/NotNullKeyValidator.cs | 2 +- .../Services/IDatFileService.cs | 89 --- .../Services/IDatModelService.cs | 14 +- .../Services/IDatService.cs | 180 +++++ .../Services/Internal/DatFileService.cs | 94 --- .../Services/Internal/DatModelService.cs | 11 +- .../Services/Internal/DatService.cs | 192 +++++ .../Services/MergedKeyResult.cs | 2 +- .../ConstructingMegArchiveBuilderBaseTest.cs | 8 +- .../ConstructingMegArchiveBuilderV1Test.cs | 8 +- .../Binary/MegBinaryServiceFactoryTest.cs | 23 +- .../Version/MegVersionIdentifierTest.cs | 129 ++-- .../Archives/ConstructingMegArchiveTest.cs | 15 +- .../Data/Archives/VirtualMegArchiveTest.cs | 5 +- .../Data/Entries/MegDataEntryReferenceTest.cs | 3 +- ...ataEntryReferenceTest_ReferenceLocation.cs | 3 +- .../MegDataEntryLocationReferenceTest.cs | 13 +- .../MegDataEntryLocationTest.cs | 4 + .../MegDataEntryOriginInfoTest.cs | 67 +- .../Data/MegDataEntryBuilderInfoTest.cs | 24 +- .../Exceptions/EntryNotInMegExceptionTest.cs | 5 +- .../Files/MegEncryptionDataTest.cs | 1 + .../Files/MegFileInformationTest.cs | 31 +- .../Files/MegFileTest.cs | 17 +- .../MegServiceContributionTest.cs | 3 +- .../MegTestConstants.cs | 22 +- .../PG.StarWarsGame.Files.MEG.Test.csproj | 2 +- .../Builders/EmpireAtWarMegBuilderTest.cs | 25 +- .../Builder/Builders/MegBuilderTestBase.cs | 21 +- .../BinaryMegDataEntryValidatorTestBase.cs | 4 +- .../BinaryMegFileInformationValidatorTest.cs | 17 +- ...naryMegFileInformationValidatorTestBase.cs | 20 +- ...ireAtWarMegFileInformationValidatorTest.cs | 17 +- .../Services/MegDataSourceTest.cs | 71 ++ .../Services/MegDataSourceTestSuite.cs | 102 +++ .../Services/MegDataStreamFactoryTest.cs | 189 ----- .../Services/MegFileExtractorTest.cs | 124 ++-- .../Services/MegFileServiceIntegrationTest.cs | 397 ----------- .../Services/MegServiceIntegrationTest.cs | 655 ++++++++++++++++++ .../Services/VirtualMegArchiveBuilderTest.cs | 77 +- .../ConstructingMegArchiveBuilderBase.cs | 13 +- ...Information.cs => MegBinaryInformation.cs} | 10 +- .../V1/ConstructingMegArchiveBuilderV1.cs | 6 +- .../Binary/IMegBinaryServiceFactory.cs | 13 +- .../Binary/MegBinaryServiceFactory.cs | 20 +- .../Binary/MegFileConstants.cs | 10 +- .../Binary/Metadata/IMegFileDescriptor.cs | 23 +- .../Binary/Metadata/IMegFileMetadata.cs | 9 + .../Binary/Metadata/IMegHeader.cs | 5 +- .../Binary/Metadata/V1/MegFileTableRecord.cs | 8 +- .../Binary/Metadata/V1/MegMetadata.cs | 2 +- .../Reader/Validation/IMegBinaryValidator.cs | 2 +- .../Binary/Version/IMegVersionIdentifier.cs | 4 +- .../Binary/Version/MegVersionIdentifier.cs | 43 +- .../Data/Archives/ConstructingMegArchive.cs | 5 +- .../Data/Archives/IConstructingMegArchive.cs | 3 +- .../Data/Archives/IMegArchive.cs | 4 +- .../Data/Archives/IMegDataEntryHolder.cs | 24 +- .../Data/Archives/MegArchive.cs | 5 +- .../Data/Archives/MegDataEntryHolderBase.cs | 7 +- .../Data/Archives/VirtualMegArchive.cs | 5 +- .../Data/Entries/IMegDataEntry.cs | 5 +- .../Data/Entries/MegDataEntry.cs | 12 +- .../Data/Entries/MegDataEntryBase.cs | 2 +- .../Data/Entries/MegDataEntryReference.cs | 2 +- .../Data/EntryLocations/IDataEntryLocation.cs | 2 +- .../EntryLocations/MegDataEntryLocation.cs | 6 +- .../MegDataEntryLocationReference.cs | 44 +- .../EntryLocations/MegDataEntryOriginInfo.cs | 29 +- .../Data/IMegDataSource.cs | 29 + .../Data/InMemoryMeg.cs | 54 ++ .../Data/MegDataEntryBuilderInfo.cs | 14 +- .../{Files => Data}/MegEncryptionData.cs | 8 +- .../MegFileVersion.cs => Data/MegVersion.cs} | 4 +- .../Exceptions/EntryNotInMegException.cs | 16 +- .../Exceptions/MegSizeException.cs | 3 +- .../Files/IMegFile.cs | 16 +- .../Files/MegFile.cs | 39 +- .../Files/MegFileInformation.cs | 25 +- .../MegServiceContribution.cs | 5 +- .../Builder/Builders/EmpireAtWarMegBuilder.cs | 14 +- .../Builders/PetroglyphGameMegBuilder.cs | 16 +- .../Services/Builder/IMegBuilder.cs | 19 +- .../Services/Builder/MegBuilderBase.cs | 20 +- .../Services/Builder/MegDataEntryAddResult.cs | 12 +- .../Services/Builder/MegDataEntryAddStatus.cs | 2 +- .../EmpireAtWarMegDataEntryPathNormalizer.cs | 4 +- .../IMegDataEntryPathNormalizer.cs | 2 +- .../MegDataEntryPathNormalizerBase.cs | 2 +- .../PetroglyphMegDataEntryPathNormalizer.cs | 4 +- .../Entry/MegDataEntryValidationResult.cs | 2 +- .../BinaryMegFileInformationValidator.cs | 12 +- .../EmpireAtWarMegFileInformationValidator.cs | 10 +- .../FileInfo/MegFileInfoValidationResult.cs | 4 +- .../Services/IMegFileExtractor.cs | 32 +- .../Services/IMegFileService.cs | 89 --- .../Services/IMegService.cs | 194 ++++++ .../Services/IVirtualMegArchiveBuilder.cs | 28 +- .../Internal/IMegDataStreamFactory.cs | 15 - .../Services/Internal/MegDataStreamFactory.cs | 63 -- .../Services/Internal/MegFileExtractor.cs | 13 +- .../Services/Internal/MegFileService.cs | 129 ---- .../Services/Internal/MegService.cs | 216 ++++++ .../Internal/VirtualMegArchiveBuilder.cs | 28 +- .../Utilities/MegEntryStream.cs | 2 +- .../PG.StarWarsGame.Files.MTD.Test.csproj | 2 +- .../Services/MtdFileServiceTest.cs | 109 --- .../Services/MtdServiceTest.cs | 135 ++++ .../Binary/MtdFileConstants.cs | 2 +- .../Data/IMegaTextureDirectory.cs | 8 +- .../Data/MegaTextureFileIndex.cs | 6 +- .../MtdServiceContribution.cs | 2 +- .../Services/IMtdFileService.cs | 36 - .../Services/IMtdService.cs | 64 ++ .../Services/MtdFileService.cs | 46 -- .../Services/MtdService.cs | 74 ++ .../FileBuilderTestBase.cs | 41 +- .../Binary/BinaryBase.cs | 4 +- .../Binary/BinaryFile.cs | 2 +- .../Binary/BinaryTable.cs | 2 +- .../Binary/File/IBinaryFileReader.cs | 4 +- .../PG.StarWarsGame.Files/Binary/IBinary.cs | 2 +- .../Binary/IBinaryConverter.cs | 2 +- .../Binary/IBinaryTable.cs | 2 +- .../Binary/PetroglyphBinaryReader.cs | 20 +- .../IPetroglyphFileHolder.cs | 4 +- .../PetroglyphFileHolder.cs | 8 +- .../PetroglyphFileInformation.cs | 6 +- .../PetroglyphMegPackableFileInformation.cs | 11 +- .../Services/Builder/FileBuilderBase.cs | 6 +- .../Services/Builder/IFileBuilder.cs | 8 +- .../Hashing/ParseIntCrc32HashingService.cs | 8 + PG.Testing/NonSeekableReadStream.cs | 50 ++ PG.Testing/PG.Testing.csproj | 2 +- PG.Testing/PGTestBase.cs | 4 + PG.Testing/TestMegDataStream.cs | 24 + version.json | 2 +- 186 files changed, 3597 insertions(+), 2408 deletions(-) create mode 100644 CLAUDE.md delete mode 100644 PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/DatFileServiceTest.cs create mode 100644 PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/DatServiceTest.cs create mode 100644 PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatLayoutKind.cs delete mode 100644 PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Files/DatFileType.cs delete mode 100644 PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatFileService.cs create mode 100644 PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatService.cs delete mode 100644 PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatFileService.cs create mode 100644 PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatService.cs create mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataSourceTest.cs create mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataSourceTestSuite.cs delete mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataStreamFactoryTest.cs delete mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegFileServiceIntegrationTest.cs create mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegServiceIntegrationTest.cs rename PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/{MegFileBinaryInformation.cs => MegBinaryInformation.cs} (63%) create mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/IMegDataSource.cs create mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/InMemoryMeg.cs rename PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/{Files => Data}/MegEncryptionData.cs (91%) rename PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/{Files/MegFileVersion.cs => Data/MegVersion.cs} (92%) delete mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegFileService.cs create mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegService.cs delete mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/IMegDataStreamFactory.cs delete mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegDataStreamFactory.cs delete mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegFileService.cs create mode 100644 PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegService.cs delete mode 100644 PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/Services/MtdFileServiceTest.cs create mode 100644 PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/Services/MtdServiceTest.cs delete mode 100644 PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/IMtdFileService.cs create mode 100644 PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/IMtdService.cs delete mode 100644 PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/MtdFileService.cs create mode 100644 PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/MtdService.cs create mode 100644 PG.Testing/NonSeekableReadStream.cs diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..f053e3b1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,58 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository purpose + +PetroglyphTools is a set of .NET libraries (published as `AlamoEngineTools.*` NuGet packages) for reading and writing the proprietary file formats used by Petroglyph's Alamo / Glyphx engines — most notably Star Wars: Empire at War. Each format gets its own library; `PG.Commons` is the shared foundation. + +## Solution layout + +The solution file is **`PetroglyphTools.slnx`** (the new XML solution format — not `.sln`). Each format library lives in a top-level folder containing two sibling projects: `PG.` (production) and `PG..Test` (xUnit v3 test runner). Notable cross-cutting projects: + +- `PG.Commons` — CRC32 hashing, numerics, `ServiceBase`, file-name validation, stream/string utilities. Foundation for everything else. +- `PG.StarWarsGame.Files` — abstract `PetroglyphFileHolder` and `PetroglyphFileInformation` record types that all file holders extend. +- `PG.Testing` — `PGTestBase` (built on `AnakinRaW.CommonUtilities.Testing.TestBaseWithFileSystem` + Testably.Abstractions for an in-memory `IFileSystem`). +- `PG.StarWarsGame.Files.Testing` — shared file-builder test base. +- `PG.Benchmarks` — BenchmarkDotNet harness (not in the slnx test pipeline). + +Format libraries: `PG.StarWarsGame.Files.{MEG,DAT,MTD,Xml,AnimationSfxMap}` and `PG.StarWarsGame.Localisation`. The `Xml` projects are present in the slnx but currently have `` — they are not built or tested by the default solution build. + +## Common commands + +Run from the repo root. The repo uses `Microsoft.Testing.Platform` (declared in `global.json`), so test projects build as executables and `dotnet test` invokes MTP rather than VSTest. + +```pwsh +dotnet build --configuration Release +dotnet test --configuration Release # full test suite (matches CI) +dotnet test PG.Commons/PG.Commons.Test # tests for one project +dotnet test PG.Commons/PG.Commons.Test --filter "FullyQualifiedName~PgFileNameUtilitiesTest" +dotnet pack --configuration Release --output ./packages # produces NuGet packages under bin/Packages/ +``` + +Benchmarks are run by executing the `PG.Benchmarks` project directly (`dotnet run -c Release --project PG.Benchmarks`). + +## Target frameworks + +- Production libraries: `netstandard2.0;netstandard2.1;net10.0` (some are `netstandard2.0;netstandard2.1` only — e.g. `PG.StarWarsGame.Files`). +- Test projects: `net8.0;net10.0`, plus `net481` on Windows. +- `Directory.Build.props` enforces `Nullable=enable`, `ImplicitUsings=disable`, `LangVersion=latest`, and `EnableNETAnalyzers=true`. Most production projects additionally set `TreatWarningsAsErrors=true` and `GenerateDocumentationFile=true` — XML doc comments on public API are mandatory; missing-doc warnings will fail the build. +- Modern language features on `netstandard2.0` are enabled via the `Nullable`, `IsExternalInit`, and `Required` polyfill packages — do not remove these references when editing csproj files. + +## Architectural conventions + +**Service registration via `*ServiceContribution` classes.** Each library exposes a static class (e.g. `MegServiceContribution.SupportMEG(IServiceCollection)`, `DatServiceContribution.SupportDAT(...)`, `PetroglyphCommons.ContributeServices(...)`) that registers all of its services in DI. Consumers compose the libraries they need; tests call the relevant `Support*` extension from a `Common*TestBase` deriving from `PGTestBase`. **When adding a new service, register it in the corresponding `*ServiceContribution` method** — there is no auto-discovery. + +**`ServiceBase` for services, `PetroglyphFileHolder` for file containers.** Services derive from `PG.Commons.Services.ServiceBase`, which captures `IServiceProvider`, `IFileSystem` (always resolved from DI — never use `System.IO` directly so the in-memory test FS works), and an `ILogger`. File holders derive from `PetroglyphFileHolder` and pair a parsed model with a `PetroglyphFileInformation` record (a `record` so it supports `with` expressions; the holder stores its own copy and disposes it). + +**MEG packable files.** `PetroglyphMegPackableFileInformation` skips the on-disk existence check when `IsInsideMeg` is true. When writing code that constructs file information for content extracted from a MEG, use this subtype rather than `PetroglyphFileInformation` directly. + +**MEG versions.** The MEG library handles three on-disk format versions (`MegFileVersion.V1/V2/V3`) — V1 is what EaW/FoC ship; V3 may be encrypted. Binary readers/writers are split into `Binary/Reader/V1/...` and `Binary/Construction/V1/...` subfolders; do not assume there is only one version when adding code under `Binary/`. + +**CRC32 is foundational.** Many Petroglyph formats key entries by CRC32 of an ASCII (uppercased) name. `ICrc32HashingService` and `CrcUtilities` in `PG.Commons` are the canonical entry points; do not introduce a second CRC32 implementation. + +## Branching & release + +- Day-to-day work targets **`develop`** (CI: `test.yml` runs build+test on Windows and Ubuntu for PRs into `develop`). +- Merges into **`master`** trigger `release.yml`: it runs the test workflow, then `dotnet pack`, pushes packages to nuget.org, and creates a GitHub release tagged `v{SemVer2}`. Versioning is computed by **Nerdbank.GitVersioning** from `version.json` — do not hand-edit version numbers in csproj files. +- The `.vscode/settings.json` references `PetroglyphTools.sln`, which does not exist in the repo. Use the `.slnx` file (or open the folder) instead. diff --git a/Directory.Build.props b/Directory.Build.props index 9a9ba632..9ec498ce 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -32,7 +32,7 @@ all - 3.9.50 + 3.10.85 diff --git a/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj b/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj index d86dcc28..95a920ce 100644 --- a/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj +++ b/PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj @@ -12,7 +12,7 @@ - + diff --git a/PG.Commons/PG.Commons/Data/CrcBasedEqualityComparer.cs b/PG.Commons/PG.Commons/Data/CrcBasedEqualityComparer.cs index 4d058af2..b7a9d217 100644 --- a/PG.Commons/PG.Commons/Data/CrcBasedEqualityComparer.cs +++ b/PG.Commons/PG.Commons/Data/CrcBasedEqualityComparer.cs @@ -28,7 +28,7 @@ namespace PG.Commons.Data; public sealed class CrcBasedEqualityComparer : IEqualityComparer where T : IHasCrc32 { /// - /// Returns a default equality comparer for the type specified by the generic argument. + /// Gets the default equality comparer for the type specified by the generic argument. /// public static readonly CrcBasedEqualityComparer Instance = new(); @@ -45,8 +45,8 @@ public sealed class CrcBasedEqualityComparer : IEqualityComparer where T : /// The values of and are equal. /// /// - /// The first object of type to compare. - /// The second object of type to compare. + /// The first object to compare. + /// The second object to compare. /// /// if the specified objects are both , /// are the same reference, or have equal CRC32 checksums; otherwise, . diff --git a/PG.Commons/PG.Commons/Data/IHasCrc32.cs b/PG.Commons/PG.Commons/Data/IHasCrc32.cs index febd2943..3a8d745d 100644 --- a/PG.Commons/PG.Commons/Data/IHasCrc32.cs +++ b/PG.Commons/PG.Commons/Data/IHasCrc32.cs @@ -6,7 +6,7 @@ namespace PG.Commons.Data; /// -/// An interface representing data that has a CRC32 checksum. +/// Represents data that has a CRC32 checksum. /// /// /// It is up to the implementation to decide which properties are considered for the CRC32 checksum. diff --git a/PG.Commons/PG.Commons/Hashing/Crc32.cs b/PG.Commons/PG.Commons/Hashing/Crc32.cs index 7be57a2c..1988a58d 100644 --- a/PG.Commons/PG.Commons/Hashing/Crc32.cs +++ b/PG.Commons/PG.Commons/Hashing/Crc32.cs @@ -57,7 +57,7 @@ public override string ToString() } /// - /// When , the checksum is represented by a signed integer; unsigned otherwise. + /// to represent the checksum as a signed integer; otherwise, . public string ToString(bool asSignedInteger) { var sb = new StringBuilder("CRC: "); @@ -95,7 +95,7 @@ public override int GetHashCode() /// /// Returns the CRC32 checksum as a byte array. /// - /// The CRC32 checksum as a byte array. + /// The CRC32 checksum in little endian byte order. public unsafe byte[] GetBytes() { Span data = stackalloc byte[sizeof(Crc32)]; @@ -106,6 +106,7 @@ public unsafe byte[] GetBytes() /// /// Writes the CRC32 checksum into a span of bytes in little endian. /// + /// The span to write the checksum into. public void GetBytes(Span destination) { BinaryPrimitives.WriteUInt32LittleEndian(destination, _checksum); @@ -178,15 +179,15 @@ public void GetBytes(Span destination) } /// - /// Defines an implicit conversion of an CRC32 checksum to an . + /// Defines an explicit conversion of a CRC32 checksum to a . /// - /// The checksum data. + /// The checksum to convert. public static explicit operator uint(Crc32 crc) => crc._checksum; /// - /// Defines an implicit conversion of an CRC32 checksum to an , which might be negative. + /// Defines an explicit conversion of a CRC32 checksum to an , which might be negative. /// - /// The checksum data. + /// The checksum to convert. public static explicit operator int(Crc32 crc) { unchecked diff --git a/PG.Commons/PG.Commons/Numerics/Vector2Int.cs b/PG.Commons/PG.Commons/Numerics/Vector2Int.cs index fbcdc919..34419faa 100644 --- a/PG.Commons/PG.Commons/Numerics/Vector2Int.cs +++ b/PG.Commons/PG.Commons/Numerics/Vector2Int.cs @@ -18,8 +18,8 @@ namespace PG.Commons.Numerics; public int Second { get; } /// - /// Constructs a vector from the given . If the span does not contain enough elements, - /// the default integer value 0 is used to initialize the respecting component. + /// Initializes a new instance of the struct from the given . + /// If the span does not contain enough elements, the default integer value 0 is used to initialize the respective component. /// /// The span of elements to assign to the vector. public Vector2Int(ReadOnlySpan values) @@ -32,7 +32,7 @@ public Vector2Int(ReadOnlySpan values) } /// - /// Creates a vector whose elements have the specified values. + /// Initializes a new instance of the struct whose elements have the specified values. /// /// The value to assign to the field. /// The value to assign to the field. @@ -56,7 +56,7 @@ public bool Equals(Vector2Int other) /// Returns a value that indicates whether this instance and a specified object are equal. /// /// The object to compare with the current instance. - /// if the current instance and are equal; otherwise, . If obj is , the method returns . + /// if the current instance and are equal; otherwise, . If is , the method returns . public override bool Equals(object? obj) { return obj is Vector2Int other && Equals(other); diff --git a/PG.Commons/PG.Commons/Numerics/Vector3Int.cs b/PG.Commons/PG.Commons/Numerics/Vector3Int.cs index 6e0bc842..d33a71ee 100644 --- a/PG.Commons/PG.Commons/Numerics/Vector3Int.cs +++ b/PG.Commons/PG.Commons/Numerics/Vector3Int.cs @@ -23,8 +23,8 @@ namespace PG.Commons.Numerics; public int Third { get; } /// - /// Constructs a vector from the given . If the span does not contain enough elements, - /// the default integer value 0 is used to initialize the respecting component. + /// Initializes a new instance of the struct from the given . + /// If the span does not contain enough elements, the default integer value 0 is used to initialize the respective component. /// /// The span of elements to assign to the vector. public Vector3Int(ReadOnlySpan values) @@ -39,7 +39,7 @@ public Vector3Int(ReadOnlySpan values) } /// - /// Creates a vector whose elements have the specified values. + /// Initializes a new instance of the struct whose elements have the specified values. /// /// The value to assign to the field. /// The value to assign to the field. @@ -65,7 +65,7 @@ public bool Equals(Vector3Int other) /// Returns a value that indicates whether this instance and a specified object are equal. /// /// The object to compare with the current instance. - /// if the current instance and are equal; otherwise, . If obj is , the method returns . + /// if the current instance and are equal; otherwise, . If is , the method returns . public override bool Equals(object? obj) { return obj is Vector3Int other && Equals(other); diff --git a/PG.Commons/PG.Commons/Numerics/Vector4Int.cs b/PG.Commons/PG.Commons/Numerics/Vector4Int.cs index ed692318..fd439409 100644 --- a/PG.Commons/PG.Commons/Numerics/Vector4Int.cs +++ b/PG.Commons/PG.Commons/Numerics/Vector4Int.cs @@ -28,8 +28,8 @@ namespace PG.Commons.Numerics; public int Fourth { get; } /// - /// Constructs a vector from the given . If the span does not contain enough elements, - /// the default integer value 0 is used to initialize the respecting component. + /// Initializes a new instance of the struct from the given . + /// If the span does not contain enough elements, the default integer value 0 is used to initialize the respective component. /// /// The span of elements to assign to the vector. public Vector4Int(ReadOnlySpan values) @@ -46,7 +46,7 @@ public Vector4Int(ReadOnlySpan values) } /// - /// Creates a vector whose elements have the specified values. + /// Initializes a new instance of the struct whose elements have the specified values. /// /// The value to assign to the field. /// The value to assign to the field. @@ -74,7 +74,7 @@ public bool Equals(Vector4Int other) /// Returns a value that indicates whether this instance and a specified object are equal. /// /// The object to compare with the current instance. - /// if the current instance and are equal; otherwise, . If obj is , the method returns . + /// if the current instance and are equal; otherwise, . If is , the method returns . public override bool Equals(object? obj) { return obj is Vector4Int other && Equals(other); diff --git a/PG.Commons/PG.Commons/PG.Commons.csproj b/PG.Commons/PG.Commons/PG.Commons.csproj index 77aeba12..d19a406c 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.Commons/PG.Commons/Services/ServiceBase.cs b/PG.Commons/PG.Commons/Services/ServiceBase.cs index dc781c76..0b5f640e 100644 --- a/PG.Commons/PG.Commons/Services/ServiceBase.cs +++ b/PG.Commons/PG.Commons/Services/ServiceBase.cs @@ -11,7 +11,7 @@ namespace PG.Commons.Services; /// -/// Base class for services. +/// Provides a base class for services. /// public abstract class ServiceBase : DisposableObject { diff --git a/PG.Commons/PG.Commons/Utilities/CrcUtilities.cs b/PG.Commons/PG.Commons/Utilities/CrcUtilities.cs index 61c1110f..24e4df32 100644 --- a/PG.Commons/PG.Commons/Utilities/CrcUtilities.cs +++ b/PG.Commons/PG.Commons/Utilities/CrcUtilities.cs @@ -24,7 +24,7 @@ public static class Crc32Utilities /// CRC32 sorted list of elements. /// The CRC-to-range table. /// is . - /// is not sorted. + /// is not sorted. /// /// The given input list [1,2,2,2,3] returns the following dictionary: {{1, 0..1}, {2, 1..4}, {3, 4..5}} /// @@ -125,7 +125,7 @@ public static IList SortByCrc32(IEnumerable items) where T : IHasCrc32 /// /// The type of the elements of source. /// A sequence of values to check for correct sorting. - /// is . + /// is . /// is not sorted by CRC32 checksum. public static void EnsureSortedByCrc32(IEnumerable items) where T : IHasCrc32 { @@ -140,8 +140,8 @@ public static void EnsureSortedByCrc32(IEnumerable items) where T : IHasCr /// /// The type of the elements of source. /// A sequence of values to check for correct sorting. - /// is the specified collection is sorted; otherwise, . - /// is . + /// if the specified collection is sorted; otherwise, . + /// is . public static bool IsSortedByCrc32(IEnumerable items) where T : IHasCrc32 { if (items == null) diff --git a/PG.Commons/PG.Commons/Utilities/EncodingExtensions.cs b/PG.Commons/PG.Commons/Utilities/EncodingExtensions.cs index fa3ae6e9..75c14cd7 100644 --- a/PG.Commons/PG.Commons/Utilities/EncodingExtensions.cs +++ b/PG.Commons/PG.Commons/Utilities/EncodingExtensions.cs @@ -36,7 +36,7 @@ public static class EncodingExtensions /// The encoding to be used. /// The number of characters to encode. /// The number of bytes - /// is . + /// is . /// is less than zero. /// is not supported. [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/PG.Commons/PG.Commons/Utilities/PGFileNameUtilities.cs b/PG.Commons/PG.Commons/Utilities/PGFileNameUtilities.cs index 1cdb2556..bc643b11 100644 --- a/PG.Commons/PG.Commons/Utilities/PGFileNameUtilities.cs +++ b/PG.Commons/PG.Commons/Utilities/PGFileNameUtilities.cs @@ -13,7 +13,7 @@ namespace PG.Commons.Utilities; public static class PGFileNameUtilities { /// - /// Checks whether a given filename is can be used for a Petroglyph Star Wars game. + /// Checks whether a given filename can be used for a Petroglyph Star Wars game. /// /// /// A filename is considered to be invalid under the following conditions:
@@ -22,8 +22,11 @@ public static class PGFileNameUtilities /// c) The filename contains a non ASCII character (char > 0xFF).
///
/// The filename to check. - /// Detailed information status. Can be used for error message reporting. - /// when the filename is valid; otherwise. + /// + /// When this method returns, contains the detailed validation status, which can be used for error message reporting. + /// This parameter is treated as uninitialized. + /// + /// if the filename is valid; otherwise, . public static bool IsValidFileName(ReadOnlySpan filename, out FileNameValidationResult result) { var validator = WindowsFileNameValidator.Instance; diff --git a/PG.Commons/PG.Commons/Utilities/StreamExtensions.cs b/PG.Commons/PG.Commons/Utilities/StreamExtensions.cs index d5723044..7ae091b0 100644 --- a/PG.Commons/PG.Commons/Utilities/StreamExtensions.cs +++ b/PG.Commons/PG.Commons/Utilities/StreamExtensions.cs @@ -29,7 +29,10 @@ public static string GetFilePath(this Stream stream) /// Gets the file path of the file opened in the . The path may be relative. /// /// The stream to get the file path from. - /// Stores the status whether is a MEG stream. + /// + /// When this method returns, contains a value that indicates whether is a MEG stream. + /// This parameter is treated as uninitialized. + /// /// The file path of the opened file. /// does not have path information. public static string GetFilePath(this Stream stream, out bool isMegStream) @@ -57,7 +60,7 @@ public static string GetFilePath(this Stream stream, out bool isMegStream) /// The stream to retrieve the file path from. /// /// When this method returns, contains the file path of the opened file if the operation was successful; - /// otherwise, . This parameter is passed uninitialized. + /// otherwise, . This parameter is treated as uninitialized. /// /// /// if the file path was successfully retrieved; otherwise, . @@ -73,7 +76,7 @@ public static bool TryGetFilePath(this Stream stream, [NotNullWhen(true)] out st /// The stream to retrieve the file path from. /// /// When this method returns, contains the file path of the opened file if the operation was successful; - /// otherwise, . This parameter is passed uninitialized. + /// otherwise, . This parameter is treated as uninitialized. /// /// /// When this method returns, contains a value indicating whether the stream is a MEG file data stream. diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Binary/Converter/DatBinaryConverterTest.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Binary/Converter/DatBinaryConverterTest.cs index f035a51b..55693607 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Binary/Converter/DatBinaryConverterTest.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Binary/Converter/DatBinaryConverterTest.cs @@ -1,10 +1,9 @@ -using AnakinRaW.CommonUtilities.Collections; +using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Files.Binary; using PG.StarWarsGame.Files.DAT.Binary; using PG.StarWarsGame.Files.DAT.Binary.Metadata; using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; using PG.Testing; using System; using System.Collections.Generic; @@ -75,7 +74,7 @@ public void BinaryToModel_Sorted() var model = _converter.BinaryToModel(binary); Assert.Equal(binary.RecordNumber, model.Count); - Assert.Equal(DatFileType.OrderedByCrc32, model.KeySortOrder); + Assert.Equal(DatLayoutKind.OrderedByCrc32, model.Layout); Assert.Equal([ CreateEntry(true, "a"), @@ -112,7 +111,7 @@ public void BinaryToModel_Unsorted() var model = _converter.BinaryToModel(binary); Assert.Equal(binary.RecordNumber, model.Count); - Assert.Equal(DatFileType.NotOrdered, model.KeySortOrder); + Assert.Equal(DatLayoutKind.NotOrdered, model.Layout); Assert.Equal([ CreateEntry(false, "a"), @@ -212,7 +211,7 @@ public void ModelToBinary_WantsSortedButIsNot_ThrowsArgumentException() // Model claims to be sorted, while it does not enforce it. private class InvalidDatModel(IEnumerable entries) : DatModel(entries) { - public override DatFileType KeySortOrder => DatFileType.OrderedByCrc32; + public override DatLayoutKind Layout => DatLayoutKind.OrderedByCrc32; public override ImmutableFrugalList EntriesWithCrc(Crc32 key) { diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Binary/Reader/DatFileReaderTest.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Binary/Reader/DatFileReaderTest.cs index ff2e30cb..3a87f160 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Binary/Reader/DatFileReaderTest.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Binary/Reader/DatFileReaderTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,7 +7,7 @@ using PG.Commons.Hashing; using PG.StarWarsGame.Files.Binary; using PG.StarWarsGame.Files.DAT.Binary; -using PG.StarWarsGame.Files.DAT.Files; +using PG.StarWarsGame.Files.DAT.Data; using Xunit; namespace PG.StarWarsGame.Files.DAT.Test.Binary.Reader; @@ -22,36 +22,36 @@ public DatFileReaderTest() } [Fact] - public void PeekFileType_ThrowsArgumentNullException() + public void PeekLayout_ThrowsArgumentNullException() { - Assert.Throws(() => _reader.PeekFileType(null!)); + Assert.Throws(() => _reader.PeekLayout(null!)); } [Fact] - public void PeekFileType_ThrowsBinaryCorruptedException() + public void PeekLayout_ThrowsBinaryCorruptedException() { - Assert.Throws(() => _reader.PeekFileType(new MemoryStream())); + Assert.Throws(() => _reader.PeekLayout(new MemoryStream())); } [Theory] - [MemberData(nameof(DatFileTypeTestData))] - public void PeekFileType(Stream stream, DatFileType expectedFileType) + [MemberData(nameof(DatLayoutKindTestData))] + public void PeekLayout(Stream stream, DatLayoutKind expectedLayout) { - var fileType = _reader.PeekFileType(stream); - Assert.Equal(expectedFileType, fileType); + var layout = _reader.PeekLayout(stream); + Assert.Equal(expectedLayout, layout); // Ensure that stream is not disposed after read operation stream.Position = 1; } - public static IEnumerable DatFileTypeTestData() + public static IEnumerable DatLayoutKindTestData() { return [ [ // Empty .DAT: While the file type is not specified by the interface, this test must not crash. new MemoryStream([0x0, 0x0, 0x0, 0x0]), - DatFileType.OrderedByCrc32 + DatLayoutKind.OrderedByCrc32 ], [ new MemoryStream([ @@ -66,7 +66,7 @@ public static IEnumerable DatFileTypeTestData() 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ]), - DatFileType.OrderedByCrc32 + DatLayoutKind.OrderedByCrc32 ], [ new MemoryStream([ @@ -81,39 +81,39 @@ public static IEnumerable DatFileTypeTestData() 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ]), - DatFileType.NotOrdered + DatLayoutKind.NotOrdered ], [ TestingHelpers.GetEmbeddedResource(typeof(DatFileReaderTest), "Files.EmptyKeyWithValue.dat"), - DatFileType.OrderedByCrc32 + DatLayoutKind.OrderedByCrc32 ], [ TestingHelpers.GetEmbeddedResource(typeof(DatFileReaderTest), "Files.SingleEmptyEntry.dat"), - DatFileType.OrderedByCrc32 + DatLayoutKind.OrderedByCrc32 ], [ TestingHelpers.GetEmbeddedResource(typeof(DatFileReaderTest), "Files.SingleEntry.dat"), - DatFileType.OrderedByCrc32 + DatLayoutKind.OrderedByCrc32 ], [ TestingHelpers.GetEmbeddedResource(typeof(DatFileReaderTest), "Files.Sorted_TwoEntries.dat"), - DatFileType.OrderedByCrc32 + DatLayoutKind.OrderedByCrc32 ], [ TestingHelpers.GetEmbeddedResource(typeof(DatFileReaderTest), "Files.Sorted_TwoEntriesDuplicate.dat"), - DatFileType.OrderedByCrc32 + DatLayoutKind.OrderedByCrc32 ], [ TestingHelpers.GetEmbeddedResource(typeof(DatFileReaderTest), "Files.mastertextfile_english.dat"), - DatFileType.OrderedByCrc32 + DatLayoutKind.OrderedByCrc32 ], [ TestingHelpers.GetEmbeddedResource(typeof(DatFileReaderTest), "Files.Index_WithDuplicates.dat"), - DatFileType.NotOrdered + DatLayoutKind.NotOrdered ], [ TestingHelpers.GetEmbeddedResource(typeof(DatFileReaderTest), "Files.creditstext_english.dat"), - DatFileType.NotOrdered + DatLayoutKind.NotOrdered ] ]; } diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/DatModelTest.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/DatModelTest.cs index 032eb4dd..02adc964 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/DatModelTest.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/DatModelTest.cs @@ -1,17 +1,16 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; using PG.Commons.Hashing; using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; using Xunit; namespace PG.StarWarsGame.Files.DAT.Test.Data; public abstract class DatModelTest { - protected abstract DatFileType ExpectedFileType { get; } + protected abstract DatLayoutKind ExpectedLayout { get; } protected abstract IDatModel CreateModel(IList entries); @@ -28,7 +27,7 @@ public void Ctor() { var model = CreateModel(CreateDataEntries()); - Assert.Equal(ExpectedFileType, model.KeySortOrder); + Assert.Equal(ExpectedLayout, model.Layout); Assert.Equal(4, model.Count); Assert.Equivalent(new HashSet{ "1", "3", "4" }.ToList(), model.Keys.ToList()); Assert.Equivalent(new HashSet { new(1), new(3), new(4) }.ToList(), model.CrcKeys.ToList()); diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/SortedDatModelTest.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/SortedDatModelTest.cs index 3b315453..679dab12 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/SortedDatModelTest.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/SortedDatModelTest.cs @@ -1,16 +1,15 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using PG.Commons.Hashing; using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; using Xunit; namespace PG.StarWarsGame.Files.DAT.Test.Data; public class SortedDatModelTest : DatModelTest { - protected override DatFileType ExpectedFileType => DatFileType.OrderedByCrc32; + protected override DatLayoutKind ExpectedLayout => DatLayoutKind.OrderedByCrc32; private SortedDatModel CreateSortedModel(IList entries) { @@ -60,6 +59,6 @@ public void ToUnsortedModel() var unsorted = model.ToUnsortedModel(); Assert.Equal(model.ToList(), unsorted.ToList()); - Assert.Equal(DatFileType.NotOrdered, unsorted.KeySortOrder); + Assert.Equal(DatLayoutKind.NotOrdered, unsorted.Layout); } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/UnsortedDatModelTest.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/UnsortedDatModelTest.cs index a03a4c23..203f0ba5 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/UnsortedDatModelTest.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Data/UnsortedDatModelTest.cs @@ -1,15 +1,14 @@ -using System.Collections.Generic; +using System.Collections.Generic; using PG.Commons.Hashing; using PG.Commons.Utilities; using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; using Xunit; namespace PG.StarWarsGame.Files.DAT.Test.Data; public class UnsortedDatModelTest : DatModelTest { - protected override DatFileType ExpectedFileType => DatFileType.NotOrdered; + protected override DatLayoutKind ExpectedLayout => DatLayoutKind.NotOrdered; private UnsortedDatModel CreateUnsortedModel(IList entries) { @@ -46,6 +45,6 @@ public void ToSortedModel() var sorted = model.ToSortedModel(); Assert.True(Crc32Utilities.IsSortedByCrc32(sorted)); - Assert.Equal(DatFileType.OrderedByCrc32, sorted.KeySortOrder); + Assert.Equal(DatLayoutKind.OrderedByCrc32, sorted.Layout); } } \ No newline at end of file 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 24fe103e..51ea7466 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 @@ -34,7 +34,7 @@
- + diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/DatBuilderBaseTest.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/DatBuilderBaseTest.cs index 9ae8cd72..e715208b 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/DatBuilderBaseTest.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/DatBuilderBaseTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -266,7 +266,7 @@ public void BuildModel() builder.AddEntry("key3", "value"); var model = builder.BuildModel(); - Assert.Equal(builder.TargetKeySortOrder, model.KeySortOrder); + Assert.Equal(builder.TargetLayout, model.Layout); Assert.Equal(3, model.Count); Assert.Equal(["key1", "key2", "key3"], model.Keys); } @@ -276,12 +276,12 @@ public void IntegrationTest_Sorted_MasterText_CreateFromModelAndBuild() { using (var fs = FileSystem.FileStream.New("MasterTextFile.dat", FileMode.Create)) { - using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatFileServiceTest), "Files.mastertextfile_english.dat"); + using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatServiceTest), "Files.mastertextfile_english.dat"); stream.CopyTo(fs); } - var masterTextModel = ServiceProvider.GetRequiredService().LoadAs("MasterTextFile.dat", - IsOrderedBuilder ? DatFileType.OrderedByCrc32 : DatFileType.NotOrdered).Content; + var masterTextModel = ServiceProvider.GetRequiredService().LoadFileAs("MasterTextFile.dat", + IsOrderedBuilder ? DatLayoutKind.OrderedByCrc32 : DatLayoutKind.NotOrdered).Content; var builder = CreateBuilder(); diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/EmpireAtWarCreditsTextBuilderTest.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/EmpireAtWarCreditsTextBuilderTest.cs index 78f38b1a..64f3849e 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/EmpireAtWarCreditsTextBuilderTest.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/EmpireAtWarCreditsTextBuilderTest.cs @@ -1,7 +1,6 @@ -using AnakinRaW.CommonUtilities.Testing; +using AnakinRaW.CommonUtilities.Testing; using Microsoft.Extensions.DependencyInjection; using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; using PG.StarWarsGame.Files.DAT.Services; using PG.StarWarsGame.Files.DAT.Services.Builder; using PG.StarWarsGame.Files.DAT.Services.Builder.Validation; @@ -51,7 +50,7 @@ public void Ctor() Assert.NotNull(builder.SortedEntries); Assert.NotNull(builder.Entries); Assert.Equal(BuilderOverrideKind.AllowDuplicate, builder.KeyOverwriteBehavior); - Assert.Equal(DatFileType.NotOrdered, builder.TargetKeySortOrder); + Assert.Equal(DatLayoutKind.NotOrdered, builder.TargetLayout); Assert.IsType(builder.KeyValidator); } @@ -60,11 +59,11 @@ public void IntegrationTest_Unsorted_Credits_CreateFromModelAndBuild() { using (var fs = FileSystem.FileStream.New("Credits.dat", FileMode.Create)) { - using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatFileServiceTest), "Files.creditstext_english.dat"); + using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatServiceTest), "Files.creditstext_english.dat"); stream.CopyTo(fs); } - var creditsModel = ServiceProvider.GetRequiredService().LoadAs("Credits.dat", DatFileType.NotOrdered).Content; + var creditsModel = ServiceProvider.GetRequiredService().LoadFileAs("Credits.dat", DatLayoutKind.NotOrdered).Content; var builder = CreateBuilder(); diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/EmpireAtWarMasterTextBuilderTest.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/EmpireAtWarMasterTextBuilderTest.cs index 3ffc7d8f..65e29949 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/EmpireAtWarMasterTextBuilderTest.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/Builder/EmpireAtWarMasterTextBuilderTest.cs @@ -1,6 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; using PG.StarWarsGame.Files.DAT.Services.Builder; using PG.StarWarsGame.Files.DAT.Services.Builder.Validation; using Xunit; @@ -41,7 +40,7 @@ public void Ctor() Assert.NotNull(builder.SortedEntries); Assert.NotNull(builder.Entries); Assert.Equal(OverrideKind, builder.KeyOverwriteBehavior); - Assert.Equal(DatFileType.OrderedByCrc32, builder.TargetKeySortOrder); + Assert.Equal(DatLayoutKind.OrderedByCrc32, builder.TargetLayout); Assert.IsType(builder.KeyValidator); } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/DatFileServiceTest.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/DatFileServiceTest.cs deleted file mode 100644 index 118ac026..00000000 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/DatFileServiceTest.cs +++ /dev/null @@ -1,293 +0,0 @@ -using AnakinRaW.CommonUtilities.Testing; -using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; -using PG.StarWarsGame.Files.DAT.Services; -using PG.Testing; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Testably.Abstractions.Testing; -using Xunit; - -namespace PG.StarWarsGame.Files.DAT.Test.Services; - -public class DatFileServiceTest : PGTestBase -{ - private readonly DatFileService _service; - - public DatFileServiceTest() - { - _service = new DatFileService(ServiceProvider); - } - - protected override void SetupServices(IServiceCollection serviceCollection) - { - base.SetupServices(serviceCollection); - serviceCollection.SupportDAT(); - } - - [Fact] - public void CreateDatFile_Throws() - { - Assert.Throws(() => - _service.CreateDatFile(null!, new List(), DatFileType.NotOrdered)); - - Assert.Throws(() => - _service.CreateDatFile(FileSystem.FileStream.New("test.dat", FileMode.Create), null!, DatFileType.NotOrdered)); - } - - [Fact] - public void CreateDatFile_PreserveOrder() - { - var binary = DatTestData.CreateUnsortedBinary(); - var model = DatTestData.CreateUnsortedModel(); - - var fs = FileSystem.FileStream.New("test.dat", FileMode.Create); - _service.CreateDatFile(fs, model, DatFileType.NotOrdered); - fs.Dispose(); - - Assert.Equal(binary.Bytes, FileSystem.File.ReadAllBytes("test.dat")); - } - - [Fact] - public void CreateDatFile_SortEntries() - { - var binary = DatTestData.CreateSortedBinary(); - var model = DatTestData.CreateUnsortedModel(); - - var fs = FileSystem.FileStream.New("test.dat", FileMode.Create); - _service.CreateDatFile(fs, model, DatFileType.OrderedByCrc32); - fs.Dispose(); - - Assert.Equal(binary.Bytes, FileSystem.File.ReadAllBytes("test.dat")); - } - - [Fact] - public void GetDatFileType_MatchesExpected() - { - using (var fs = FileSystem.FileStream.New("MasterTextFile.dat", FileMode.Create)) - { - using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatFileServiceTest), "Files.mastertextfile_english.dat"); - stream.CopyTo(fs); - } - using (var fs = FileSystem.FileStream.New("Credits.dat", FileMode.Create)) - { - using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatFileServiceTest), "Files.creditstext_english.dat"); - stream.CopyTo(fs); - } - - Assert.Equal(DatFileType.OrderedByCrc32, _service.GetDatFileType("MasterTextFile.dat")); - Assert.Equal(DatFileType.NotOrdered, _service.GetDatFileType("Credits.dat")); - } - - [Fact] - public void Load() - { - var sortedBinary = DatTestData.CreateSortedBinary(); - var unsortedBinary = DatTestData.CreateUnsortedBinary(); - - FileSystem.Initialize() - .WithFile("sorted.dat").Which(a => a.HasBytesContent(sortedBinary.Bytes)) - .WithFile("unsorted.dat").Which(a => a.HasBytesContent(unsortedBinary.Bytes)); - - var sortedFileHolder = _service.Load("sorted.dat"); - Assert.Equal(FileSystem.Path.GetFullPath("sorted.dat"), sortedFileHolder.FilePath); - Assert.Equal(FileSystem.Path.GetFullPath("sorted.dat"), sortedFileHolder.FileInformation.FilePath); - Assert.Equal(DatFileType.OrderedByCrc32, sortedFileHolder.Content.KeySortOrder); - Assert.Equal(DatTestData.CreateSortedModel(), sortedFileHolder.Content.ToList()); - - var unsortedFileHolder = _service.Load("unsorted.dat"); - Assert.Equal(FileSystem.Path.GetFullPath("unsorted.dat"), unsortedFileHolder.FilePath); - Assert.Equal(FileSystem.Path.GetFullPath("unsorted.dat"), unsortedFileHolder.FileInformation.FilePath); - Assert.Equal(DatFileType.NotOrdered, unsortedFileHolder.Content.KeySortOrder); - Assert.Equal(DatTestData.CreateUnsortedModel(), unsortedFileHolder.Content.ToList()); - } - - [Fact] - public void LoadAs_SortedAsUnsorted() - { - var sortedBinary = DatTestData.CreateSortedBinary(); - - FileSystem.Initialize() - .WithFile("sorted.dat").Which(a => a.HasBytesContent(sortedBinary.Bytes)); - - var unsortedFileHolder = _service.LoadAs("sorted.dat", DatFileType.NotOrdered); - - Assert.Equal(FileSystem.Path.GetFullPath("sorted.dat"), unsortedFileHolder.FilePath); - Assert.Equal(FileSystem.Path.GetFullPath("sorted.dat"), unsortedFileHolder.FileInformation.FilePath); - // Entries are still sorted, but the key sort oder was adjusted - Assert.Equal(DatFileType.NotOrdered, unsortedFileHolder.Content.KeySortOrder); - Assert.Equal(DatTestData.CreateSortedModel(), unsortedFileHolder.Content.ToList()); - } - - [Fact] - public void LoadAs_UnsortedAsSorted_Throws() - { - var unsortedBinary = DatTestData.CreateUnsortedBinary(); - - FileSystem.Initialize() - .WithFile("unsorted.dat").Which(a => a.HasBytesContent(unsortedBinary.Bytes)); - - Assert.Throws(() => - _service.LoadAs("unsorted.dat", DatFileType.OrderedByCrc32)); - } - - [Fact] - public void LoadStore_Sorted() - { - FileSystem.Initialize(); - using (var fs = FileSystem.FileStream.New("MasterTextFile.dat", FileMode.Create)) - { - using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatFileServiceTest), "Files.mastertextfile_english.dat"); - stream.CopyTo(fs); - } - - var datFile = _service.Load("MasterTextFile.dat").Content; - Assert.Equal(DatFileType.OrderedByCrc32, datFile.KeySortOrder); - - using (var fs = FileSystem.FileStream.New("NewSorted.dat", FileMode.Create)) - _service.CreateDatFile(fs, datFile, DatFileType.OrderedByCrc32); - - var asUnsortedDatFile = _service.LoadAs("MasterTextFile.dat", DatFileType.NotOrdered).Content; - Assert.Equal(DatFileType.NotOrdered, asUnsortedDatFile.KeySortOrder); - - using (var fs = FileSystem.FileStream.New("NewUnsorted.dat", FileMode.Create)) - _service.CreateDatFile(fs, asUnsortedDatFile, DatFileType.NotOrdered); - - - using (var fs = FileSystem.FileStream.New("NewSorted.dat", FileMode.Create)) - _service.CreateDatFile(fs, datFile, DatFileType.OrderedByCrc32); - using (var fs = FileSystem.FileStream.New("NewUnsorted.dat", FileMode.Create)) - _service.CreateDatFile(fs, asUnsortedDatFile, DatFileType.OrderedByCrc32); - - var expectedBytes = FileSystem.File.ReadAllBytes("MasterTextFile.dat"); - var actualBytesSorted = FileSystem.File.ReadAllBytes("NewSorted.dat"); - var actualBytesUnsorted = FileSystem.File.ReadAllBytes("NewUnsorted.dat"); - Assert.Equal(expectedBytes, actualBytesSorted); - Assert.Equal(expectedBytes, actualBytesUnsorted); - } - - [Fact] - public void LoadStore_Unsorted() - { - FileSystem.Initialize(); - using (var fs = FileSystem.FileStream.New("Credits.dat", FileMode.Create)) - { - using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatFileServiceTest), "Files.creditstext_english.dat"); - stream.CopyTo(fs); - } - - var datFile = _service.Load("Credits.dat").Content; - Assert.Equal(DatFileType.NotOrdered, datFile.KeySortOrder); - - using (var fs = FileSystem.FileStream.New("New.dat", FileMode.Create)) - _service.CreateDatFile(fs, datFile, DatFileType.NotOrdered); - - var expectedBytes = FileSystem.File.ReadAllBytes("Credits.dat"); - var actualBytesSorted = FileSystem.File.ReadAllBytes("New.dat"); - Assert.Equal(expectedBytes, actualBytesSorted); - } - - [Fact] - public void LoadStore_UnsortedAsSortedThrows() - { - FileSystem.Initialize(); - using (var fs = FileSystem.FileStream.New("Credits.dat", FileMode.Create)) - { - using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatFileServiceTest), "Files.creditstext_english.dat"); - stream.CopyTo(fs); - } - - var datFile = _service.Load("Credits.dat").Content; - - using (var fs = FileSystem.FileStream.New("New.dat", FileMode.Create)) - _service.CreateDatFile(fs, datFile, DatFileType.OrderedByCrc32); - - var creditBytes = FileSystem.File.ReadAllBytes("Credits.dat"); - var resortedBytes = FileSystem.File.ReadAllBytes("New.dat"); - Assert.NotEqual(creditBytes, resortedBytes); - - Assert.Throws(() => _service.LoadAs("Credits.dat", DatFileType.OrderedByCrc32)); - } - - [Fact] - public void Load_Empty() - { - FileSystem.Initialize(); - using (var fs = FileSystem.FileStream.New("Empty.dat", FileMode.Create)) - { - using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatFileServiceTest), "Files.Empty.dat"); - stream.CopyTo(fs); - } - - var model = _service.Load("Empty.dat"); - Assert.Empty(model.Content); - - model = _service.Load(FileSystem.File.OpenRead("Empty.dat")); - Assert.Empty(model.Content); - } - - [Fact] - public void Load_EmptyKeyWithValue() - { - FileSystem.Initialize(); - using (var fs = FileSystem.FileStream.New("EmptyKeyWithValue.dat", FileMode.Create)) - { - using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatFileServiceTest), "Files.EmptyKeyWithValue.dat"); - stream.CopyTo(fs); - } - - var model = _service.Load("EmptyKeyWithValue.dat"); - Assert.Single(model.Content); - Assert.True(model.Content.ContainsKey(string.Empty)); - - model = _service.Load(FileSystem.File.OpenRead("EmptyKeyWithValue.dat")); - Assert.Single(model.Content); - Assert.True(model.Content.ContainsKey(string.Empty)); - } - - [Fact] - public void Load_Sorted_TwoEntriesDuplicate() - { - FileSystem.Initialize(); - using (var fs = FileSystem.FileStream.New("Sorted_TwoEntriesDuplicate.dat", FileMode.Create)) - { - using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatFileServiceTest), "Files.Sorted_TwoEntriesDuplicate.dat"); - stream.CopyTo(fs); - } - - var model = _service.Load("Sorted_TwoEntriesDuplicate.dat"); - Assert.Equal(2, model.Content.Count); - Assert.Single(model.Content.Keys); - - model = _service.Load(FileSystem.File.OpenRead("Sorted_TwoEntriesDuplicate.dat")); - Assert.Equal(2, model.Content.Count); - Assert.Single(model.Content.Keys); - } - - - [Fact] - public void LoadModifyCreate() - { - FileSystem.Initialize(); - using (var fs = FileSystem.FileStream.New("Index_WithDuplicates.dat", FileMode.Create)) - { - using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatFileServiceTest), "Files.Index_WithDuplicates.dat"); - stream.CopyTo(fs); - } - - var model = _service.LoadAs("Index_WithDuplicates.dat", DatFileType.NotOrdered).Content; - Assert.Equal(DatFileType.NotOrdered, model.KeySortOrder); - var modelService = ServiceProvider.GetRequiredService(); - Assert.True(modelService.GetDuplicateEntries(model).Any()); - var withoutDups = modelService.RemoveDuplicates(model); - Assert.False(modelService.GetDuplicateEntries(withoutDups).Any()); - var sorted = modelService.SortModel(withoutDups); - Assert.Equal(DatFileType.OrderedByCrc32, sorted.KeySortOrder); - - using (var fs = FileSystem.FileStream.New("newSorted.dat", FileMode.Create)) - _service.CreateDatFile(fs, sorted, DatFileType.OrderedByCrc32); - } -} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/DatServiceTest.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/DatServiceTest.cs new file mode 100644 index 00000000..45d6eb92 --- /dev/null +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/DatServiceTest.cs @@ -0,0 +1,408 @@ +using AnakinRaW.CommonUtilities.Testing; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Files.DAT.Data; +using PG.StarWarsGame.Files.DAT.Services; +using PG.Testing; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Testably.Abstractions.Testing; +using Xunit; + +namespace PG.StarWarsGame.Files.DAT.Test.Services; + +public class DatServiceTest : PGTestBase +{ + private readonly IDatService _service; + + public DatServiceTest() + { + _service = ServiceProvider.GetRequiredService(); + } + + protected override void SetupServices(IServiceCollection serviceCollection) + { + base.SetupServices(serviceCollection); + serviceCollection.SupportDAT(); + } + + [Fact] + public void CreateDatBinary_Throws() + { + Assert.Throws(() => + _service.CreateDatBinary(null!, new List(), DatLayoutKind.NotOrdered)); + + Assert.Throws(() => + _service.CreateDatBinary(FileSystem.FileStream.New("test.dat", FileMode.Create), null!, DatLayoutKind.NotOrdered)); + } + + [Fact] + public void CreateDatBinary_PreserveOrder() + { + var binary = DatTestData.CreateUnsortedBinary(); + var model = DatTestData.CreateUnsortedModel(); + + var fs = FileSystem.FileStream.New("test.dat", FileMode.Create); + _service.CreateDatBinary(fs, model, DatLayoutKind.NotOrdered); + fs.Dispose(); + + Assert.Equal(binary.Bytes, FileSystem.File.ReadAllBytes("test.dat")); + } + + [Fact] + public void CreateDatBinary_SortEntries() + { + var binary = DatTestData.CreateSortedBinary(); + var model = DatTestData.CreateUnsortedModel(); + + var fs = FileSystem.FileStream.New("test.dat", FileMode.Create); + _service.CreateDatBinary(fs, model, DatLayoutKind.OrderedByCrc32); + fs.Dispose(); + + Assert.Equal(binary.Bytes, FileSystem.File.ReadAllBytes("test.dat")); + } + + [Fact] + public void CreateDatBinary_MemoryStream_PreserveOrder() + { + var binary = DatTestData.CreateUnsortedBinary(); + var model = DatTestData.CreateUnsortedModel(); + + using var ms = new MemoryStream(); + _service.CreateDatBinary(ms, model, DatLayoutKind.NotOrdered); + + Assert.Equal(binary.Bytes, ms.ToArray()); + } + + [Fact] + public void CreateDatBinary_MemoryStream_SortEntries() + { + var binary = DatTestData.CreateSortedBinary(); + var model = DatTestData.CreateUnsortedModel(); + + using var ms = new MemoryStream(); + _service.CreateDatBinary(ms, model, DatLayoutKind.OrderedByCrc32); + + Assert.Equal(binary.Bytes, ms.ToArray()); + } + + [Fact] + public void GetDatLayoutKind_MatchesExpected() + { + using (var fs = FileSystem.FileStream.New("MasterTextFile.dat", FileMode.Create)) + { + using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatServiceTest), "Files.mastertextfile_english.dat"); + stream.CopyTo(fs); + } + using (var fs = FileSystem.FileStream.New("Credits.dat", FileMode.Create)) + { + using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatServiceTest), "Files.creditstext_english.dat"); + stream.CopyTo(fs); + } + + Assert.Equal(DatLayoutKind.OrderedByCrc32, _service.GetDatLayoutKind("MasterTextFile.dat")); + Assert.Equal(DatLayoutKind.NotOrdered, _service.GetDatLayoutKind("Credits.dat")); + } + + [Fact] + public void LoadFile() + { + var sortedBinary = DatTestData.CreateSortedBinary(); + var unsortedBinary = DatTestData.CreateUnsortedBinary(); + + FileSystem.Initialize() + .WithFile("sorted.dat").Which(a => a.HasBytesContent(sortedBinary.Bytes)) + .WithFile("unsorted.dat").Which(a => a.HasBytesContent(unsortedBinary.Bytes)); + + var sortedFileHolder = _service.LoadFile("sorted.dat"); + Assert.Equal(FileSystem.Path.GetFullPath("sorted.dat"), sortedFileHolder.FilePath); + Assert.Equal(FileSystem.Path.GetFullPath("sorted.dat"), sortedFileHolder.FileInformation.FilePath); + Assert.Equal(DatLayoutKind.OrderedByCrc32, sortedFileHolder.Content.Layout); + Assert.Equal(DatTestData.CreateSortedModel(), sortedFileHolder.Content.ToList()); + + var unsortedFileHolder = _service.LoadFile("unsorted.dat"); + Assert.Equal(FileSystem.Path.GetFullPath("unsorted.dat"), unsortedFileHolder.FilePath); + Assert.Equal(FileSystem.Path.GetFullPath("unsorted.dat"), unsortedFileHolder.FileInformation.FilePath); + Assert.Equal(DatLayoutKind.NotOrdered, unsortedFileHolder.Content.Layout); + Assert.Equal(DatTestData.CreateUnsortedModel(), unsortedFileHolder.Content.ToList()); + } + + [Fact] + public void LoadFileAs_SortedAsUnsorted() + { + var sortedBinary = DatTestData.CreateSortedBinary(); + + FileSystem.Initialize() + .WithFile("sorted.dat").Which(a => a.HasBytesContent(sortedBinary.Bytes)); + + var unsortedFileHolder = _service.LoadFileAs("sorted.dat", DatLayoutKind.NotOrdered); + + Assert.Equal(FileSystem.Path.GetFullPath("sorted.dat"), unsortedFileHolder.FilePath); + Assert.Equal(FileSystem.Path.GetFullPath("sorted.dat"), unsortedFileHolder.FileInformation.FilePath); + // Entries are still sorted, but the key sort oder was adjusted + Assert.Equal(DatLayoutKind.NotOrdered, unsortedFileHolder.Content.Layout); + Assert.Equal(DatTestData.CreateSortedModel(), unsortedFileHolder.Content.ToList()); + } + + [Fact] + public void LoadFileAs_UnsortedAsSorted_Throws() + { + var unsortedBinary = DatTestData.CreateUnsortedBinary(); + + FileSystem.Initialize() + .WithFile("unsorted.dat").Which(a => a.HasBytesContent(unsortedBinary.Bytes)); + + Assert.Throws(() => + _service.LoadFileAs("unsorted.dat", DatLayoutKind.OrderedByCrc32)); + } + + [Fact] + public void LoadStore_Sorted() + { + FileSystem.Initialize(); + using (var fs = FileSystem.FileStream.New("MasterTextFile.dat", FileMode.Create)) + { + using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatServiceTest), "Files.mastertextfile_english.dat"); + stream.CopyTo(fs); + } + + var datFile = _service.LoadFile("MasterTextFile.dat").Content; + Assert.Equal(DatLayoutKind.OrderedByCrc32, datFile.Layout); + + using (var fs = FileSystem.FileStream.New("NewSorted.dat", FileMode.Create)) + _service.CreateDatBinary(fs, datFile, DatLayoutKind.OrderedByCrc32); + + var asUnsortedDatFile = _service.LoadFileAs("MasterTextFile.dat", DatLayoutKind.NotOrdered).Content; + Assert.Equal(DatLayoutKind.NotOrdered, asUnsortedDatFile.Layout); + + using (var fs = FileSystem.FileStream.New("NewUnsorted.dat", FileMode.Create)) + _service.CreateDatBinary(fs, asUnsortedDatFile, DatLayoutKind.NotOrdered); + + + using (var fs = FileSystem.FileStream.New("NewSorted.dat", FileMode.Create)) + _service.CreateDatBinary(fs, datFile, DatLayoutKind.OrderedByCrc32); + using (var fs = FileSystem.FileStream.New("NewUnsorted.dat", FileMode.Create)) + _service.CreateDatBinary(fs, asUnsortedDatFile, DatLayoutKind.OrderedByCrc32); + + var expectedBytes = FileSystem.File.ReadAllBytes("MasterTextFile.dat"); + var actualBytesSorted = FileSystem.File.ReadAllBytes("NewSorted.dat"); + var actualBytesUnsorted = FileSystem.File.ReadAllBytes("NewUnsorted.dat"); + Assert.Equal(expectedBytes, actualBytesSorted); + Assert.Equal(expectedBytes, actualBytesUnsorted); + } + + [Fact] + public void LoadStore_Unsorted() + { + FileSystem.Initialize(); + using (var fs = FileSystem.FileStream.New("Credits.dat", FileMode.Create)) + { + using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatServiceTest), "Files.creditstext_english.dat"); + stream.CopyTo(fs); + } + + var datFile = _service.LoadFile("Credits.dat").Content; + Assert.Equal(DatLayoutKind.NotOrdered, datFile.Layout); + + using (var fs = FileSystem.FileStream.New("New.dat", FileMode.Create)) + _service.CreateDatBinary(fs, datFile, DatLayoutKind.NotOrdered); + + var expectedBytes = FileSystem.File.ReadAllBytes("Credits.dat"); + var actualBytesSorted = FileSystem.File.ReadAllBytes("New.dat"); + Assert.Equal(expectedBytes, actualBytesSorted); + } + + [Fact] + public void LoadStore_UnsortedAsSortedThrows() + { + FileSystem.Initialize(); + using (var fs = FileSystem.FileStream.New("Credits.dat", FileMode.Create)) + { + using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatServiceTest), "Files.creditstext_english.dat"); + stream.CopyTo(fs); + } + + var datFile = _service.LoadFile("Credits.dat").Content; + + using (var fs = FileSystem.FileStream.New("New.dat", FileMode.Create)) + _service.CreateDatBinary(fs, datFile, DatLayoutKind.OrderedByCrc32); + + var creditBytes = FileSystem.File.ReadAllBytes("Credits.dat"); + var resortedBytes = FileSystem.File.ReadAllBytes("New.dat"); + Assert.NotEqual(creditBytes, resortedBytes); + + Assert.Throws(() => _service.LoadFileAs("Credits.dat", DatLayoutKind.OrderedByCrc32)); + } + + [Fact] + public void LoadFile_Empty() + { + FileSystem.Initialize(); + using (var fs = FileSystem.FileStream.New("Empty.dat", FileMode.Create)) + { + using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatServiceTest), "Files.Empty.dat"); + stream.CopyTo(fs); + } + + var model = _service.LoadFile("Empty.dat"); + Assert.Empty(model.Content); + + model = _service.LoadFile(FileSystem.File.OpenRead("Empty.dat")); + Assert.Empty(model.Content); + } + + [Fact] + public void LoadFile_EmptyKeyWithValue() + { + FileSystem.Initialize(); + using (var fs = FileSystem.FileStream.New("EmptyKeyWithValue.dat", FileMode.Create)) + { + using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatServiceTest), "Files.EmptyKeyWithValue.dat"); + stream.CopyTo(fs); + } + + var model = _service.LoadFile("EmptyKeyWithValue.dat"); + Assert.Single(model.Content); + Assert.True(model.Content.ContainsKey(string.Empty)); + + model = _service.LoadFile(FileSystem.File.OpenRead("EmptyKeyWithValue.dat")); + Assert.Single(model.Content); + Assert.True(model.Content.ContainsKey(string.Empty)); + } + + [Fact] + public void LoadFile_Sorted_TwoEntriesDuplicate() + { + FileSystem.Initialize(); + using (var fs = FileSystem.FileStream.New("Sorted_TwoEntriesDuplicate.dat", FileMode.Create)) + { + using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatServiceTest), "Files.Sorted_TwoEntriesDuplicate.dat"); + stream.CopyTo(fs); + } + + var model = _service.LoadFile("Sorted_TwoEntriesDuplicate.dat"); + Assert.Equal(2, model.Content.Count); + Assert.Single(model.Content.Keys); + + model = _service.LoadFile(FileSystem.File.OpenRead("Sorted_TwoEntriesDuplicate.dat")); + Assert.Equal(2, model.Content.Count); + Assert.Single(model.Content.Keys); + } + + + [Fact] + public void LoadModifyCreate() + { + FileSystem.Initialize(); + using (var fs = FileSystem.FileStream.New("Index_WithDuplicates.dat", FileMode.Create)) + { + using var stream = TestingHelpers.GetEmbeddedResource(typeof(DatServiceTest), "Files.Index_WithDuplicates.dat"); + stream.CopyTo(fs); + } + + var model = _service.LoadFileAs("Index_WithDuplicates.dat", DatLayoutKind.NotOrdered).Content; + Assert.Equal(DatLayoutKind.NotOrdered, model.Layout); + var modelService = ServiceProvider.GetRequiredService(); + Assert.True(modelService.GetDuplicateEntries(model).Any()); + var withoutDups = modelService.RemoveDuplicates(model); + Assert.False(modelService.GetDuplicateEntries(withoutDups).Any()); + var sorted = modelService.SortModel(withoutDups); + Assert.Equal(DatLayoutKind.OrderedByCrc32, sorted.Layout); + + using (var fs = FileSystem.FileStream.New("newSorted.dat", FileMode.Create)) + _service.CreateDatBinary(fs, sorted, DatLayoutKind.OrderedByCrc32); + } + + [Fact] + public void LoadModel() + { + var sorted = DatTestData.CreateSortedBinary().Bytes; + foreach (var model in new[] + { + _service.LoadModel(new MemoryStream(sorted)), + _service.LoadModel(sorted), + _service.LoadModel(sorted.AsSpan()) + }) + { + Assert.Equal(DatLayoutKind.OrderedByCrc32, model.Layout); + Assert.Equal(DatTestData.CreateSortedModel(), model.ToList()); + } + + var unsorted = DatTestData.CreateUnsortedBinary().Bytes; + foreach (var model in new[] + { + _service.LoadModel(new MemoryStream(unsorted)), + _service.LoadModel(unsorted), + _service.LoadModel(unsorted.AsSpan()) + }) + { + Assert.Equal(DatLayoutKind.NotOrdered, model.Layout); + Assert.Equal(DatTestData.CreateUnsortedModel(), model.ToList()); + } + } + + [Fact] + public void LoadModelAs_SortedAsUnsorted() + { + var sorted = DatTestData.CreateSortedBinary().Bytes; + foreach (var model in new[] + { + _service.LoadModelAs(new MemoryStream(sorted), DatLayoutKind.NotOrdered), + _service.LoadModelAs(sorted, DatLayoutKind.NotOrdered), + _service.LoadModelAs(sorted.AsSpan(), DatLayoutKind.NotOrdered) + }) + { + // Entries are still sorted, but the key sort order was adjusted. + Assert.Equal(DatLayoutKind.NotOrdered, model.Layout); + Assert.Equal(DatTestData.CreateSortedModel(), model.ToList()); + } + } + + [Fact] + public void LoadModelAs_UnsortedAsSorted_Throws() + { + var unsorted = DatTestData.CreateUnsortedBinary().Bytes; + + Assert.Throws(() => _service.LoadModelAs(new MemoryStream(unsorted), DatLayoutKind.OrderedByCrc32)); + Assert.Throws(() => _service.LoadModelAs(unsorted, DatLayoutKind.OrderedByCrc32)); + Assert.Throws(() => _service.LoadModelAs(unsorted.AsSpan(), DatLayoutKind.OrderedByCrc32)); + } + + [Fact] + public void GetDatLayoutKind_FromMemory_MatchesExpected() + { + var sorted = DatTestData.CreateSortedBinary().Bytes; + var unsorted = DatTestData.CreateUnsortedBinary().Bytes; + + Assert.Equal(DatLayoutKind.OrderedByCrc32, _service.GetDatLayoutKind(new MemoryStream(sorted))); + Assert.Equal(DatLayoutKind.OrderedByCrc32, _service.GetDatLayoutKind(sorted)); + Assert.Equal(DatLayoutKind.OrderedByCrc32, _service.GetDatLayoutKind(sorted.AsSpan())); + + Assert.Equal(DatLayoutKind.NotOrdered, _service.GetDatLayoutKind(new MemoryStream(unsorted))); + Assert.Equal(DatLayoutKind.NotOrdered, _service.GetDatLayoutKind(unsorted)); + Assert.Equal(DatLayoutKind.NotOrdered, _service.GetDatLayoutKind(unsorted.AsSpan())); + } + + [Fact] + public void LoadModel_NonSeekableStream() + { + using var nonSeekable = new NonSeekableReadStream(DatTestData.CreateUnsortedBinary().Bytes); + + var model = _service.LoadModel(nonSeekable); + + Assert.Equal(DatLayoutKind.NotOrdered, model.Layout); + Assert.Equal(DatTestData.CreateUnsortedModel(), model.ToList()); + } + + [Fact] + public void LoadModel_NullArgs_Throws() + { + Assert.Throws(() => _service.LoadModel((Stream)null!)); + Assert.Throws(() => _service.LoadModel((byte[])null!)); + Assert.Throws(() => _service.LoadModelAs((Stream)null!, DatLayoutKind.NotOrdered)); + Assert.Throws(() => _service.LoadModelAs((byte[])null!, DatLayoutKind.NotOrdered)); + Assert.Throws(() => _service.GetDatLayoutKind((Stream)null!)); + Assert.Throws(() => _service.GetDatLayoutKind((byte[])null!)); + } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/ModelService/DatModelServiceTest.RemoveDuplicates.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/ModelService/DatModelServiceTest.RemoveDuplicates.cs index ca7bc61e..7015f072 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/ModelService/DatModelServiceTest.RemoveDuplicates.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/ModelService/DatModelServiceTest.RemoveDuplicates.cs @@ -1,9 +1,8 @@ -using System; +using System; using System.Collections.Generic; using PG.Commons.Hashing; using PG.Commons.Utilities; using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; using Xunit; namespace PG.StarWarsGame.Files.DAT.Test.Services; @@ -21,7 +20,7 @@ public void RemoveDuplicates_Throws() public void RemoveDuplicates(IList entries, IList expected) { var model = CreateModel(entries); - if (model.KeySortOrder == DatFileType.OrderedByCrc32) + if (model.Layout == DatLayoutKind.OrderedByCrc32) expected = Crc32Utilities.SortByCrc32(expected); var newModel = Service.RemoveDuplicates(model); diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/ModelService/DatModelServiceTest.SortModel.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/ModelService/DatModelServiceTest.SortModel.cs index 603b6ab3..fc937801 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/ModelService/DatModelServiceTest.SortModel.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/Services/ModelService/DatModelServiceTest.SortModel.cs @@ -1,9 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using PG.Commons.Hashing; using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; using Xunit; namespace PG.StarWarsGame.Files.DAT.Test.Services; @@ -22,11 +21,11 @@ public void SortModel(IList entries, IList expec { var model = CreateModel(entries); var sorted = Service.SortModel(model); - Assert.Equal(DatFileType.OrderedByCrc32, sorted.KeySortOrder); + Assert.Equal(DatLayoutKind.OrderedByCrc32, sorted.Layout); Assert.Equal(expectedList, sorted.ToList()); var sortedMock = Service.SortModel(new UnsortedDatModel(entries)); - Assert.Equal(DatFileType.OrderedByCrc32, sortedMock.KeySortOrder); + Assert.Equal(DatLayoutKind.OrderedByCrc32, sortedMock.Layout); Assert.Equal(expectedList, sortedMock.ToList()); } diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Converters/DatBinaryConverter.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Converters/DatBinaryConverter.cs index a13e4998..5446c5df 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Converters/DatBinaryConverter.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Converters/DatBinaryConverter.cs @@ -9,7 +9,6 @@ using PG.StarWarsGame.Files.Binary; using PG.StarWarsGame.Files.DAT.Binary.Metadata; using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; namespace PG.StarWarsGame.Files.DAT.Binary; @@ -52,7 +51,7 @@ public DatBinaryFile ModelToBinary(IDatModel model) lastCrc = keyChecksum; } - if (model.KeySortOrder == DatFileType.OrderedByCrc32 && !isSorted) + if (model.Layout == DatLayoutKind.OrderedByCrc32 && !isSorted) throw new ArgumentException("MasterTextModel must be sorted.", nameof(model)); return new DatBinaryFile(header, new BinaryTable(indexRecords), new BinaryTable(values), new BinaryTable(keys)); diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/DatFileConstants.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/DatFileConstants.cs index a8ce00f0..ba0fba36 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/DatFileConstants.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/DatFileConstants.cs @@ -11,7 +11,7 @@ namespace PG.StarWarsGame.Files.DAT.Binary; public static class DatFileConstants { /// - /// Returns the text key encoding, which is ASCII. + /// Represents the text key encoding, which is ASCII. /// public static readonly Encoding TextKeyEncoding = Encoding.ASCII; @@ -21,7 +21,7 @@ public static class DatFileConstants internal static readonly Encoding TextKeyEncoding_Latin1 = Encoding.GetEncoding(28591); /// - /// Returns the text value encoding, which is 16bit UTF Little Endian. + /// Represents the text value encoding, which is 16bit UTF Little Endian. /// public static readonly Encoding TextValueEncoding = Encoding.Unicode; } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Reader/DatFileReader.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Reader/DatFileReader.cs index cab15465..fe40635c 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Reader/DatFileReader.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Reader/DatFileReader.cs @@ -10,7 +10,7 @@ using PG.Commons.Services; using PG.StarWarsGame.Files.Binary; using PG.StarWarsGame.Files.DAT.Binary.Metadata; -using PG.StarWarsGame.Files.DAT.Files; +using PG.StarWarsGame.Files.DAT.Data; namespace PG.StarWarsGame.Files.DAT.Binary; @@ -71,7 +71,7 @@ public DatBinaryFile ReadBinary(Stream byteStream) } } - public DatFileType PeekFileType(Stream byteStream) + public DatLayoutKind PeekLayout(Stream byteStream) { if (byteStream == null) throw new ArgumentNullException(nameof(byteStream)); @@ -89,7 +89,7 @@ public DatFileType PeekFileType(Stream byteStream) var currentCrc = new Crc32(reader.ReadUInt32()); if (currentCrc < lastCrc) - return DatFileType.NotOrdered; + return DatLayoutKind.NotOrdered; reader.ReadUInt32(); // Value Length reader.ReadUInt32(); // Key Length @@ -97,7 +97,7 @@ public DatFileType PeekFileType(Stream byteStream) lastCrc = currentCrc; } - return DatFileType.OrderedByCrc32; + return DatLayoutKind.OrderedByCrc32; } catch (EndOfStreamException) { diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Reader/IDatFileReader.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Reader/IDatFileReader.cs index 59baee9e..a4bdb7b1 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Reader/IDatFileReader.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Binary/Reader/IDatFileReader.cs @@ -4,11 +4,11 @@ using System.IO; using PG.StarWarsGame.Files.Binary.File; using PG.StarWarsGame.Files.DAT.Binary.Metadata; -using PG.StarWarsGame.Files.DAT.Files; +using PG.StarWarsGame.Files.DAT.Data; namespace PG.StarWarsGame.Files.DAT.Binary; internal interface IDatFileReader : IBinaryFileReader { - DatFileType PeekFileType(Stream byteStream); + DatLayoutKind PeekLayout(Stream byteStream); } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/DatServiceContribution.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/DatServiceContribution.cs index 7dd1ad6a..e2bdc3fb 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/DatServiceContribution.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/DatServiceContribution.cs @@ -19,7 +19,7 @@ public static class DatServiceContribution public static void SupportDAT(this IServiceCollection serviceCollection) { serviceCollection - .AddSingleton(sp => new DatFileService(sp)) + .AddSingleton(sp => new DatService(sp)) .AddSingleton(sp => new DatModelService(sp)) .AddTransient(sp => new DatFileReader(sp)) .AddTransient(sp => new DatBinaryConverter(sp)); diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/ConstructingDatModel.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/ConstructingDatModel.cs index 741a71fc..47f6d153 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/ConstructingDatModel.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/ConstructingDatModel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; @@ -9,17 +9,16 @@ using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; using PG.Commons.Utilities; -using PG.StarWarsGame.Files.DAT.Files; namespace PG.StarWarsGame.Files.DAT.Data; -internal class ConstructingDatModel(IEnumerable entries, DatFileType fileType) : IDatModel +internal class ConstructingDatModel(IEnumerable entries, DatLayoutKind fileType) : IDatModel { - private readonly IList _entries = fileType == DatFileType.NotOrdered ? entries.ToList() : Crc32Utilities.SortByCrc32(entries); + private readonly IList _entries = fileType == DatLayoutKind.NotOrdered ? entries.ToList() : Crc32Utilities.SortByCrc32(entries); public int Count => _entries.Count; - public DatFileType KeySortOrder { get; } = fileType; + public DatLayoutKind Layout { get; } = fileType; public DatStringEntry this[int index] => _entries[index]; diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatLayoutKind.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatLayoutKind.cs new file mode 100644 index 00000000..15bc1a0c --- /dev/null +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatLayoutKind.cs @@ -0,0 +1,28 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using PG.Commons.Hashing; + +namespace PG.StarWarsGame.Files.DAT.Data; + +/// +/// Represents the available entry ordering of a Petroglyph Localized String Table (.DAT). +/// +public enum DatLayoutKind +{ + /// + /// Represents a sorted DAT. This only allows for unique text keys and is expected to + /// be sorted on the text key's in an ascending manner. Any + /// MasterText DAT is of this archive type. + /// + OrderedByCrc32, + + /// + /// Represents an unsorted .DAT file. This file type allows (and encourages) non-unique text + /// keys, as this format is used for the CreditsText DAT, where the keys + /// act as formatting information/instructions rather than queriable keys. The order of the + /// entries is also equivalent to the order of the credits displayed on screen, so the insert + /// order of the key-value pairs is important and should be retained. + /// + NotOrdered +} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatModel.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatModel.cs index 5bb593b5..305f7b20 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatModel.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatModel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; @@ -9,7 +9,6 @@ using System.Linq; using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; -using PG.StarWarsGame.Files.DAT.Files; namespace PG.StarWarsGame.Files.DAT.Data; @@ -29,7 +28,7 @@ internal abstract class DatModel : IDatModel public ISet CrcKeys => new HashSet(_firstCrcKeyValueDictionary.Keys); - public abstract DatFileType KeySortOrder { get; } + public abstract DatLayoutKind Layout { get; } protected DatModel(IEnumerable entries) { diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatStringEntry.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatStringEntry.cs index e248b1bb..3d11b0bc 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatStringEntry.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/DatStringEntry.cs @@ -10,7 +10,7 @@ namespace PG.StarWarsGame.Files.DAT.Data; /// -/// A key-value pair that can be stored in a DAT file. +/// Represents a key-value pair that can be stored in a DAT file. /// /// /// Equality is based on , and . @@ -39,7 +39,7 @@ namespace PG.StarWarsGame.Files.DAT.Data; public Crc32 Crc32 { get; } /// - /// Initializes a new instance of the structure with a specified key, checksum and value. + /// Initializes a new instance of the struct. /// /// The entry's key. /// The CRC32 checksum of the key. @@ -53,8 +53,7 @@ public DatStringEntry(string key, Crc32 keyChecksum, string value) : this(key, k } /// - /// Initializes a new instance of the structure with a specified key, checksum, - /// value and the original extended ASCII key. + /// Initializes a new instance of the struct. /// /// The entry's key. /// The CRC32 checksum of the key. diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/IDatModel.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/IDatModel.cs index 8e4426a6..3f2434cc 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/IDatModel.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/IDatModel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; @@ -6,12 +6,11 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using PG.Commons.Hashing; -using PG.StarWarsGame.Files.DAT.Files; namespace PG.StarWarsGame.Files.DAT.Data; /// -/// A list of key-value string entries which are used by Petroglyph games to store in-game text. +/// Represents a list of key-value string entries that are used by Petroglyph games to store in-game text. /// public interface IDatModel : IReadOnlyList { @@ -26,13 +25,13 @@ public interface IDatModel : IReadOnlyList ISet CrcKeys { get; } /// - /// Gets a value indicating how keys are organized in the . + /// Gets a value that indicates how keys are organized in the . /// /// /// Game credit models may also be sorted by pure chance. /// So this property does not provide a safe way to determine the semantics of this model. /// - public DatFileType KeySortOrder { get; } + public DatLayoutKind Layout { get; } /// /// Determines whether the contains the specified key. @@ -58,7 +57,7 @@ public interface IDatModel : IReadOnlyList /// /// When this method returns, contains the value associated with the specified key, /// if the key is found; otherwise, the default value for the type of the parameter. - /// This parameter is passed uninitialized. + /// This parameter is treated as uninitialized. /// /// if the contains an element with the specified key; otherwise, . /// is . @@ -75,14 +74,14 @@ public interface IDatModel : IReadOnlyList /// Gets a list of entries with the matching CRC32 checksum or an empty list, if the CRC32 checksum is not found. /// /// The key to match. - /// List of matching entries. + /// A list of the matching entries. ImmutableFrugalList EntriesWithCrc(Crc32 key); /// /// Gets a list of entries with the matching key or an empty list, if the key is not found. /// /// The key to match. - /// List of matching entries. + /// A list of the matching entries. /// is . ImmutableFrugalList EntriesWithKey(string key); @@ -102,7 +101,7 @@ public interface IDatModel : IReadOnlyList /// /// When this method returns, contains the value associated with the specified key, /// if the key is found; otherwise, the default value for the type of the parameter. - /// This parameter is passed uninitialized. + /// This parameter is treated as uninitialized. /// /// if the contains an element with the specified key; otherwise, . bool TryGetValue(Crc32 key, [NotNullWhen(true)] out string? value); diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/SortedDatModel.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/SortedDatModel.cs index d9bd6788..22a63958 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/SortedDatModel.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/SortedDatModel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; @@ -6,7 +6,6 @@ using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; using PG.Commons.Utilities; -using PG.StarWarsGame.Files.DAT.Files; namespace PG.StarWarsGame.Files.DAT.Data; @@ -14,8 +13,7 @@ internal sealed class SortedDatModel : DatModel, ISortedDatModel { private readonly IReadOnlyDictionary _crcToIndexMap; - public override DatFileType KeySortOrder => DatFileType.OrderedByCrc32; - + public override DatLayoutKind Layout => DatLayoutKind.OrderedByCrc32; public SortedDatModel(IEnumerable entries) : base(entries) { diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/UnsortedDatModel.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/UnsortedDatModel.cs index ea09095e..e328c579 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/UnsortedDatModel.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Data/UnsortedDatModel.cs @@ -1,17 +1,16 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System.Collections.Generic; using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; using PG.Commons.Utilities; -using PG.StarWarsGame.Files.DAT.Files; namespace PG.StarWarsGame.Files.DAT.Data; internal sealed class UnsortedDatModel(IEnumerable entries) : DatModel(entries), IUnsortedDatModel { - public override DatFileType KeySortOrder => DatFileType.NotOrdered; + public override DatLayoutKind Layout => DatLayoutKind.NotOrdered; public override ImmutableFrugalList EntriesWithCrc(Crc32 key) { diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Files/DatFileType.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Files/DatFileType.cs deleted file mode 100644 index 021d2d29..00000000 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Files/DatFileType.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using PG.Commons.Hashing; - -namespace PG.StarWarsGame.Files.DAT.Files; - -/// -/// Represents the available entry ordering of a Petroglyph translation data file archive as defined -/// in the .DAT file specification. -/// -public enum DatFileType -{ - /// - /// Represents a sorted .DAT file. This file only allows for unique text keys and is expected to - /// be sorted on the text key's in an ascending manner. Any - /// mastertextfile .DAT file is of this archive type. - /// - OrderedByCrc32, - - /// - /// Represents an unsorted .DAT file. This file type allows (and encourages) non-unique text - /// keys, as this format is used for the creditstextfile .DAT file, where the keys - /// act as formatting information/instructions rather than queriable keys. The order of the - /// entries is also equivalent to the order of the credits displayed on screen, so the insert - /// order of the key-value pairs is important and should be retained. - /// - NotOrdered -} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/AddEntryResult.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/AddEntryResult.cs index 274d7cb1..e40b4e06 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/AddEntryResult.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/AddEntryResult.cs @@ -9,12 +9,12 @@ namespace PG.StarWarsGame.Files.DAT.Services.Builder; /// -/// Status information whether an entry was added to an . +/// Represents status information about whether an entry was added to an . /// public readonly struct AddEntryResult { /// - /// Gets whether the entry was added or not. + /// Gets a value that indicates whether the entry was added. /// [MemberNotNullWhen(true, nameof(AddedEntry))] public bool Added => Status is AddEntryState.Added or AddEntryState.AddedDuplicate && AddedEntry is not null; @@ -25,23 +25,23 @@ public readonly struct AddEntryResult public AddEntryState Status { get; } /// - /// Indicates whether a previous entry was overwritten. + /// Gets a value that indicates whether a previous entry was overwritten. /// [MemberNotNullWhen(true, nameof(OverwrittenEntry))] public bool WasOverwrite => OverwrittenEntry is not null; /// - /// The entry which was added or if no entry was added. + /// Gets the entry that was added, or if no entry was added. /// public DatStringEntry? AddedEntry { get; } /// - /// The entry which was overwritten or if no entry was overwritten. + /// Gets the entry that was overwritten, or if no entry was overwritten. /// public DatStringEntry? OverwrittenEntry { get; } /// - /// A user readable message why the entry was not added. if the entry was added successfully or no message was provided. + /// Gets a user-readable message describing why the entry was not added, or if the entry was added successfully or no message was provided. /// [ExcludeFromCodeCoverage] public string? Message { get; } diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/EmpireAtWarCreditsTextBuilder.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/EmpireAtWarCreditsTextBuilder.cs index ec0cdde8..e94a447c 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/EmpireAtWarCreditsTextBuilder.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/EmpireAtWarCreditsTextBuilder.cs @@ -1,20 +1,20 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; -using PG.StarWarsGame.Files.DAT.Files; +using PG.StarWarsGame.Files.DAT.Data; namespace PG.StarWarsGame.Files.DAT.Services.Builder; /// -/// A for building Credits DAT files used by the +/// Represents an for building Credits DAT files used by the /// Petroglyph game Star Wars: Empire at War and its extension Empire at War: Forces of Corruption. /// public sealed class EmpireAtWarCreditsTextBuilder : PetroglyphStarWarsGameDatBuilder { /// - /// An instance of this class always returns . - public override DatFileType TargetKeySortOrder => DatFileType.NotOrdered; + /// An instance of this class always returns . + public override DatLayoutKind TargetLayout => DatLayoutKind.NotOrdered; /// /// Initializes a new instance of the class. diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/EmpireAtWarMasterTextBuilder.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/EmpireAtWarMasterTextBuilder.cs index f914dd72..72c4bf73 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/EmpireAtWarMasterTextBuilder.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/EmpireAtWarMasterTextBuilder.cs @@ -1,25 +1,27 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; -using PG.StarWarsGame.Files.DAT.Files; +using PG.StarWarsGame.Files.DAT.Data; namespace PG.StarWarsGame.Files.DAT.Services.Builder; /// -/// A for building MasterText DAT files used by the +/// Represents an for building MasterText DAT files used by the /// Petroglyph game Star Wars: Empire at War and its extension Empire at War: Forces of Corruption. /// public sealed class EmpireAtWarMasterTextBuilder : PetroglyphStarWarsGameDatBuilder { /// - /// An instance of this class always returns . - public override DatFileType TargetKeySortOrder => DatFileType.OrderedByCrc32; + /// An instance of this class always returns . + public override DatLayoutKind TargetLayout => DatLayoutKind.OrderedByCrc32; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// Specifies how the build treats duplicates. + /// + /// to overwrite an existing entry when a duplicate key is added; otherwise, . + /// /// The service provider. public EmpireAtWarMasterTextBuilder(bool overwriteDuplicates, IServiceProvider services) : base(overwriteDuplicates ? BuilderOverrideKind.Overwrite : BuilderOverrideKind.NoOverwrite, services) diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/PetroglyphStarWarsGameDatBuilder.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/PetroglyphStarWarsGameDatBuilder.cs index 8788b3df..7b1618d0 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/PetroglyphStarWarsGameDatBuilder.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Builders/PetroglyphStarWarsGameDatBuilder.cs @@ -11,7 +11,7 @@ namespace PG.StarWarsGame.Files.DAT.Services.Builder; /// -/// Base class for an used by the Petroglyph game Star Wars: Empire at War and its extension Empire at War: Forces of Corruption. +/// Provides a base class for an used by the Petroglyph game Star Wars: Empire at War and its extension Empire at War: Forces of Corruption. /// public abstract class PetroglyphStarWarsGameDatBuilder : DatBuilderBase { diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/DatBuilderBase.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/DatBuilderBase.cs index 15cd16d8..9bfa58f0 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/DatBuilderBase.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/DatBuilderBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; @@ -18,7 +18,7 @@ namespace PG.StarWarsGame.Files.DAT.Services.Builder; /// -/// Base class for a service providing the fundamental implementations. +/// Provides a base class for a service with the fundamental implementations. /// public abstract class DatBuilderBase : FileBuilderBase, DatFileInformation>, IDatBuilder { @@ -28,10 +28,10 @@ public abstract class DatBuilderBase : FileBuilderBase public sealed override IReadOnlyList BuilderData => - TargetKeySortOrder == DatFileType.OrderedByCrc32 ? SortedEntries : Entries; + TargetLayout == DatLayoutKind.OrderedByCrc32 ? SortedEntries : Entries; /// - public abstract DatFileType TargetKeySortOrder { get; } + public abstract DatLayoutKind TargetLayout { get; } /// public BuilderOverrideKind KeyOverwriteBehavior { get; } @@ -137,7 +137,7 @@ public bool IsKeyValid(string? key) /// public IDatModel BuildModel() { - if (TargetKeySortOrder == DatFileType.OrderedByCrc32) + if (TargetLayout == DatLayoutKind.OrderedByCrc32) return new SortedDatModel(BuilderData); return new UnsortedDatModel(BuilderData); } @@ -145,8 +145,8 @@ public IDatModel BuildModel() /// protected sealed override void BuildFileCore(FileSystemStream fileStream, DatFileInformation fileInformation, IReadOnlyList data) { - var datService = Services.GetRequiredService(); - datService.CreateDatFile(fileStream, data, TargetKeySortOrder); + var datService = Services.GetRequiredService(); + datService.CreateDatBinary(fileStream, data, TargetLayout); } /// diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/IDatBuilder.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/IDatBuilder.cs index ea174ea2..4b1c93cb 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/IDatBuilder.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/IDatBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; @@ -11,17 +11,17 @@ namespace PG.StarWarsGame.Files.DAT.Services.Builder; /// -/// Service to create DAT files ensuring validation of keys. +/// Provides methods to create DAT files while ensuring validation of keys. /// public interface IDatBuilder : IFileBuilder, DatFileInformation> { /// - /// Gets the key sort order of the DATs created by the . + /// Gets the layout of the DAT created by the . /// - DatFileType TargetKeySortOrder { get; } + DatLayoutKind TargetLayout { get; } /// - /// Gets a value indicating how the treats an already existing key. + /// Gets a value that indicates how the treats an already existing key. /// BuilderOverrideKind KeyOverwriteBehavior { get; } @@ -78,8 +78,8 @@ public interface IDatBuilder : IFileBuilder, DatFi /// /// Checks whether the specified key is valid for this . /// - /// The key to validate - /// if the passed file information are valid; otherwise, . + /// The key to validate. + /// if is valid for this ; otherwise, . bool IsKeyValid(string? key); /// diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/EmpireAtWarKeyValidator.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/EmpireAtWarKeyValidator.cs index ab784de1..efc019ca 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/EmpireAtWarKeyValidator.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/EmpireAtWarKeyValidator.cs @@ -6,12 +6,12 @@ namespace PG.StarWarsGame.Files.DAT.Services.Builder.Validation; /// -/// Validator for DAT keys using the rules of a Petroglyph Star Wars game. +/// Validates DAT keys using the rules of a Petroglyph Star Wars game. /// public sealed class EmpireAtWarKeyValidator : IDatKeyValidator { /// - /// Returns a singleton instance of the . + /// Gets the singleton instance of the class. /// public static readonly EmpireAtWarKeyValidator Instance = new(); diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/IDatKeyValidator.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/IDatKeyValidator.cs index d7580e2f..fd1002bb 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/IDatKeyValidator.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/IDatKeyValidator.cs @@ -6,7 +6,7 @@ namespace PG.StarWarsGame.Files.DAT.Services.Builder.Validation; /// -/// A validator that checks whether a string can be used as a key for DAT files. +/// Defines a validator that checks whether a string can be used as a key for DAT files. /// public interface IDatKeyValidator { diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/NotNullKeyValidator.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/NotNullKeyValidator.cs index 502f751f..5f049252 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/NotNullKeyValidator.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Builder/Validation/NotNullKeyValidator.cs @@ -6,7 +6,7 @@ namespace PG.StarWarsGame.Files.DAT.Services.Builder.Validation; /// -/// A validator that checks the passed key is not . +/// Validates that the passed key is not . /// public sealed class NotNullKeyValidator : IDatKeyValidator { diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatFileService.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatFileService.cs deleted file mode 100644 index 3b3c45c5..00000000 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatFileService.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Abstractions; -using PG.StarWarsGame.Files.Binary; -using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; - -namespace PG.StarWarsGame.Files.DAT.Services; - -/// -/// A service to load and create Petroglyph .DAT files -/// -public interface IDatFileService -{ - /// - /// Takes a collection of s and packs them into a DAT file. - /// - /// The file stream to write the DAT content to. - /// A list of key-value-pairs to be stored in the DAT file. - /// - /// Determines whether the output file's entries will be ordered (usually used for - /// mastertextfile_LANGUAGE.dat) or the sort-order of the provided entries will be preserved (usually used - /// for creditstextfile_LANGUAGE.dat). - /// - /// or is . - /// The DAT file could not be created. - void CreateDatFile(FileSystemStream fileStream, IEnumerable entries, DatFileType fileType); - - /// - /// Loads a *.DAT file from the provided path into a - /// - /// The path to the DAT file. - /// The loaded DAT file. - /// is . - /// is not a DAT archive. - /// is not found. - /// is empty. - IDatFile Load(string filePath); - - /// - /// Loads a *.DAT file from the provided path and - /// - /// The path to the DAT file. - /// The requested type of the DAT model. - /// The loaded DAT file - /// is not compatible to the loaded file. - /// is not a DAT archive. - /// is not found. - /// is . - /// is empty. - IDatFile LoadAs(string filePath, DatFileType requestedFileType); - - /// - /// Loads a *.DAT file from the provided path into a - /// - /// The DAT file stream. - /// The loaded DAT file. - /// is . - /// is not a DAT archive. - /// is not readable or seekable. - IDatFile Load(FileSystemStream fileStream); - - /// - /// Loads a *.DAT file from the provided path and - /// - /// The DAT file stream. - /// The requested type of the DAT model. - /// The loaded DAT file - /// is not compatible to the loaded file. - /// is not a DAT archive. - /// is . - /// is not readable or seekable. - IDatFile LoadAs(FileSystemStream fileStream, DatFileType requestedFileType); - - /// - /// Determines whether a provided DAT file is or . - /// - /// - /// For empty or single-entry DAT files this method returns - /// - /// The path to the DAT file. - /// The loaded DAT file - /// is . - DatFileType GetDatFileType(string filePath); -} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatModelService.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatModelService.cs index 013b0216..8874ff58 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatModelService.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatModelService.cs @@ -8,7 +8,7 @@ namespace PG.StarWarsGame.Files.DAT.Services; /// -/// A service to work with DAT models. +/// Provides operations for working with DAT models. /// public interface IDatModelService { @@ -76,8 +76,10 @@ public interface IDatModelService /// /// The base model. /// The model to merge into . - /// When this method returns, the collection contains keys that got added or overwritten. - /// Specifies how to treat existing keys. + /// + /// When this method returns, contains the keys that got added or overwritten. This parameter is treated as uninitialized. + /// + /// One of the enumeration values that specifies how to treat existing keys. /// The merged model. /// or is . /// or is not sorted. @@ -89,8 +91,10 @@ IDatModel MergeSorted(IDatModel baseDatModel, IDatModel datToMerge, out ICollect /// /// The base model. /// The model to merge into . - /// When this method returns, the collection contains keys that got added or overwritten. - /// Specifies how to treat existing keys. + /// + /// When this method returns, contains the keys that got added or overwritten. This parameter is treated as uninitialized. + /// + /// One of the enumeration values that specifies how to treat existing keys. /// The merged model. /// or is . /// or is not unsorted. diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatService.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatService.cs new file mode 100644 index 00000000..50fcfd61 --- /dev/null +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/IDatService.cs @@ -0,0 +1,180 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using PG.StarWarsGame.Files.Binary; +using PG.StarWarsGame.Files.DAT.Data; +using PG.StarWarsGame.Files.DAT.Files; + +namespace PG.StarWarsGame.Files.DAT.Services; + +/// +/// Provides methods to load and create Petroglyph Localized String Tables (.DAT). +/// +public interface IDatService +{ + /// + /// Creates a binary DAT from a collection of string entries and writes it to a specified stream. + /// + /// The stream to write the DAT content to. + /// A list of key-value-pairs to be stored in the DAT file. + /// + /// Determines whether the output entries will be ordered (usually used for + /// mastertextfile_LANGUAGE.dat) or the sort-order of the provided entries will be preserved + /// (usually used for creditstextfile_LANGUAGE.dat). + /// + /// or is . + /// The DAT data could not be written. + void CreateDatBinary(Stream stream, IEnumerable entries, DatLayoutKind layout); + + /// + /// Loads a DAT file into a + /// + /// The path to the DAT file. + /// The loaded DAT file. + /// is . + /// is not a DAT archive. + /// is not found. + /// is empty. + IDatFile LoadFile(string filePath); + + /// + /// Loads a DAT file into a using the specified layout. + /// + /// The path to the DAT file. + /// The requested type of the DAT model. + /// The loaded DAT file + /// is not compatible to the loaded file. + /// is not a DAT archive. + /// is not found. + /// is . + /// is empty. + IDatFile LoadFileAs(string filePath, DatLayoutKind requestedLayout); + + /// + /// Loads a DAT file into a . + /// + /// The DAT file stream. + /// The loaded DAT file. + /// is . + /// is not a DAT archive. + /// is not readable or seekable. + IDatFile LoadFile(FileSystemStream fileStream); + + /// + /// Loads a DAT file into a using the specified layout. + /// + /// The DAT file stream. + /// The requested type of the DAT model. + /// The loaded DAT file + /// is not compatible to the loaded file. + /// is not a DAT archive. + /// is . + /// is not readable or seekable. + IDatFile LoadFileAs(FileSystemStream fileStream, DatLayoutKind requestedLayout); + + /// + /// Loads a DAT model from the specified stream. + /// + /// The DAT stream. + /// The loaded DAT model. + /// is . + /// is not a DAT archive. + IDatModel LoadModel(Stream stream); + + /// + /// Loads a DAT model from the specified byte array. + /// + /// The DAT bytes. + /// The loaded DAT model. + /// is . + /// is not a DAT archive. + IDatModel LoadModel(byte[] data); + + /// + /// Loads a DAT model from the specified read-only span of bytes. + /// + /// The DAT bytes. + /// The loaded DAT model. + /// is not a DAT archive. + IDatModel LoadModel(ReadOnlySpan data); + + /// + /// Loads a DAT model from the specified stream and layout kind. + /// + /// The DAT stream. + /// The requested layout of the DAT model. + /// The loaded DAT model + /// is not compatible to the loaded DAT. + /// is not a DAT archive. + /// is . + IDatModel LoadModelAs(Stream stream, DatLayoutKind requestedLayout); + + /// + /// Loads a DAT model from the specified bytes and type. + /// + /// The DAT bytes. + /// The requested layout of the DAT model. + /// The loaded DAT model + /// is not compatible to the loaded DAT. + /// is not a DAT archive. + /// is . + IDatModel LoadModelAs(byte[] data, DatLayoutKind requestedLayout); + + /// + /// Loads a DAT model from the specified read-only span of bytes and layout kind. + /// + /// The DAT bytes. + /// The requested layout of the DAT model. + /// The loaded DAT model + /// is not compatible to the loaded DAT. + /// is not a DAT archive. + IDatModel LoadModelAs(ReadOnlySpan data, DatLayoutKind requestedLayout); + + /// + /// Determines whether the specified DAT file is or . + /// + /// + /// For empty or single-entry DAT files this method returns + /// + /// The path to the DAT file. + /// The layout of the DAT. + /// is . + DatLayoutKind GetDatLayoutKind(string filePath); + + /// + /// Determines whether the specified DAT stream is or . + /// + /// + /// For empty or single-entry DAT data this method returns + /// + /// The DAT data stream. + /// The layout of the DAT. + /// is . + DatLayoutKind GetDatLayoutKind(Stream stream); + + /// + /// Determines whether the specified byte array is a DAT using the layout or . + /// + /// + /// For empty or single-entry DAT data this method returns + /// + /// The DAT data bytes. + /// The layout of the DAT. + /// is . + DatLayoutKind GetDatLayoutKind(byte[] data); + + /// + /// Determines whether the specified read-only span of bytes is a DAT using the layout + /// or . + /// + /// + /// For empty or single-entry DAT data this method returns + /// + /// The DAT bytes. + /// The layout of the DAT. + DatLayoutKind GetDatLayoutKind(ReadOnlySpan data); +} diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatFileService.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatFileService.cs deleted file mode 100644 index 15bcf21d..00000000 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatFileService.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Abstractions; -using PG.Commons.Services; -using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; -using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Files.DAT.Binary; - -namespace PG.StarWarsGame.Files.DAT.Services; - -internal class DatFileService(IServiceProvider services) : ServiceBase(services), IDatFileService -{ - public void CreateDatFile(FileSystemStream fileStream, IEnumerable entries, DatFileType datFileType) - { - if (fileStream is null) - throw new ArgumentNullException(nameof(fileStream)); - if (entries is null) - throw new ArgumentNullException(nameof(entries)); - - var datModel = new ConstructingDatModel(entries, datFileType); - - var datBinary = Services.GetRequiredService().ModelToBinary(datModel); - - datBinary.WriteTo(fileStream); - } - - public IDatFile Load(string filePath) - { - if (filePath == null) - throw new ArgumentNullException(nameof(filePath)); - using var fs = FileSystem.FileStream.New(filePath, FileMode.Open, FileAccess.Read); - var fileType = GetDatFileType(fs); - fs.Seek(0, SeekOrigin.Begin); - return LoadAs(fs, fileType); - } - - public IDatFile Load(FileSystemStream fileStream) - { - if (fileStream == null) - throw new ArgumentNullException(nameof(fileStream)); - var currentPos = fileStream.Position; - var fileType = GetDatFileType(fileStream); - fileStream.Seek(currentPos, SeekOrigin.Begin); - return LoadAs(fileStream, fileType); - } - - public IDatFile LoadAs(string filePath, DatFileType requestedFileType) - { - if (filePath == null) - throw new ArgumentNullException(nameof(filePath)); - using var fs = FileSystem.FileStream.New(filePath, FileMode.Open, FileAccess.Read); - return LoadAs(fs, requestedFileType); - } - - public IDatFile LoadAs(FileSystemStream fileStream, DatFileType requestedFileType) - { - if (fileStream == null) - throw new ArgumentNullException(nameof(fileStream)); - - var reader = Services.GetRequiredService(); - var datBinary = reader.ReadBinary(fileStream); - - var converter = Services.GetRequiredService(); - var datModel = converter.BinaryToModel(datBinary); - - if (requestedFileType == DatFileType.NotOrdered && datModel is ISortedDatModel sorted) - datModel = sorted.ToUnsortedModel(); - - if (requestedFileType == DatFileType.OrderedByCrc32 && datModel is IUnsortedDatModel) - throw new InvalidOperationException("Unsorted DAT file cannot be loaded as sorted DAT file"); - - var filePath = FileSystem.Path.GetFullPath(fileStream.Name); - var fileInfo = new DatFileInformation { FilePath = filePath }; - - return new DatFile(datModel, fileInfo, Services); - } - - - public DatFileType GetDatFileType(string filePath) - { - using var fs = FileSystem.FileStream.New(filePath, FileMode.Open, FileAccess.Read); - return GetDatFileType(fs); - } - - private DatFileType GetDatFileType(Stream fileStream) - { - return Services.GetRequiredService().PeekFileType(fileStream); - } -} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatModelService.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatModelService.cs index d9790dc1..f27f5318 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatModelService.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatModelService.cs @@ -10,7 +10,6 @@ using PG.Commons.Services; using PG.Commons.Utilities; using PG.StarWarsGame.Files.DAT.Data; -using PG.StarWarsGame.Files.DAT.Files; namespace PG.StarWarsGame.Files.DAT.Services; @@ -40,7 +39,7 @@ public IDatModel RemoveDuplicates(IDatModel datModel) var newEntries = new LinkedHashSet(datModel, CrcBasedEqualityComparer.Instance) .ToList(); - if (datModel.KeySortOrder == DatFileType.OrderedByCrc32) + if (datModel.Layout == DatLayoutKind.OrderedByCrc32) { newEntries = Crc32Utilities.SortByCrc32(newEntries); return new SortedDatModel(newEntries); @@ -82,9 +81,9 @@ public IDatModel MergeSorted(IDatModel baseDatModel, IDatModel datToMerge, if (datToMerge == null) throw new ArgumentNullException(nameof(datToMerge)); - if (baseDatModel.KeySortOrder != DatFileType.OrderedByCrc32) + if (baseDatModel.Layout != DatLayoutKind.OrderedByCrc32) throw new ArgumentException("DAT model not sorted.", nameof(baseDatModel)); - if (datToMerge.KeySortOrder != DatFileType.OrderedByCrc32) + if (datToMerge.Layout != DatLayoutKind.OrderedByCrc32) throw new ArgumentException("DAT model not sorted.", nameof(datToMerge)); var newEntries = baseDatModel.ToList(); @@ -130,9 +129,9 @@ public IDatModel MergeUnsorted(IDatModel baseDatModel, IDatModel datToMerge, out if (datToMerge == null) throw new ArgumentNullException(nameof(datToMerge)); - if (baseDatModel.KeySortOrder != DatFileType.NotOrdered) + if (baseDatModel.Layout != DatLayoutKind.NotOrdered) throw new ArgumentException("DAT model not unsorted.", nameof(baseDatModel)); - if (datToMerge.KeySortOrder != DatFileType.NotOrdered) + if (datToMerge.Layout != DatLayoutKind.NotOrdered) throw new ArgumentException("DAT model not unsorted.", nameof(datToMerge)); mergedKeys = new List(); diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatService.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatService.cs new file mode 100644 index 00000000..124d0153 --- /dev/null +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/Internal/DatService.cs @@ -0,0 +1,192 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using PG.Commons.Services; +using PG.StarWarsGame.Files.DAT.Data; +using PG.StarWarsGame.Files.DAT.Files; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Files.DAT.Binary; + +namespace PG.StarWarsGame.Files.DAT.Services; + +internal class DatService(IServiceProvider services) : ServiceBase(services), IDatService +{ + public void CreateDatBinary(Stream stream, IEnumerable entries, DatLayoutKind layout) + { + if (stream is null) + throw new ArgumentNullException(nameof(stream)); + if (entries is null) + throw new ArgumentNullException(nameof(entries)); + + var datModel = new ConstructingDatModel(entries, layout); + + var datBinary = Services.GetRequiredService().ModelToBinary(datModel); + + datBinary.WriteTo(stream); + } + + public IDatFile LoadFile(string filePath) + { + if (filePath == null) + throw new ArgumentNullException(nameof(filePath)); + using var fs = FileSystem.FileStream.New(filePath, FileMode.Open, FileAccess.Read); + var layout = GetDatLayoutKind(fs); + fs.Seek(0, SeekOrigin.Begin); + return LoadFileAs(fs, layout); + } + + public IDatFile LoadFile(FileSystemStream fileStream) + { + if (fileStream == null) + throw new ArgumentNullException(nameof(fileStream)); + var currentPos = fileStream.Position; + var layout = GetDatLayoutKind(fileStream); + fileStream.Seek(currentPos, SeekOrigin.Begin); + return LoadFileAs(fileStream, layout); + } + + public IDatFile LoadFileAs(string filePath, DatLayoutKind requestedLayout) + { + if (filePath == null) + throw new ArgumentNullException(nameof(filePath)); + using var fs = FileSystem.FileStream.New(filePath, FileMode.Open, FileAccess.Read); + return LoadFileAs(fs, requestedLayout); + } + + public IDatFile LoadFileAs(FileSystemStream fileStream, DatLayoutKind requestedLayout) + { + if (fileStream == null) + throw new ArgumentNullException(nameof(fileStream)); + + var datModel = ReadModel(fileStream, requestedLayout); + + var filePath = FileSystem.Path.GetFullPath(fileStream.Name); + var fileInfo = new DatFileInformation { FilePath = filePath }; + + return new DatFile(datModel, fileInfo, Services); + } + + public IDatModel LoadModel(Stream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + return LoadModelFromStream(stream, null); + } + + public IDatModel LoadModel(byte[] data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + using var stream = new MemoryStream(data, writable: false); + return LoadModel(stream); + } + + public IDatModel LoadModel(ReadOnlySpan data) + { + using var stream = new MemoryStream(data.ToArray(), writable: false); + return LoadModel(stream); + } + + public IDatModel LoadModelAs(Stream stream, DatLayoutKind requestedLayout) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + return LoadModelFromStream(stream, requestedLayout); + } + + public IDatModel LoadModelAs(byte[] data, DatLayoutKind requestedLayout) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + using var stream = new MemoryStream(data, writable: false); + return LoadModelAs(stream, requestedLayout); + } + + public IDatModel LoadModelAs(ReadOnlySpan data, DatLayoutKind requestedLayout) + { + using var stream = new MemoryStream(data.ToArray(), writable: false); + return LoadModelAs(stream, requestedLayout); + } + + public DatLayoutKind GetDatLayoutKind(string filePath) + { + if (filePath == null) + throw new ArgumentNullException(nameof(filePath)); + using var fs = FileSystem.FileStream.New(filePath, FileMode.Open, FileAccess.Read); + return GetDatLayoutKind(fs); + } + + public DatLayoutKind GetDatLayoutKind(Stream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + return Services.GetRequiredService().PeekLayout(stream); + } + + public DatLayoutKind GetDatLayoutKind(byte[] data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + using var stream = new MemoryStream(data, writable: false); + return GetDatLayoutKind(stream); + } + + public DatLayoutKind GetDatLayoutKind(ReadOnlySpan data) + { + using var stream = new MemoryStream(data.ToArray(), writable: false); + return GetDatLayoutKind(stream); + } + + private IDatModel LoadModelFromStream(Stream stream, DatLayoutKind? requestedLayout) + { + // Reading the model requires seeking back to the start after peeking the file type. If the source + // stream cannot seek, copy it into memory first so the in-memory load path can re-read it. + if (!stream.CanSeek) + { + using var seekableCopy = new MemoryStream(); + stream.CopyTo(seekableCopy); + seekableCopy.Position = 0; + return ReadModelFromSeekableStream(seekableCopy, requestedLayout); + } + + return ReadModelFromSeekableStream(stream, requestedLayout); + } + + private IDatModel ReadModelFromSeekableStream(Stream stream, DatLayoutKind? requestedLayout) + { + DatLayoutKind layout; + if (requestedLayout.HasValue) + { + layout = requestedLayout.Value; + } + else + { + var startPosition = stream.Position; + layout = GetDatLayoutKind(stream); + stream.Seek(startPosition, SeekOrigin.Begin); + } + + return ReadModel(stream, layout); + } + + private IDatModel ReadModel(Stream stream, DatLayoutKind requestedLayout) + { + var reader = Services.GetRequiredService(); + var datBinary = reader.ReadBinary(stream); + + var converter = Services.GetRequiredService(); + var datModel = converter.BinaryToModel(datBinary); + + if (requestedLayout == DatLayoutKind.NotOrdered && datModel is ISortedDatModel sorted) + datModel = sorted.ToUnsortedModel(); + + if (requestedLayout == DatLayoutKind.OrderedByCrc32 && datModel is IUnsortedDatModel) + throw new InvalidOperationException("Unsorted DAT file cannot be loaded as sorted DAT file"); + + return datModel; + } +} diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/MergedKeyResult.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/MergedKeyResult.cs index fca4f1c0..d6334522 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/MergedKeyResult.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/Services/MergedKeyResult.cs @@ -21,7 +21,7 @@ public readonly struct MergedKeyResult public DatStringEntry? OldEntry { get; } /// - /// Gets a value indicating whether the new entry was added or overwritten. + /// Gets a value that indicates whether the new entry was added or overwritten. /// public MergeOperation Status => !OldEntry.HasValue ? MergeOperation.Added : MergeOperation.Overwritten; diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Construction/ConstructingMegArchiveBuilderBaseTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Construction/ConstructingMegArchiveBuilderBaseTest.cs index 0afdc1cf..daec4c1f 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Construction/ConstructingMegArchiveBuilderBaseTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Construction/ConstructingMegArchiveBuilderBaseTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -22,7 +22,7 @@ public abstract class ConstructingMegArchiveBuilderBaseTest : CommonMegTestBase protected abstract int GetExpectedHeaderSize(); - protected abstract MegFileVersion GetExpectedFileVersion(); + protected abstract MegVersion GetExpectedFileVersion(); protected override void SetupServices(IServiceCollection serviceCollection) { @@ -117,7 +117,7 @@ public void BuildConstructingMegArchive_NonASCIITreatment() FileSystem.File.Create("file.meg"); var entry = MegDataEntryTest.CreateEntry("A", default, 0, 5); - var megFile = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var megFile = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var builderEntries = new List { @@ -238,6 +238,6 @@ internal abstract class SmallMaxFileSizeConstructingService( internal override uint MaxFileSize => maxFileSize; - protected abstract override MegFileVersion FileVersion { get; } + protected abstract override MegVersion MegVersion { get; } } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Construction/ConstructingMegArchiveBuilderV1Test.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Construction/ConstructingMegArchiveBuilderV1Test.cs index a9889222..956e77fe 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Construction/ConstructingMegArchiveBuilderV1Test.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Construction/ConstructingMegArchiveBuilderV1Test.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using PG.StarWarsGame.Files.MEG.Binary; @@ -23,9 +23,9 @@ protected override int GetExpectedHeaderSize() return MegHeader.SizeValue; } - protected override MegFileVersion GetExpectedFileVersion() + protected override MegVersion GetExpectedFileVersion() { - return MegFileVersion.V1; + return MegVersion.V1; } [Fact] @@ -47,7 +47,7 @@ protected override uint GetMaxEntrySizeForTooLargeTest(MegDataEntryBuilderInfo e private sealed class SmallMaxFileSizeConstructingServiceV1(uint maxEntrySize, uint maxFileSize, IServiceProvider services) : SmallMaxFileSizeConstructingService(maxEntrySize, maxFileSize, services) { - protected override MegFileVersion FileVersion => MegFileVersion.V1; + protected override MegVersion MegVersion => MegVersion.V1; } protected override uint GetTotalMegSizeForTooLargeTest(IEnumerable entries) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/MegBinaryServiceFactoryTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/MegBinaryServiceFactoryTest.cs index 57dfc5b0..df7fe79a 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/MegBinaryServiceFactoryTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/MegBinaryServiceFactoryTest.cs @@ -2,6 +2,7 @@ using PG.StarWarsGame.Files.MEG.Binary; using PG.StarWarsGame.Files.MEG.Binary.Size; using PG.StarWarsGame.Files.MEG.Binary.V1; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Files; using Xunit; @@ -17,41 +18,41 @@ public MegBinaryServiceFactoryTest() } [Theory] - [InlineData(MegFileVersion.V1, typeof(MegFileBinaryReaderV1))] - public void GetReader_V1_ReturnsCorrectType(MegFileVersion version, Type expectedType) + [InlineData(MegVersion.V1, typeof(MegFileBinaryReaderV1))] + public void GetReader_V1_ReturnsCorrectType(MegVersion version, Type expectedType) { var reader = _factory.GetReader(version); Assert.IsType(expectedType, reader); } [Theory] - [InlineData(MegFileVersion.V1, typeof(MegBinaryConverterV1))] - public void GetConverter_V1_ReturnsCorrectType(MegFileVersion version, Type expectedType) + [InlineData(MegVersion.V1, typeof(MegBinaryConverterV1))] + public void GetConverter_V1_ReturnsCorrectType(MegVersion version, Type expectedType) { var converter = _factory.GetConverter(version); Assert.IsType(expectedType, converter); } [Theory] - [InlineData(MegFileVersion.V1, typeof(ConstructingMegArchiveBuilderV1))] - public void GetConstructionBuilder_V1_ReturnsCorrectType(MegFileVersion version, Type expectedType) + [InlineData(MegVersion.V1, typeof(ConstructingMegArchiveBuilderV1))] + public void GetConstructionBuilder_V1_ReturnsCorrectType(MegVersion version, Type expectedType) { var builder = _factory.GetConstructionBuilder(version); Assert.IsType(expectedType, builder); } [Theory] - [InlineData(MegFileVersion.V1, typeof(MegV1SizeCalculator))] - public void GetMegSizeCalculator_ReturnsCorrectType(MegFileVersion version, Type expectedType) + [InlineData(MegVersion.V1, typeof(MegV1SizeCalculator))] + public void GetMegSizeCalculator_ReturnsCorrectType(MegVersion version, Type expectedType) { var calculator = _factory.GetMegSizeCalculator(version); Assert.IsType(expectedType, calculator); } [Theory] - [InlineData(MegFileVersion.V2)] - [InlineData(MegFileVersion.V3)] - public void V2andV3_Unsupported_ThrowsNotImplementedException(MegFileVersion version) + [InlineData(MegVersion.V2)] + [InlineData(MegVersion.V3)] + public void V2andV3_Unsupported_ThrowsNotImplementedException(MegVersion version) { Assert.Throws(() => _factory.GetConstructionBuilder(version)); Assert.Throws(() => _factory.GetMegSizeCalculator(version)); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Version/MegVersionIdentifierTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Version/MegVersionIdentifierTest.cs index 914009b7..f6a4caf8 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Version/MegVersionIdentifierTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Binary/Version/MegVersionIdentifierTest.cs @@ -1,7 +1,8 @@ -using AnakinRaW.CommonUtilities.Testing; +using AnakinRaW.CommonUtilities.Testing; using Microsoft.Extensions.DependencyInjection; using PG.StarWarsGame.Files.Binary; using PG.StarWarsGame.Files.MEG.Binary; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Files; using System; using System.IO; @@ -23,43 +24,43 @@ public MegVersionIdentifierTest() } [Fact] - public void GetMegFileVersion_ThrowsArgNull() + public void GetMegVersion_ThrowsArgNull() { - Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(null!, out _)); + Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegVersion(null!, out _)); } [Fact] - public void GetMegFileVersion_ThrowsArg() + public void GetMegVersion_ThrowsArg() { - Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MegTestConstants.NonSeekableStream(), out _)); + Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MegTestConstants.NonSeekableStream(), out _)); } [Fact] - public void GetMegFileVersion_EmptyStream() + public void GetMegVersion_EmptyStream() { - Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream([]), out _)); + Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream([]), out _)); } [Fact] - public void GetMegFileVersion_InvalidFlags() + public void GetMegVersion_InvalidFlags() { var data = new byte[] { 0x77, 0x77, 0x77, 0x77, 0xa4, 0x70, 0x7d, 0x3f }; - Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out _)); + Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out _)); } [Fact] - public void GetMegFileVersion_InvalidId() + public void GetMegVersion_InvalidId() { var data = new byte[] { 0xff, 0xff, 0xff, 0xff, 0xaa, 0x77, 0x77, 0x33 }; - Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out _)); + Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out _)); } /* @@ -67,68 +68,68 @@ public void GetMegFileVersion_InvalidId() */ [Fact] - public void GetMegFileVersion_V1_EmptyFile() + public void GetMegVersion_V1_EmptyFile() { var data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V1, version); + Assert.Equal(MegVersion.V1, version); Assert.False(encrypted); } [Fact] - public void GetMegFileVersion_V1_SomeFileWithJunk() + public void GetMegVersion_V1_SomeFileWithJunk() { var data = new byte[] { 1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 3, 4, 5 // Junk }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V1, version); + Assert.Equal(MegVersion.V1, version); Assert.False(encrypted); } [Fact] - public void GetMegFileVersion_V1_NumFilesMatchesId() + public void GetMegVersion_V1_NumFilesMatchesId() { var data = new byte[] { 0xa4, 0x70, 0x7d, 0x3f, 0xa4, 0x70, 0x7d, 0x3f, 1, 2, 3, 4, 5 // Junk }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V1, version); + Assert.Equal(MegVersion.V1, version); Assert.False(encrypted); } [Fact] - public void GetMegFileVersion_V1_NumFilesMatchesFlags() + public void GetMegVersion_V1_NumFilesMatchesFlags() { var data = new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 1, 2, 3, 4, 5 // Junk }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V1, version); + Assert.Equal(MegVersion.V1, version); Assert.False(encrypted); } [Fact] - public void GetMegFileVersion_V1_NumFilesMatchesEncryptedFlag() + public void GetMegVersion_V1_NumFilesMatchesEncryptedFlag() { var data = new byte[] { 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0x8f, 1, 2, 3, 4, 5 // Junk }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V1, version); + Assert.Equal(MegVersion.V1, version); Assert.False(encrypted); } @@ -137,18 +138,18 @@ public void GetMegFileVersion_V1_NumFilesMatchesEncryptedFlag() */ [Fact] - public void GetMegFileVersion_V2_ThrowsIncompleteData() + public void GetMegVersion_V2_ThrowsIncompleteData() { var data = new byte[] { 0xff, 0xff, 0xff, 0xff, 0xa4, 0x70, 0x7d, 0x3f }; - Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out _)); + Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out _)); } [Fact] - public void GetMegFileVersion_V2_Empty() + public void GetMegVersion_V2_Empty() { var data = new byte[] { @@ -158,14 +159,14 @@ public void GetMegFileVersion_V2_Empty() 0,0,0,0, 0,0,0,0, }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V2, version); + Assert.Equal(MegVersion.V2, version); Assert.False(encrypted); } [Fact] - public void GetMegFileVersion_V2_WithJunk() + public void GetMegVersion_V2_WithJunk() { var data = new byte[] { @@ -176,14 +177,14 @@ public void GetMegFileVersion_V2_WithJunk() 0,0,0,0, 1,2,3,4 // Some junk }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V2, version); + Assert.Equal(MegVersion.V2, version); Assert.False(encrypted); } [Fact] - public void GetMegFileVersion_V2_1File_ButCorrupted() + public void GetMegVersion_V2_1File_ButCorrupted() { var data = new byte[] { @@ -193,11 +194,11 @@ public void GetMegFileVersion_V2_1File_ButCorrupted() 1, 0, 0, 0, 1, 0, 0, 0 }; - Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out _)); + Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out _)); } [Fact] - public void GetMegFileVersion_V2_1FileEmpty() + public void GetMegVersion_V2_1FileEmpty() { var data = new byte[] { @@ -214,19 +215,19 @@ public void GetMegFileVersion_V2_1FileEmpty() 0x2b,0,0,0, 0,0,0,0, }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V2, version); + Assert.Equal(MegVersion.V2, version); Assert.False(encrypted); } [Fact] - public void GetMegFileVersion_V2_2Files() + public void GetMegVersion_V2_2Files() { var data = TestingHelpers.GetEmbeddedResource(typeof(MegVersionIdentifierTest), "Files.v2_2_files_data.meg"); - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(data, out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(data, out var encrypted); - Assert.Equal(MegFileVersion.V2, version); + Assert.Equal(MegVersion.V2, version); Assert.False(encrypted); } @@ -236,7 +237,7 @@ public void GetMegFileVersion_V2_2Files() */ [Fact] - public void GetMegFileVersion_V3_EmptyFile() + public void GetMegVersion_V3_EmptyFile() { var data = new byte[] { @@ -247,14 +248,14 @@ public void GetMegFileVersion_V3_EmptyFile() 0,0,0,0, 0,0,0,0, }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V3, version); + Assert.Equal(MegVersion.V3, version); Assert.False(encrypted); } [Fact] - public void GetMegFileVersion_V3_EmptyFile_Junk() + public void GetMegVersion_V3_EmptyFile_Junk() { var data = new byte[] { @@ -266,14 +267,14 @@ public void GetMegFileVersion_V3_EmptyFile_Junk() 0,0,0,0, 1,2,3,4 }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V3, version); + Assert.Equal(MegVersion.V3, version); Assert.False(encrypted); } [Fact] - public void GetMegFileVersion_V3_1FileEmpty() + public void GetMegVersion_V3_1FileEmpty() { var data = new byte[] { @@ -292,14 +293,14 @@ public void GetMegFileVersion_V3_1FileEmpty() 0x2f,0,0,0, 0,0, }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V3, version); + Assert.Equal(MegVersion.V3, version); Assert.False(encrypted); } [Fact] - public void GetMegFileVersion_V3_CorruptedCauseEncryptFlagSet() + public void GetMegVersion_V3_CorruptedCauseEncryptFlagSet() { var data = new byte[] { @@ -318,11 +319,11 @@ public void GetMegFileVersion_V3_CorruptedCauseEncryptFlagSet() 0x2f,0,0,0, 0,0, }; - Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out _)); + Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out _)); } [Fact] - public void GetMegFileVersion_V3_1File_ButCorrupted() + public void GetMegVersion_V3_1File_ButCorrupted() { var data = new byte[] { @@ -333,16 +334,16 @@ public void GetMegFileVersion_V3_1File_ButCorrupted() 1, 0, 0, 0, 3, 0, 0, 0 }; - Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out _)); + Assert.Throws(() => new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out _)); } [Fact] - public void GetMegFileVersion_V3_2Files() + public void GetMegVersion_V3_2Files() { var data = TestingHelpers.GetEmbeddedResource(typeof(MegVersionIdentifierTest), "Files.v3n_2_files_data.meg"); - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(data, out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(data, out var encrypted); - Assert.Equal(MegFileVersion.V3, version); + Assert.Equal(MegVersion.V3, version); Assert.False(encrypted); } @@ -352,7 +353,7 @@ public void GetMegFileVersion_V3_2Files() */ [Fact] - public void GetMegFileVersion_V3_Enc_Empty() + public void GetMegVersion_V3_Enc_Empty() { var data = new byte[] { @@ -363,14 +364,14 @@ public void GetMegFileVersion_V3_Enc_Empty() 0,0,0,0, 0,0,0,0, }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V3, version); + Assert.Equal(MegVersion.V3, version); Assert.True(encrypted); } [Fact] - public void GetMegFileVersion_V3_Enc_WithJunk() + public void GetMegVersion_V3_Enc_WithJunk() { var data = new byte[] { @@ -382,9 +383,9 @@ public void GetMegFileVersion_V3_Enc_WithJunk() 0xAA,0,0,0, 1,2,3,4, // Junk }; - var version = new MegVersionIdentifier(_serviceProvider).GetMegFileVersion(new MemoryStream(data), out var encrypted); + var version = new MegVersionIdentifier(_serviceProvider).GetMegVersion(new MemoryStream(data), out var encrypted); - Assert.Equal(MegFileVersion.V3, version); + Assert.Equal(MegVersion.V3, version); Assert.True(encrypted); } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Archives/ConstructingMegArchiveTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Archives/ConstructingMegArchiveTest.cs index fa5ef5bc..9172ef66 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Archives/ConstructingMegArchiveTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Archives/ConstructingMegArchiveTest.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using PG.Commons.Hashing; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Data.Entries; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; @@ -16,16 +17,16 @@ public class ConstructingMegArchiveTest : CommonMegTestBase [Fact] public void Ctor_Throw_NullArgument() { - Assert.Throws(() =>new ConstructingMegArchive(null!, MegFileVersion.V1, 0, false)); + Assert.Throws(() =>new ConstructingMegArchive(null!, MegVersion.V1, 0, false)); } [Fact] public void Ctor_Empty() { var entries = new List(); - var cArchive = new ConstructingMegArchive(entries, MegFileVersion.V3, 123, true); + var cArchive = new ConstructingMegArchive(entries, MegVersion.V3, 123, true); - Assert.Equal(MegFileVersion.V3, cArchive.MegVersion); + Assert.Equal(MegVersion.V3, cArchive.MegVersion); Assert.Equal(new MegArchive(new List()).ToList(), cArchive.Archive.ToList()); Assert.Equal(123u, cArchive.ExpectedFileSize); Assert.True(cArchive.Encrypted); @@ -38,7 +39,7 @@ public void Ctor_CreateArchive() var entry2 = MegDataEntryTest.CreateEntry("pathB", new Crc32(2), 2, 2); FileSystem.File.Create("file.meg"); - var mf = new MegFile(new MegArchive([entry1, entry2]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var mf = new MegFile(new MegArchive([entry1, entry2]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var locEntry = MegDataEntryTest.CreateEntry("pathC", new Crc32(3), 3, 3); @@ -53,9 +54,9 @@ public void Ctor_CreateArchive() reference1, reference2 }; - var cArchive = new ConstructingMegArchive(entries, MegFileVersion.V3, 123, false); + var cArchive = new ConstructingMegArchive(entries, MegVersion.V3, 123, false); - Assert.Equal(MegFileVersion.V3, cArchive.MegVersion); + Assert.Equal(MegVersion.V3, cArchive.MegVersion); Assert.False(cArchive.Encrypted); var expectedArchiveList = new List diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Archives/VirtualMegArchiveTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Archives/VirtualMegArchiveTest.cs index 42775f70..1bbef0f1 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Archives/VirtualMegArchiveTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Archives/VirtualMegArchiveTest.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using PG.Commons.Hashing; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Data.Entries; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; @@ -19,7 +20,7 @@ protected override MegDataEntryReference CreateEntry(string path, Crc32 crc = de { FileSystem.File.Create("file.meg").Dispose(); var entry = MegDataEntryTest.CreateEntry(path, crc); - var megFile = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var megFile = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); return new MegDataEntryReference(new MegDataEntryLocationReference(megFile, entry)); } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Entries/MegDataEntryReferenceTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Entries/MegDataEntryReferenceTest.cs index 16d4cb8f..53476899 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Entries/MegDataEntryReferenceTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/Entries/MegDataEntryReferenceTest.cs @@ -1,4 +1,5 @@ using PG.Commons.Hashing; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Data.Entries; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; @@ -13,7 +14,7 @@ public class MegDataEntryReferenceTest : MegDataEntryBaseTest(() => new MegDataEntryLocationReference(megFile, null!)); } @@ -25,13 +26,13 @@ public void Ctor_Throws() public void Ctor() { FileSystem.File.Create("file.meg"); - var megFile = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegFileVersion.V1), + var megFile = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var entry = MegDataEntryTest.CreateEntry("path"); var reference = new MegDataEntryLocationReference(megFile, entry); - Assert.Same(megFile, reference.MegFile); + Assert.Same(megFile, reference.Source); Assert.Same(entry, reference.DataEntry); } @@ -41,9 +42,9 @@ public void Equals_Hashcode() FileSystem.File.Create("a.meg"); FileSystem.File.Create("b.meg"); - var megFileA = new MegFile(new MegArchive([]), new MegFileInformation("a.meg", MegFileVersion.V1), + var megFileA = new MegFile(new MegArchive([]), new MegFileInformation("a.meg", MegVersion.V1), ServiceProvider); - var megFileB = new MegFile(new MegArchive([]), new MegFileInformation("b.meg", MegFileVersion.V1), + var megFileB = new MegFile(new MegArchive([]), new MegFileInformation("b.meg", MegVersion.V1), ServiceProvider); var entry = MegDataEntryTest.CreateEntry("path"); @@ -76,7 +77,7 @@ public void Exists() var entry = MegDataEntryTest.CreateEntry("path"); FileSystem.File.Create("file.meg"); - var megFile = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegFileVersion.V1), + var megFile = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var locationExists = new MegDataEntryLocationReference(megFile, entry); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryLocationTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryLocationTest.cs index ce040020..31a2ecb6 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryLocationTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Data/EntryLocations/MegDataEntryLocationTest.cs @@ -37,9 +37,13 @@ public void Equality_HashCode() var location3 = default(MegDataEntryLocation); Assert.Equal(location1, location2); + Assert.True(location1 == location2); + Assert.False(location1 != location2); Assert.Equal(location1.GetHashCode(), location2.GetHashCode()); Assert.NotEqual(location1, location3); + Assert.False(location1 == location3); + Assert.True(location1 != location3); Assert.NotEqual(location1, new object()); Assert.NotEqual((object?)null, location1); Assert.NotEqual(location1.GetHashCode(), location3.GetHashCode()); 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 bfb95ade..98f624c0 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 @@ -1,10 +1,13 @@ -using System; -using System.IO.Abstractions; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; using PG.StarWarsGame.Files.MEG.Files; using PG.StarWarsGame.Files.MEG.Test.Data.Entries; using PG.Testing; +using System; +using System.IO; +using System.IO.Abstractions; +using Testably.Abstractions.Testing; using Xunit; namespace PG.StarWarsGame.Files.MEG.Test.Data.EntryLocations; @@ -38,7 +41,7 @@ public void Ctor_FileInfo() public void Ctor_ReferenceLocation() { using var _ = FileSystem.File.Create("test.meg"); - var meg = new MegFile(new MegArchive([]), new MegFileInformation("test.meg", MegFileVersion.V1), + var meg = new MegFile(new MegArchive([]), new MegFileInformation("test.meg", MegVersion.V1), ServiceProvider); var location = new MegDataEntryLocationReference(meg, MegDataEntryTest.CreateEntry("path")); @@ -134,11 +137,67 @@ public void Ctor_Bytes_Span_DefensiveCopy() #endregion + #region GetDataStream + + [Fact] + public void GetDataStream_File_NotFound_Throws() + { + var originInfo = new MegDataEntryOriginInfo(FileSystem.FileInfo.New("test.txt")); + Assert.Throws(originInfo.GetDataStream); + } + + [Fact] + public void GetDataStream_File() + { + FileSystem.Initialize().WithFile("test.txt").Which(m => m.HasBytesContent([1, 2, 3])); + + var originInfo = new MegDataEntryOriginInfo(FileSystem.FileInfo.New("test.txt")); + var stream = originInfo.GetDataStream(); + Assert.Equal(3, stream.Length); + + var resultStream = new MemoryStream(new byte[3]); + stream.CopyTo(resultStream); + Assert.Equal([1, 2, 3], resultStream.ToArray()); + } + + [Fact] + public void GetDataStream_LocationReference() + { + FileSystem.Initialize().WithFile("a.meg").Which(m => m.HasBytesContent([1, 2, 3, 4, 5])); + + var entry = MegDataEntryTest.CreateEntry("file.txt", offset: 1, size: 2); + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("a.meg", MegVersion.V1), ServiceProvider); + var originInfo = new MegDataEntryOriginInfo(new MegDataEntryLocationReference(meg, entry)); + + var stream = originInfo.GetDataStream(); + Assert.Equal(2, stream.Length); + + var resultStream = new MemoryStream(new byte[2]); + stream.CopyTo(resultStream); + Assert.Equal([2, 3], resultStream.ToArray()); + } + + [Fact] + public void GetDataStream_Bytes() + { + var bytes = new byte[] { 10, 20, 30, 40, 50 }; + var originInfo = new MegDataEntryOriginInfo(bytes); + + using var resultStream = originInfo.GetDataStream(); + Assert.Equal(5, resultStream.Length); + + var sink = new MemoryStream(); + resultStream.CopyTo(sink); + Assert.Equal(bytes, sink.ToArray()); + } + + #endregion + [Fact] public void EqualsHashCode() { using var _ = FileSystem.File.Create("test.meg"); - var meg = new MegFile(new MegArchive([]), new MegFileInformation("test.meg", MegFileVersion.V1), + var meg = new MegFile(new MegArchive([]), new MegFileInformation("test.meg", MegVersion.V1), ServiceProvider); var location = new MegDataEntryLocationReference(meg, MegDataEntryTest.CreateEntry("path")); 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 ffc12512..4effca9e 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 @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; @@ -75,7 +75,7 @@ public void Ctor_OriginIsLocalFile_FileTooLarge_ThrowsMegEntrySizeException() public void Ctor_OriginIsEntryReference() { FileSystem.File.Create("file.meg"); - var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var origin = new MegDataEntryOriginInfo(new MegDataEntryLocationReference( meg, MegDataEntryTest.CreateEntry("path", default, 123, 321, true))); @@ -97,7 +97,7 @@ public void Ctor_OriginIsEntryReference() public void Ctor_OriginIsEntryReference_OverridesProperties(string? overridePath, bool encrypted) { FileSystem.File.Create("file.meg"); - var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var origin = new MegDataEntryOriginInfo(new MegDataEntryLocationReference( meg, MegDataEntryTest.CreateEntry("path", default, 123, 321, true))); @@ -114,7 +114,7 @@ public void Ctor_OriginIsEntryReference_OverridesProperties(string? overridePath public void Ctor_OriginIsEntryReference_OverridesProperties_PathEmpty_Throws(string path) { FileSystem.File.Create("file.meg"); - var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var origin = new MegDataEntryOriginInfo(new MegDataEntryLocationReference( meg, MegDataEntryTest.CreateEntry("path", default, 123, 321, true))); Assert.Throws(() => new MegDataEntryBuilderInfo(origin, path, true)); @@ -174,7 +174,7 @@ public void FromEntry_NullArgs() { FileSystem.File.Create("file.meg"); var entry = MegDataEntryTest.CreateEntry("test.xml", default, 123, 321, true); - var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); Assert.Throws(() => MegDataEntryBuilderInfo.FromEntry(null!, entry)); Assert.Throws(() => MegDataEntryBuilderInfo.FromEntry(meg, null!)); @@ -184,7 +184,7 @@ public void FromEntry_NullArgs() public void FromEntry_EntryNotInMeg_ThrowsArgumentException() { FileSystem.File.Create("file.meg"); - var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var entry = MegDataEntryTest.CreateEntry("test.xml", default, 123, 321, true); Assert.Throws(() => MegDataEntryBuilderInfo.FromEntry(meg, entry)); @@ -200,7 +200,7 @@ public void FromEntry_SetsProperties(string? overridePath, bool encrypted) { FileSystem.File.Create("file.meg"); var entry = MegDataEntryTest.CreateEntry("test.xml", default, 123, 321, true); - var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var info = MegDataEntryBuilderInfo.FromEntry(meg, entry, overridePath, encrypted); @@ -216,7 +216,7 @@ public void FromEntry_OriginIsEntryReference_EntryPathEmpty_ThrowsArgumentExcept { FileSystem.File.Create("file.meg"); var entry = MegDataEntryTest.CreateEntry("path", default, 123, 321, true); - var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); Assert.Throws(() => MegDataEntryBuilderInfo.FromEntry(meg, entry, string.Empty, false)); @@ -236,7 +236,7 @@ public void FromEntryReference_NullArgs() public void FromEntryReference_EntryNotInMeg_ThrowsArgumentException() { FileSystem.File.Create("file.meg"); - var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var entry = MegDataEntryTest.CreateEntry("test.xml", default, 123, 321, true); Assert.Throws(() => @@ -253,7 +253,7 @@ public void FromEntryReference_OriginIsLocalFile(string? overridePath, bool encr { FileSystem.File.Create("file.meg"); var entry = MegDataEntryTest.CreateEntry("test.xml", default, 123, 321, true); - var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var info = MegDataEntryBuilderInfo.FromEntryReference(new MegDataEntryLocationReference(meg, entry), overridePath, encrypted); @@ -269,7 +269,7 @@ public void FromEntryReference_OriginIsEntryReference_EntryPathEmpty_ThrowsArgum { FileSystem.File.Create("file.meg"); var entry = MegDataEntryTest.CreateEntry("path", default, 123, 321, true); - var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); Assert.Throws(() => MegDataEntryBuilderInfo.FromEntryReference( new MegDataEntryLocationReference(meg, entry), string.Empty, false)); } @@ -390,7 +390,7 @@ public void RefreshSize_FromMegEntry() { FileSystem.File.Create("file.meg"); var entry = MegDataEntryTest.CreateEntry("path", default, 123, 321, true); - var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var info = MegDataEntryBuilderInfo.FromEntryReference(new MegDataEntryLocationReference(meg, entry)); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Exceptions/EntryNotInMegExceptionTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Exceptions/EntryNotInMegExceptionTest.cs index 6fa04f8f..4f9dff8e 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Exceptions/EntryNotInMegExceptionTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Exceptions/EntryNotInMegExceptionTest.cs @@ -1,4 +1,5 @@ -using AnakinRaW.CommonUtilities.Testing.Extensions; +using AnakinRaW.CommonUtilities.Testing.Extensions; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; using PG.StarWarsGame.Files.MEG.Files; @@ -15,7 +16,7 @@ public void Ctor() FileSystem.File.Create("a.meg"); FileSystem.File.Create("b.meg"); - var megFileA = new MegFile(new MegArchive([]), new MegFileInformation("a.meg", MegFileVersion.V1), + var megFileA = new MegFile(new MegArchive([]), new MegFileInformation("a.meg", MegVersion.V1), ServiceProvider); var entry = MegDataEntryTest.CreateEntry("text.xml"); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegEncryptionDataTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegEncryptionDataTest.cs index b2714467..ec6be1ce 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegEncryptionDataTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegEncryptionDataTest.cs @@ -1,5 +1,6 @@ using System; using System.Security.Cryptography; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Files; using Xunit; diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegFileInformationTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegFileInformationTest.cs index 009a3693..648279dc 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegFileInformationTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegFileInformationTest.cs @@ -1,4 +1,5 @@ -using System; +using System; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Files; using Xunit; @@ -9,21 +10,21 @@ public class MegFileInformationTest [Fact] public void Ctor_Throws() { - Assert.Throws(() => new MegFileInformation(null!, MegFileVersion.V2)); - Assert.Throws(() => new MegFileInformation("", MegFileVersion.V2)); - Assert.Throws(() => new MegFileInformation("path", MegFileVersion.V1, MegEncryptionDataTest.CreateRandomData())); - Assert.Throws(() => new MegFileInformation("path", MegFileVersion.V2, MegEncryptionDataTest.CreateRandomData())); + Assert.Throws(() => new MegFileInformation(null!, MegVersion.V2)); + Assert.Throws(() => new MegFileInformation("", MegVersion.V2)); + Assert.Throws(() => new MegFileInformation("path", MegVersion.V1, MegEncryptionDataTest.CreateRandomData())); + Assert.Throws(() => new MegFileInformation("path", MegVersion.V2, MegEncryptionDataTest.CreateRandomData())); } [Theory] - [InlineData(MegFileVersion.V1)] - [InlineData(MegFileVersion.V2)] - [InlineData(MegFileVersion.V3)] - public void Ctor(MegFileVersion version) + [InlineData(MegVersion.V1)] + [InlineData(MegVersion.V2)] + [InlineData(MegVersion.V3)] + public void Ctor(MegVersion version) { var fileInfo = new MegFileInformation("path", version); Assert.Equal("path", fileInfo.FilePath); - Assert.Equal(version, fileInfo.FileVersion); + Assert.Equal(version, fileInfo.Version); Assert.Null(fileInfo.EncryptionData); Assert.False(fileInfo.HasEncryption); } @@ -32,9 +33,9 @@ public void Ctor(MegFileVersion version) public void Ctor_Encrypted() { var encData = MegEncryptionDataTest.CreateRandomData(); - var fileInfo = new MegFileInformation("path", MegFileVersion.V3, encData); + var fileInfo = new MegFileInformation("path", MegVersion.V3, encData); Assert.Equal("path", fileInfo.FilePath); - Assert.Equal(MegFileVersion.V3, fileInfo.FileVersion); + Assert.Equal(MegVersion.V3, fileInfo.Version); Assert.Same(encData, fileInfo.EncryptionData); Assert.True(fileInfo.HasEncryption); } @@ -43,7 +44,7 @@ public void Ctor_Encrypted() public void Dispose() { var encData = MegEncryptionDataTest.CreateRandomData(); - var fileInfo = new MegFileInformation("path", MegFileVersion.V3, encData); + var fileInfo = new MegFileInformation("path", MegVersion.V3, encData); fileInfo.Dispose(); Assert.True(encData.IsDisposed); } @@ -54,11 +55,11 @@ public void CopyRecord() var encData = MegEncryptionDataTest.CreateRandomData(); var orgKey = encData.Key; var orgIv = encData.IV; - var fileInfo = new MegFileInformation("path", MegFileVersion.V3, encData); + var fileInfo = new MegFileInformation("path", MegVersion.V3, encData); var other = fileInfo with { FilePath = "otherPath"}; Assert.Equal("otherPath", other.FilePath); - Assert.Equal(MegFileVersion.V3, other.FileVersion); + Assert.Equal(MegVersion.V3, other.Version); Assert.NotSame(encData, other.EncryptionData); Assert.True(other.HasEncryption); Assert.Equal(orgKey, other.EncryptionData.Key); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegFileTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegFileTest.cs index 8d65dcdd..133f4996 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegFileTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Files/MegFileTest.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Security.Cryptography; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Files; using Testably.Abstractions.Testing; @@ -13,7 +14,7 @@ public class MegFileTest : CommonMegTestBase public void Ctor_ThrowsArgumentNullException() { FileSystem.Initialize().WithFile("test.meg"); - var param = new MegFileInformation("test.meg", MegFileVersion.V1); + var param = new MegFileInformation("test.meg", MegVersion.V1); var model = new MegArchive([]); Assert.Throws(() => new MegFile(null!, param, ServiceProvider)); @@ -25,7 +26,7 @@ public void Ctor_ThrowsArgumentNullException() public void Ctor_SetupProperties() { const string name = "test.meg"; - var param = new MegFileInformation(name, MegFileVersion.V2); + var param = new MegFileInformation(name, MegVersion.V2); var model = new MegArchive([]); FileSystem.Initialize().WithFile("test.meg"); @@ -34,7 +35,7 @@ public void Ctor_SetupProperties() Assert.Same(model, megFile.Content); Assert.Same(model, megFile.Archive); - Assert.Equal(MegFileVersion.V2, megFile.FileInformation.FileVersion); + Assert.Equal(MegVersion.V2, megFile.FileInformation.Version); Assert.False(megFile.FileInformation.HasEncryption); Assert.Equal(FileSystem.Path.GetFullPath(name), megFile.FileInformation.FilePath); @@ -49,7 +50,7 @@ public void Ctor_SetupProperties_DisposeFileInfoParam() var copyKey = encData.Key; - var param = new MegFileInformation(name, MegFileVersion.V3, encData); + var param = new MegFileInformation(name, MegVersion.V3, encData); FileSystem.Initialize().WithFile("test.meg"); @@ -73,7 +74,7 @@ public void Ctor_SetupProperties_Encrypted() var encData = new MegEncryptionData(key, iv); - var param = new MegFileInformation("test.meg", MegFileVersion.V3, encData); + var param = new MegFileInformation("test.meg", MegVersion.V3, encData); FileSystem.Initialize().WithFile("test.meg"); @@ -81,7 +82,7 @@ public void Ctor_SetupProperties_Encrypted() var megFile = new MegFile(model, param, ServiceProvider); Assert.Same(model, megFile.Content); - Assert.Equal(MegFileVersion.V3, megFile.FileInformation.FileVersion); + Assert.Equal(MegVersion.V3, megFile.FileInformation.Version); Assert.True(megFile.FileInformation.HasEncryption); Assert.Equal(iv, megFile.FileInformation.EncryptionData!.IV); Assert.Equal(key, megFile.FileInformation.EncryptionData!.Key); @@ -95,7 +96,7 @@ public void EncryptionKeyHandling() var encData = new MegEncryptionData(keyIv, keyIv); - var param = new MegFileInformation("test.meg", MegFileVersion.V3, encData); + var param = new MegFileInformation("test.meg", MegVersion.V3, encData); var model = new MegArchive([]); FileSystem.Initialize().WithFile("test.meg"); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/MegServiceContributionTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/MegServiceContributionTest.cs index 78352cb3..8cd7c7ed 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/MegServiceContributionTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/MegServiceContributionTest.cs @@ -12,11 +12,10 @@ public class MegServiceContributionTest : CommonMegTestBase [Fact] public void SupportMEG_Registers() { - Assert.DoesNotThrow(() => ServiceProvider.GetRequiredService()); + Assert.DoesNotThrow(() => ServiceProvider.GetRequiredService()); Assert.DoesNotThrow(() => ServiceProvider.GetRequiredService()); Assert.DoesNotThrow(() => ServiceProvider.GetRequiredService()); Assert.DoesNotThrow(() => ServiceProvider.GetRequiredService()); - Assert.DoesNotThrow(() => ServiceProvider.GetRequiredService()); Assert.DoesNotThrow(() => ServiceProvider.GetRequiredService()); Assert.DoesNotThrow(() => ServiceProvider.GetRequiredService()); } diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/MegTestConstants.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/MegTestConstants.cs index 6fc89559..7cc9668b 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/MegTestConstants.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/MegTestConstants.cs @@ -519,16 +519,33 @@ internal class NonSeekableStream : Stream 47, 71, 97, 109, 101, 95, 79, 98, 106, 101, 99, 116, 95, 70, 105, 108, 101, 115, 62 ]; + // A minimal V1 MEG holding a single zero-size entry named "file" and no data section. + internal static readonly byte[] EmptyEntryMegFileV1 = + [ + 1, 0, 0, 0, 1, 0, 0, 0, // Header: one file name, one file + 4, 0, 102, 105, 108, 101, // "file" + // CRC32 of "file", record index 0, size 0, offset 34 (== metadata size), name index 0 + 16, 54, 159, 140, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0 + ]; + internal class FakeFileInfo(string fullName, long length) : IFileInfo { public string FullName => fullName; public long Length { get; set; } = length; public bool Exists => true; + /// + /// Optional content returned by . Its length can intentionally differ from + /// to simulate a file whose reported size disagrees with its actual data. + /// + public byte[]? ReadBytes { get; set; } + #region Other IFileInfo Members public void Delete() => throw new NotImplementedException(); public void Refresh() { } - public FileSystemStream OpenRead() => throw new NotImplementedException(); + public FileSystemStream OpenRead() => ReadBytes is null + ? throw new NotImplementedException() + : new FakeFileSystemStream(new MemoryStream(ReadBytes, writable: false), fullName); public FileSystemStream OpenWrite() => throw new NotImplementedException(); public FileSystemStream Open(FileMode mode) => throw new NotImplementedException(); public FileSystemStream Open(FileMode mode, FileAccess access) => throw new NotImplementedException(); @@ -572,4 +589,7 @@ public void Refresh() { } #endregion } + + // Minimal FileSystemStream wrapper around an arbitrary stream, used by FakeFileInfo.OpenRead. + internal sealed class FakeFileSystemStream(Stream stream, string path) : FileSystemStream(stream, path, isAsync: false); } \ No newline at end of file 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 e061056d..d112b952 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 @@ -23,7 +23,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/EmpireAtWarMegBuilderTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/EmpireAtWarMegBuilderTest.cs index cb2d616e..79a7e5f1 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/EmpireAtWarMegBuilderTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Builders/EmpireAtWarMegBuilderTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -55,7 +55,7 @@ protected override void AddDataToBuilder(IReadOnlyCollection(); - var meg = megFileService.Load("new.meg"); + var megFileService = ServiceProvider.GetRequiredService(); + var meg = megFileService.LoadFile("new.meg"); Assert.Equal(4, meg.Archive.Count); @@ -148,11 +148,10 @@ public void BuildMeg() Assert.NotNull(packedEntry3); Assert.NotNull(packedEntry4); - var extractor = ServiceProvider.GetRequiredService(); - var entry1Data = extractor.GetData(new MegDataEntryLocationReference(meg, packedEntry1)); - var entry2Data = extractor.GetData(new MegDataEntryLocationReference(meg, packedEntry2)); - var entry3Data = extractor.GetData(new MegDataEntryLocationReference(meg, packedEntry3)); - var entry4Data = extractor.GetData(new MegDataEntryLocationReference(meg, packedEntry4)); + var entry1Data = meg.GetData(packedEntry1); + var entry2Data = meg.GetData(packedEntry2); + var entry3Data = meg.GetData(packedEntry3); + var entry4Data = meg.GetData(packedEntry4); using var ms = new MemoryStream(); entry1Data.CopyTo(ms); 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 6060f754..fb40631d 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,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -42,7 +42,7 @@ protected sealed override MegFileInformation CreateFileInfo(bool valid, string p { if (!FileInfoIsAlwaysValid && !valid) return CreateInvalidFileInfo(path); - return new MegFileInformation(path, MegFileVersion.V1); + return new MegFileInformation(path, MegVersion.V1); } protected virtual MegFileInformation CreateInvalidFileInfo(string path) @@ -159,7 +159,7 @@ public void Dispose_ThrowsOnAddingOrBuildingMethods() Assert.Throws(() => builder.AddEntry(new MegDataEntryLocationReference(CreateEmptyTestMeg(), MegDataEntryTest.CreateEntry("file.txt")))); Assert.Throws(() => - builder.Build(new MegFileInformation("a.meg", MegFileVersion.V1), false)); + builder.Build(new MegFileInformation("a.meg", MegVersion.V1), false)); Assert.DoesNotThrow(() => { _ = builder.DataEntries; }); Assert.DoesNotThrow(builder.Clear); @@ -493,7 +493,7 @@ public void AddEntry_Override() Assert.Single(builder.DataEntries); Assert.Same(addedFile.AddedBuilderInfo, resultSecondAdd.OverwrittenBuilderInfo); Assert.True(builder.DataEntries.First().OriginInfo.IsEntryReference); - Assert.Same(meg, builder.DataEntries.First().OriginInfo.MegFileLocation!.MegFile); + Assert.Same(meg, builder.DataEntries.First().OriginInfo.MegFileLocation!.Source); } } @@ -656,7 +656,7 @@ public void GetMinRequiredMegFiles_EntryTooLarge_Throws() var builder = new MaxFileSizeMegBuilder(uint.MaxValue, ServiceProvider, 36); builder.AddFile("1.txt", "1.txt"); - Assert.Throws(() => builder.GetMinRequiredMegFiles(MegFileVersion.V1)); + Assert.Throws(() => builder.GetMinRequiredMegFiles(MegVersion.V1)); } [Fact] @@ -669,7 +669,7 @@ public void GetMinRequiredMegFiles_SingleFile() builder.AddFile("1.txt", "1.txt"); builder.AddFile("2.txt", "2.txt"); - Assert.Equal(1, builder.GetMinRequiredMegFiles(MegFileVersion.V1)); + Assert.Equal(1, builder.GetMinRequiredMegFiles(MegVersion.V1)); } [Fact] @@ -682,7 +682,7 @@ public void GetMinRequiredMegFiles_MultipleFiles() builder.AddFile("1.txt", "1.txt"); builder.AddFile("2.txt", "2.txt"); - Assert.Equal(2, builder.GetMinRequiredMegFiles(MegFileVersion.V1)); + Assert.Equal(2, builder.GetMinRequiredMegFiles(MegVersion.V1)); } #endregion @@ -703,11 +703,10 @@ public void Build_FromBytes_RoundTrip() Assert.True(FileSystem.File.Exists("out.meg")); - var loaded = ServiceProvider.GetRequiredService().Load("out.meg"); + var loaded = ServiceProvider.GetRequiredService().LoadFile("out.meg"); Assert.Single(loaded.Archive); - var extractor = ServiceProvider.GetRequiredService(); - using var extracted = extractor.GetData(new MegDataEntryLocationReference(loaded, loaded.Archive[0])); + using var extracted = loaded.GetData(loaded.Archive[0]); var sink = new MemoryStream(); extracted.CopyTo(sink); Assert.Equal(contents, sink.ToArray()); @@ -780,7 +779,7 @@ private IMegFile CreateEmptyTestMeg() private IMegFile CreateTestMeg() { FileSystem.File.WriteAllBytes("test.meg", MegTestConstants.ContentMegFileV1); - return ServiceProvider.GetRequiredService().Load("test.meg"); + return ServiceProvider.GetRequiredService().LoadFile("test.meg"); } private class MaxFileSizeMegBuilder(uint maxFileSize, IServiceProvider services, uint? maxMegSize = null) : MegBuilderBase(services) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/Entry/BinaryMegDataEntryValidatorTestBase.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/Entry/BinaryMegDataEntryValidatorTestBase.cs index 0c3d3391..cce4e74d 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/Entry/BinaryMegDataEntryValidatorTestBase.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/Entry/BinaryMegDataEntryValidatorTestBase.cs @@ -37,7 +37,7 @@ public void Validate_OriginNotFound_EntryReference() { FileSystem.File.Create("file.meg"); var entry = MegDataEntryTest.CreateEntry("DUMMY", default, 0, 1); - var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + var meg = new MegFile(new MegArchive([]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); var location = new MegDataEntryLocationReference(meg, entry); var origin = new MegDataEntryOriginInfo(location); @@ -91,7 +91,7 @@ public SharedDataBuilder() { FileSystem.File.Create("file.meg"); _entry = MegDataEntryTest.CreateEntry("DUMMY", default, 0, 1); - _meg = new MegFile(new MegArchive([_entry]), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + _meg = new MegFile(new MegArchive([_entry]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); } public MegDataEntryBuilderInfo CreateInfo(string overridePath, bool encrypted = false) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidatorTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidatorTest.cs index 9be5335a..895e2222 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidatorTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidatorTest.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using PG.StarWarsGame.Files.MEG.Binary.Size; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Files; using PG.StarWarsGame.Files.MEG.Services.Builder.Validation; using PG.StarWarsGame.Files.MEG.Test.Files; @@ -35,7 +36,7 @@ public static IEnumerable ValidTestData() var data = new SharedDataBuilder(); yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V1), + data.CreateData(new MegFileInformation("path", MegVersion.V1), [data.CreateInfo("path")]) ]; } @@ -45,34 +46,34 @@ public static IEnumerable InvalidTestData() var data = new SharedDataBuilder(); yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V1), + data.CreateData(new MegFileInformation("path", MegVersion.V1), [data.CreateInfo("path", encrypted: true)]) ]; // Currently not supported. Tests will fail, as soon we do. Move to ValidTestData then. yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V2), + data.CreateData(new MegFileInformation("path", MegVersion.V2), [data.CreateInfo("path")]) ]; yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V3), + data.CreateData(new MegFileInformation("path", MegVersion.V3), [data.CreateInfo("path")]) ]; yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V3, MegEncryptionDataTest.CreateRandomData()), + data.CreateData(new MegFileInformation("path", MegVersion.V3, MegEncryptionDataTest.CreateRandomData()), [data.CreateInfo("path", encrypted: true)]) ]; yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V3, MegEncryptionDataTest.CreateRandomData()), + data.CreateData(new MegFileInformation("path", MegVersion.V3, MegEncryptionDataTest.CreateRandomData()), [data.CreateInfo("path")]) ]; yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V3, MegEncryptionDataTest.CreateRandomData()), + data.CreateData(new MegFileInformation("path", MegVersion.V3, MegEncryptionDataTest.CreateRandomData()), [ data.CreateInfo("path", encrypted: false), data.CreateInfo("path", encrypted: true) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidatorTestBase.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidatorTestBase.cs index ba6da3a0..e3f46cf1 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidatorTestBase.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidatorTestBase.cs @@ -22,7 +22,7 @@ public void Validate_Null_Throws() { var validator = CreateValidator(); Assert.Throws(() => validator.Validate(null!, [])); - Assert.Throws(() => validator.Validate(new MegFileInformation("p", MegFileVersion.V1), null!)); + Assert.Throws(() => validator.Validate(new MegFileInformation("p", MegVersion.V1), null!)); } [Theory] @@ -50,7 +50,7 @@ public void Validate_EntryFileNotFound_ReturnsInvalid() var fileInfo = FileSystem.FileInfo.New(filePath); var entryInfo = MegDataEntryBuilderInfo.FromFile(fileInfo, "PATH"); - var info = data.CreateData(new MegFileInformation("p", MegFileVersion.V1), [entryInfo]); + var info = data.CreateData(new MegFileInformation("p", MegVersion.V1), [entryInfo]); // Delete the file to trigger FileNotFoundException during RefreshSize FileSystem.File.Delete(filePath); @@ -70,7 +70,7 @@ public void Validate_EntryTooLarge_ReturnsInvalid() var entryInfo = MegDataEntryBuilderInfo.FromFile(bigFile, "ANY"); var info = - data.CreateData(new MegFileInformation("p", MegFileVersion.V1), [entryInfo]); + data.CreateData(new MegFileInformation("p", MegVersion.V1), [entryInfo]); // Now exceed the max size bigFile.Length = (long)MaxMegEntrySize + 1; @@ -85,11 +85,11 @@ public static IEnumerable BaseValidTestData() var data = new SharedDataBuilder(); yield return [ - data.CreateData(new MegFileInformation("p", MegFileVersion.V1), []) + data.CreateData(new MegFileInformation("p", MegVersion.V1), []) ]; yield return [ - data.CreateData(new MegFileInformation("p", MegFileVersion.V1), [data.CreateInfo("p1")]) + data.CreateData(new MegFileInformation("p", MegVersion.V1), [data.CreateInfo("p1")]) ]; } @@ -100,14 +100,14 @@ public static IEnumerable BaseInvalidTestData() // Encryption mismatch: Encrypted entries but no encryption data in FileInfo yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V3), + data.CreateData(new MegFileInformation("path", MegVersion.V3), [data.CreateInfo("path", encrypted: true)]) ]; // Encryption mismatch: No encrypted entries but encryption data in FileInfo yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V3, MegEncryptionDataTest.CreateRandomData()), + data.CreateData(new MegFileInformation("path", MegVersion.V3, MegEncryptionDataTest.CreateRandomData()), [data.CreateInfo("path")]) ]; } @@ -121,7 +121,7 @@ public void Validate_TotalSizeTooLarge_ReturnsInvalid() var entry1 = data.CreateInfo("p1"); // Size 1 var entry2 = data.CreateInfo("p2"); // Size 1 - var info = data.CreateData(new MegFileInformation("p", MegFileVersion.V1), [entry1, entry2]); + var info = data.CreateData(new MegFileInformation("p", MegVersion.V1), [entry1, entry2]); // Limit is 100, 62 is fine. Assert.True(validator.Validate(info.FileInformation, info.DataEntries).IsValid); @@ -129,7 +129,7 @@ public void Validate_TotalSizeTooLarge_ReturnsInvalid() // Now use a very large file to exceed 100 var bigFile = new MegTestConstants.FakeFileInfo("large_file.bin", 1000); var entry3 = MegDataEntryBuilderInfo.FromFile(bigFile, "p3"); - var info2 = data.CreateData(new MegFileInformation("p", MegFileVersion.V1), [entry3]); + var info2 = data.CreateData(new MegFileInformation("p", MegVersion.V1), [entry3]); var result = validator.Validate(info2.FileInformation, info2.DataEntries); Assert.False(result.IsValid); @@ -151,7 +151,7 @@ public SharedDataBuilder() { FileSystem.File.Create("file.meg").Dispose(); _entry = MegDataEntryTest.CreateEntry("DUMMY", default, 0, 1); - _meg = new MegFile(new MegArchive(new List { _entry }), new MegFileInformation("file.meg", MegFileVersion.V1), ServiceProvider); + _meg = new MegFile(new MegArchive(new List { _entry }), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); } public MegDataEntryBuilderInfo CreateInfo(string overridePath, bool encrypted = false) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/EmpireAtWarMegFileInformationValidatorTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/EmpireAtWarMegFileInformationValidatorTest.cs index 0d2e94b2..e097bbb4 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/EmpireAtWarMegFileInformationValidatorTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/Builder/Validation/FileInfo/EmpireAtWarMegFileInformationValidatorTest.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using PG.StarWarsGame.Files.MEG.Binary.Size; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Files; using PG.StarWarsGame.Files.MEG.Services.Builder.Validation; using PG.StarWarsGame.Files.MEG.Test.Files; @@ -36,7 +37,7 @@ public static IEnumerable ValidTestData() var data = new SharedDataBuilder(); yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V1), + data.CreateData(new MegFileInformation("path", MegVersion.V1), [data.CreateInfo("path")]) ]; } @@ -46,33 +47,33 @@ public static IEnumerable InvalidTestData() var data = new SharedDataBuilder(); yield return [ - data.CreateData(new MegFileInformation(new string('a', 260), MegFileVersion.V1), + data.CreateData(new MegFileInformation(new string('a', 260), MegVersion.V1), [data.CreateInfo("path")]) ]; yield return [ - data.CreateData(new MegFileInformation("pathÄ", MegFileVersion.V1), + data.CreateData(new MegFileInformation("pathÄ", MegVersion.V1), [data.CreateInfo("path")]) ]; yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V2), + data.CreateData(new MegFileInformation("path", MegVersion.V2), [data.CreateInfo("path")]) ]; yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V3), + data.CreateData(new MegFileInformation("path", MegVersion.V3), [data.CreateInfo("path")]) ]; yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V3, MegEncryptionDataTest.CreateRandomData()), + data.CreateData(new MegFileInformation("path", MegVersion.V3, MegEncryptionDataTest.CreateRandomData()), [data.CreateInfo("path", encrypted: true)]) ]; yield return [ - data.CreateData(new MegFileInformation("path", MegFileVersion.V3, MegEncryptionDataTest.CreateRandomData()), + data.CreateData(new MegFileInformation("path", MegVersion.V3, MegEncryptionDataTest.CreateRandomData()), [ data.CreateInfo("path", encrypted: false), data.CreateInfo("path", encrypted: true) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataSourceTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataSourceTest.cs new file mode 100644 index 00000000..2828779c --- /dev/null +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataSourceTest.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Files.MEG.Data; +using PG.StarWarsGame.Files.MEG.Data.Archives; +using PG.StarWarsGame.Files.MEG.Files; +using PG.StarWarsGame.Files.MEG.Services; +using PG.StarWarsGame.Files.MEG.Test.Files; +using Testably.Abstractions.Testing; +using Xunit; +using static PG.StarWarsGame.Files.MEG.Test.Data.Entries.MegDataEntryTest; + +namespace PG.StarWarsGame.Files.MEG.Test.Services; + +public class FileMegDataSourceTest : MegDataSourceTestSuite +{ + protected override IMegDataSource CreateMegDataSource(byte[] megBytes) + { + FileSystem.File.WriteAllBytes("test.meg", megBytes); + return ServiceProvider.GetRequiredService().LoadFile("test.meg"); + } + + [Fact] + public void GetData_FileCannotBeRead_Throws() + { + var entry = CreateEntry("file.txt", default, 0, 12); + + _ = FileSystem.File.Create("test.meg"); // intentionally left open so the file is locked + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("test.meg", MegVersion.V1), ServiceProvider); + + Assert.Throws(() => meg.GetData(entry)); + } + + [Fact] + public void GetData_EmptyEntry_BackingFileMissing_Throws() + { + // A zero-size entry whose backing file is gone still fails. + FileSystem.Initialize().WithFile("test.meg"); + var entry = CreateEntry("file.txt", offset: 2, size: 0); + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("test.meg", MegVersion.V1), ServiceProvider); + + FileSystem.File.Delete("test.meg"); + + Assert.Throws(() => meg.GetData(entry)); + } + + [Fact] + public void Dispose_ViaInterface_DisposesHolder() + { + // The MegFile holder owns its FileInformation (and any encryption material on it). + // Disposing via the IMegDataSource interface must flow through to the holder's disposal. + FileSystem.Initialize().WithFile("test.meg"); + var megFile = new MegFile( + new MegArchive([]), + new MegFileInformation("test.meg", MegVersion.V3, MegEncryptionDataTest.CreateRandomData()), + ServiceProvider); + IMegDataSource source = megFile; + + source.Dispose(); + + Assert.Throws(() => megFile.FileInformation); + } +} + +public class InMemoryMegDataSourceTest : MegDataSourceTestSuite +{ + protected override IMegDataSource CreateMegDataSource(byte[] megBytes) + { + return ServiceProvider.GetRequiredService().LoadArchive(megBytes); + } +} diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataSourceTestSuite.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataSourceTestSuite.cs new file mode 100644 index 00000000..b6af3c4b --- /dev/null +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataSourceTestSuite.cs @@ -0,0 +1,102 @@ +using System; +using System.IO; +using AnakinRaW.CommonUtilities.Testing; +using AnakinRaW.CommonUtilities.Testing.Extensions; +using PG.StarWarsGame.Files.MEG.Data; +using PG.StarWarsGame.Files.MEG.Test.Binary.Reader.V1; +using PG.StarWarsGame.Files.MEG.Test.Data.Entries; +using Xunit; + +namespace PG.StarWarsGame.Files.MEG.Test.Services; + +public abstract class MegDataSourceTestSuite : CommonMegTestBase +{ + protected abstract IMegDataSource CreateMegDataSource(byte[] megBytes); + + [Fact] + public void GetData_ReturnsEntryContent() + { + var source = CreateMegDataSource(MegTestConstants.ContentMegFileV1); + + Assert.Equal(2, source.Archive.Count); + Assert.Equal(MegTestConstants.CampaignFilesContent, ReadAll(source.GetData(source.Archive[0]))); + Assert.Equal(MegTestConstants.GameObjectFilesContent, ReadAll(source.GetData(source.Archive[1]))); + } + + [Fact] + public void GetData_FromUnorderedMeg() + { + var bytes = TestingHelpers.GetEmbeddedResourceAsByteArray(typeof(MegFileBinaryReaderV1IntegrationTest), + "Files.v1_out_of_order.meg"); + var source = CreateMegDataSource(bytes); + + using var ms = new MemoryStream(); + using (var stream = source.GetData(source.Archive[0])) + stream.CopyTo(ms); + using (var stream = source.GetData(source.Archive[1])) + stream.CopyTo(ms); + + Assert.Equal("456123"u8, ms.ToArray()); + Assert.True(source.Archive[0].Location.Offset > source.Archive[1].Location.Offset); + } + + [Fact] + public void GetData_NullEntry_Throws() + { + var source = CreateMegDataSource(MegTestConstants.ContentMegFileV1); + Assert.Throws(() => source.GetData(null!)); + } + + [Fact] + public void GetData_EntryNotInArchive_Throws() + { + var source = CreateMegDataSource(MegTestConstants.ContentMegFileV1); + Assert.Throws(() => source.GetData(MegDataEntryTest.CreateEntry("not/in/archive.xml"))); + } + + [Fact] + public void GetData_EmptyEntry_ReturnsEmptyStream() + { + var source = CreateMegDataSource(MegTestConstants.EmptyEntryMegFileV1); + + var entry = Assert.Single(source.Archive); + Assert.Equal(0u, entry.Location.Size); + + using var stream = source.GetData(entry); + Assert.Equal(0, stream.Length); + } + + [Fact] + public void GetData_ReturnsIndependentStreams() + { + var source = CreateMegDataSource(MegTestConstants.ContentMegFileV1); + + using var s1 = source.GetData(source.Archive[0]); + using var s2 = source.GetData(source.Archive[0]); + + s1.ReadByte(); + + // Reading from one stream must not move the position of the other. + Assert.Equal(1, s1.Position); + Assert.Equal(0, s2.Position); + } + + [Fact] + public void Dispose_IsIdempotent() + { + var source = CreateMegDataSource(MegTestConstants.ContentMegFileV1); + + source.Dispose(); + Assert.DoesNotThrow(source.Dispose); + } + + private static byte[] ReadAll(Stream stream) + { + using (stream) + { + using var ms = new MemoryStream(); + stream.CopyTo(ms); + return ms.ToArray(); + } + } +} 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 deleted file mode 100644 index 030e8a6c..00000000 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegDataStreamFactoryTest.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.IO; -using PG.StarWarsGame.Files.MEG.Data.Archives; -using PG.StarWarsGame.Files.MEG.Data.EntryLocations; -using PG.StarWarsGame.Files.MEG.Files; -using PG.StarWarsGame.Files.MEG.Services; -using PG.StarWarsGame.Files.MEG.Test.Data.Entries; -using Testably.Abstractions.Testing; -using Xunit; - -namespace PG.StarWarsGame.Files.MEG.Test.Services; - -public class MegDataStreamFactoryTest : CommonMegTestBase -{ - private readonly MegDataStreamFactory _streamFactory; - - public MegDataStreamFactoryTest() - { - _streamFactory = new MegDataStreamFactory(ServiceProvider); - } - - [Fact] - public void Ctor_Throws() - { - Assert.Throws(() => new MegDataStreamFactory(null!)); - } - - [Fact] - public void GetDataStream_Throws() - { - Assert.Throws(() => _streamFactory.GetStream((MegDataEntryOriginInfo)null!)); - Assert.Throws(() => _streamFactory.GetStream((MegDataEntryLocationReference)null!)); - } - - [Fact] - public void GetFileData_OriginInfo_Throws_FileNotFound() - { - var originInfo = new MegDataEntryOriginInfo(FileSystem.FileInfo.New("test.txt")); - Assert.Throws(() => _streamFactory.GetStream(originInfo)); - } - - [Fact] - public void GetFileData_OriginInfo_File() - { - FileSystem.Initialize().WithFile("test.txt").Which(m => m.HasBytesContent([1,2,3])); - - var originInfo = new MegDataEntryOriginInfo(FileSystem.FileInfo.New("test.txt")); - var stream = _streamFactory.GetStream(originInfo); - Assert.Equal(3, stream.Length); - - var resultStream = new MemoryStream(new byte[3]); - stream.CopyTo(resultStream); - Assert.Equal([1, 2, 3], resultStream.ToArray()); - } - - [Fact] - public void GetFileData_OriginInfo_LocationReference() - { - FileSystem.Initialize().WithFile("a.meg").Which(m => m.HasBytesContent([1, 2, 3, 4, 5])); - - var entry = MegDataEntryTest.CreateEntry("file.txt", offset: 1, size: 2); - - var archive = new MegArchive([entry]); - - var meg = new MegFile(archive, new MegFileInformation("a.meg", MegFileVersion.V1), ServiceProvider); - - var originInfo = new MegDataEntryOriginInfo(new MegDataEntryLocationReference(meg, entry)); - - var stream = _streamFactory.GetStream(originInfo); - Assert.Equal(2, stream.Length); - - var resultStream = new MemoryStream(new byte[2]); - stream.CopyTo(resultStream); - Assert.Equal([2, 3], resultStream.ToArray()); - } - - - [Fact] - public void GetFileData_LocationReference_Throws_FileNotInMeg() - { - FileSystem.Initialize().WithFile("a.meg"); - var entry = MegDataEntryTest.CreateEntry("file.txt"); - - var archive = new MegArchive([]); - - var meg = new MegFile(archive, new MegFileInformation("a.meg", MegFileVersion.V1), - ServiceProvider); - - var location = new MegDataEntryLocationReference(meg, entry); - - Assert.Throws(() => _streamFactory.GetStream(location)); - } - - [Fact] - public void GetFileData_LocationReference_EmptyData_MegFileNotExists_Throws() - { - FileSystem.Initialize().WithFile("a.meg"); - - var entry = MegDataEntryTest.CreateEntry("file.txt", offset: 2, size: 0); - - var archive = new MegArchive([entry]); - - var meg = new MegFile(archive, new MegFileInformation("a.meg", MegFileVersion.V1), - ServiceProvider); - - FileSystem.File.Delete("a.meg"); - - var location = new MegDataEntryLocationReference(meg, entry); - - Assert.Throws(() => _streamFactory.GetStream(location)); - } - - [Fact] - public void GetFileData_LocationReference_EmptyDataFile() - { - FileSystem.Initialize().WithFile("a.meg"); - var entry = MegDataEntryTest.CreateEntry("file.txt", offset: 2, size: 0); - - var archive = new MegArchive([entry]); - - var meg = new MegFile(archive, new MegFileInformation("a.meg", MegFileVersion.V1), - ServiceProvider); - - var location = new MegDataEntryLocationReference(meg, entry); - - var stream = _streamFactory.GetStream(location); - 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([]); - - using var resultStream = _streamFactory.GetStream(originInfo); - Assert.Equal(0, resultStream.Length); - } - - [Fact] - public void GetFileData_LocationReference_File() - { - FileSystem.Initialize().WithFile("a.meg").Which(m => m.HasBytesContent([1, 2, 3, 4, 5])); - - var entry = MegDataEntryTest.CreateEntry("file.txt", offset: 1, size: 2); - - var archive = new MegArchive([entry]); - - var meg = new MegFile(archive, new MegFileInformation("a.meg", MegFileVersion.V1), - ServiceProvider); - - var location = new MegDataEntryLocationReference(meg, entry); - - var stream = _streamFactory.GetStream(location); - Assert.Equal(2, stream.Length); - - var resultStream = new MemoryStream(new byte[2]); - stream.CopyTo(resultStream); - Assert.Equal([2, 3], resultStream.ToArray()); - } -} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegFileExtractorTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegFileExtractorTest.cs index ff2f3039..bf3908ce 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegFileExtractorTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegFileExtractorTest.cs @@ -1,11 +1,10 @@ -using AnakinRaW.CommonUtilities.Testing; using AnakinRaW.CommonUtilities.Testing.Attributes; using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; using PG.StarWarsGame.Files.MEG.Files; using PG.StarWarsGame.Files.MEG.Services; -using PG.StarWarsGame.Files.MEG.Test.Binary.Reader.V1; using System; using System.IO; using Testably.Abstractions.Testing; @@ -17,10 +16,12 @@ namespace PG.StarWarsGame.Files.MEG.Test.Services; public class MegFileExtractorTest : CommonMegTestBase { private readonly MegFileExtractor _extractor; + private readonly IMegService _megService; public MegFileExtractorTest() { _extractor = new MegFileExtractor(ServiceProvider); + _megService = ServiceProvider.GetRequiredService(); } [Fact] @@ -82,27 +83,6 @@ public void GetAbsoluteFilePath_ThrowsInvalidOperation() Assert.Throws(() => _extractor.GetAbsolutePath(entry, "someRoot", false)); } - [Fact] - public void GetFileData_ThrowsArgumentNull() - { - Assert.Throws(() => _extractor.GetData(null!)); - } - - [Fact] - public void GetFileData_CannotReadFile_Throws() - { - // Size 12 is not valid, as the entry does not really exist. - var entry = CreateEntry("file.txt", default, 0, 12); - - FileSystem.File.Create("test.meg"); - var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("test.meg", MegFileVersion.V1), - ServiceProvider); - - var location = new MegDataEntryLocationReference(meg, entry); - - Assert.Throws(() => _extractor.GetData(location)); - } - [Fact] public void ExtractFile_ThrowsArgumentsIncorrect() { @@ -114,7 +94,7 @@ public void ExtractFile_ThrowsArgumentsIncorrect() { } - var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("test.meg", MegFileVersion.V1), + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("test.meg", MegVersion.V1), ServiceProvider); var location = new MegDataEntryLocationReference(meg, entry); @@ -133,7 +113,7 @@ public void ExtractData_Throws_IllegalPath_Windows(string filePathWhereToExtract FileSystem.Initialize().WithFile("a.meg"); var entry = CreateEntry("path"); - var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("a.meg", MegFileVersion.V1), + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("a.meg", MegVersion.V1), ServiceProvider); var location = new MegDataEntryLocationReference(meg, entry); @@ -147,7 +127,7 @@ public void ExtractData_Throws_IllegalPath_Linux(string filePathWhereToExtract) FileSystem.Initialize().WithFile("a.meg"); var entry = CreateEntry("path"); - var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("a.meg", MegFileVersion.V1), + var meg = new MegFile(new MegArchive([entry]), new MegFileInformation("a.meg", MegVersion.V1), ServiceProvider); var location = new MegDataEntryLocationReference(meg, entry); @@ -155,54 +135,13 @@ public void ExtractData_Throws_IllegalPath_Linux(string filePathWhereToExtract) Assert.Throws(() => _extractor.ExtractEntry(location, filePathWhereToExtract, false)); } - [Fact] - public void GetFileData() - { - FileSystem.Initialize() - .WithFile("test.meg").Which(m => m.HasBytesContent(MegTestConstants.ContentMegFileV1)); - - var meg = ServiceProvider.GetRequiredService().Load("test.meg"); - - // CampaignFiles.xml - var entry = meg.Content[0]; - var location = new MegDataEntryLocationReference(meg, entry); - - using var stream = _extractor.GetData(location); - - var ms = new MemoryStream(); - stream.CopyTo(ms); - - Assert.Equal(MegTestConstants.CampaignFilesContent, ms.ToArray()); - } - - [Fact] - public void GetFileData_FromUnorderedMeg() - { - var unorderedMeg = TestingHelpers.GetEmbeddedResourceAsByteArray(typeof(MegFileBinaryReaderV1IntegrationTest), "Files.v1_out_of_order.meg"); - - FileSystem.Initialize() - .WithFile("test.meg").Which(m => m.HasBytesContent(unorderedMeg)); - - var meg = ServiceProvider.GetRequiredService().Load("test.meg"); - - var ms = new MemoryStream(); - - using (var stream = _extractor.GetData(new MegDataEntryLocationReference(meg, meg.Content[0]))) - stream.CopyTo(ms); - using (var stream = _extractor.GetData(new MegDataEntryLocationReference(meg, meg.Content[1]))) - stream.CopyTo(ms); - - Assert.Equal("456123"u8, ms.ToArray()); - Assert.True(meg.Content[0].Location.Offset > meg.Content[1].Location.Offset); - } - [Fact] public void ExtractData_NoOverwrite() { FileSystem.Initialize() .WithFile("test.meg").Which(m => m.HasBytesContent(MegTestConstants.ContentMegFileV1)); - var meg = ServiceProvider.GetRequiredService().Load("test.meg"); + var meg = ServiceProvider.GetRequiredService().LoadFile("test.meg"); // CampaignFiles.xml var entry = meg.Content[0]; @@ -239,7 +178,7 @@ public void ExtractData_Overwrite() Assert.Equal(existingFileData, FileSystem.File.ReadAllBytes("file.txt")); - var meg = ServiceProvider.GetRequiredService().Load("test.meg"); + var meg = _megService.LoadFile("test.meg"); // CampaignFiles.xml var entry = meg.Content[0]; @@ -260,7 +199,7 @@ public void ExtractData_CreateDirectories() FileSystem.Initialize() .WithFile("test.meg").Which(m => m.HasBytesContent(MegTestConstants.ContentMegFileV1)); - var meg = ServiceProvider.GetRequiredService().Load("test.meg"); + var meg = _megService.LoadFile("test.meg"); // CampaignFiles.xml var entry = meg.Content[0]; @@ -273,4 +212,49 @@ public void ExtractData_CreateDirectories() var actualFileData = FileSystem.File.ReadAllBytes(filePathWhereToExtract); Assert.Equal(MegTestConstants.CampaignFilesContent, actualFileData); } + + [Fact] + public void ExtractEntry_FromInMemoryArchive_WritesContentToFile() + { + var source = LoadTestArchive(); + var location = new MegDataEntryLocationReference(source, source.Archive[0]); + + var extracted = _extractor.ExtractEntry(location, "file.txt", false); + + Assert.True(extracted); + Assert.True(FileSystem.File.Exists("file.txt")); + Assert.Equal(MegTestConstants.CampaignFilesContent, FileSystem.File.ReadAllBytes("file.txt")); + } + + [Fact] + public void ExtractEntry_NoOverwrite_SkipsExistingFile() + { + var source = LoadTestArchive(); + var location = new MegDataEntryLocationReference(source, source.Archive[0]); + FileSystem.File.WriteAllText("file.txt", "existing"); + + var extracted = _extractor.ExtractEntry(location, "file.txt", false); + + Assert.False(extracted); + Assert.Equal("existing", FileSystem.File.ReadAllText("file.txt")); + } + + [Fact] + public void ExtractEntry_Overwrite_ReplacesExistingFile() + { + var source = LoadTestArchive(); + var location = new MegDataEntryLocationReference(source, source.Archive[0]); + FileSystem.File.WriteAllText("file.txt", "existing"); + + var extracted = _extractor.ExtractEntry(location, "file.txt", true); + + Assert.True(extracted); + Assert.Equal(MegTestConstants.CampaignFilesContent, FileSystem.File.ReadAllBytes("file.txt")); + } + + private IMegDataSource LoadTestArchive() + { + using var stream = new MemoryStream(MegTestConstants.ContentMegFileV1); + return _megService.LoadArchive(stream); + } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegFileServiceIntegrationTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegFileServiceIntegrationTest.cs deleted file mode 100644 index 1caf6d85..00000000 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegFileServiceIntegrationTest.cs +++ /dev/null @@ -1,397 +0,0 @@ -using PG.StarWarsGame.Files.MEG.Utilities; -using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Files.Binary; -using PG.StarWarsGame.Files.MEG.Data; -using PG.StarWarsGame.Files.MEG.Data.Archives; -using PG.StarWarsGame.Files.MEG.Data.EntryLocations; -using PG.StarWarsGame.Files.MEG.Files; -using PG.StarWarsGame.Files.MEG.Services; -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Abstractions; -using System.Linq; -using AnakinRaW.CommonUtilities.Testing; -using Testably.Abstractions.Testing; -using Xunit; - -namespace PG.StarWarsGame.Files.MEG.Test.Services; - -public class MegFileServiceIntegrationTest : CommonMegTestBase -{ - private readonly IMegFileService _megFileService; - - public MegFileServiceIntegrationTest() - { - _megFileService = ServiceProvider.GetRequiredService(); - } - - #region Create Meg Archive - - [Fact] - public void CreateMegArchive_Throws() - { - Assert.Throws(() => _megFileService.CreateMegArchive(null!, MegFileVersion.V1, null, new List())); - Assert.Throws(() => - { - using var fs = FileSystem.File.OpenWrite("path"); - _megFileService.CreateMegArchive(fs, MegFileVersion.V3, null, null!); - }); - } - - [Fact] - public void CreateMegArchive_DoesNotCreateDirectories_Throws() - { - const string megFileName = "test/a.meg"; - - Assert.False(FileSystem.Directory.Exists("test")); - - Assert.Throws(() => - { - using var fs = FileSystem.File.OpenWrite(megFileName); - _megFileService.CreateMegArchive(fs, MegFileVersion.V1, null, []); - }); - } - - [Fact] - public void CreateMegArchive_EntryNotFoundInMeg_Throws() - { - const string megFileName = "test.meg"; - const string dummyMegFile = "dummy.meg"; - const string newFileName = "new.meg"; - - FileSystem.Initialize() - .WithFile(megFileName).Which(m => m.HasBytesContent(MegTestConstants.ContentMegFileV1)) - .WithFile(dummyMegFile).Which(m => m.HasBytesContent([0, 0, 0, 0, 0, 0, 0, 0])); - - var meg = _megFileService.Load(megFileName); - - var dummyMeg = new MegFile(new MegArchive([]), new MegFileInformation(dummyMegFile, MegFileVersion.V1), - ServiceProvider); - - var builderInfo = new List - { - new(new MegDataEntryOriginInfo(new MegDataEntryLocationReference(dummyMeg, meg.Archive[0]))) - }; - - Assert.Throws(() => - { - using var fs = FileSystem.File.OpenWrite(newFileName); - _megFileService.CreateMegArchive(fs, meg.FileInformation.FileVersion, null, builderInfo); - }); - } - - private sealed class ManualStreamFactory(Stream streamToReturn) : IMegDataStreamFactory - { - public Stream GetStream(MegDataEntryOriginInfo originInfo) => streamToReturn; - public MegEntryStream GetStream(MegDataEntryLocationReference locationReference) => throw new NotImplementedException(); - } - - [Theory] - [InlineData(CreateMegArchiveInvalidOperationType.DataEntrySizeMismatch)] - [InlineData(CreateMegArchiveInvalidOperationType.FilePositionMismatch)] - public void CreateMegArchive_ThrowsInvalidOperationException(CreateMegArchiveInvalidOperationType type) - { - const string megFileName = "new.meg"; - const string entryFileName = "file.txt"; - - FileSystem.File.WriteAllBytes(entryFileName, [1, 2, 3]); - var fileInfo = FileSystem.FileInfo.New(entryFileName); - var builderInfo = MegDataEntryBuilderInfo.FromFile(fileInfo, entryFileName); - - Stream streamToReturn = type switch - { - CreateMegArchiveInvalidOperationType.DataEntrySizeMismatch => new MemoryStream([1, 2, 3, 4]), - _ => new MemoryStream([1, 2, 3]) - }; - - var manualFactory = new ManualStreamFactory(streamToReturn); - - var sc = new ServiceCollection(); - SetupServices(sc); - sc.AddSingleton(FileSystem); - sc.AddSingleton(manualFactory); - var serviceProvider = sc.BuildServiceProvider(); - - var megFileService = serviceProvider.GetRequiredService(); - - var ex = Assert.Throws(() => - { - using var fs = FileSystem.File.OpenWrite(megFileName); - if (type == CreateMegArchiveInvalidOperationType.FilePositionMismatch) - fs.WriteByte(0); - megFileService.CreateMegArchive(fs, MegFileVersion.V1, null, [builderInfo]); - }); - - var expectedMessagePart = type switch - { - CreateMegArchiveInvalidOperationType.DataEntrySizeMismatch => "Actual data entry size", - CreateMegArchiveInvalidOperationType.FilePositionMismatch => "Actual file position", - _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) - }; - - Assert.Contains(expectedMessagePart, ex.Message); - } - - [Fact] - public void CreateMegArchive_MegWithEntriesOfSameNameButWithDifferentData() - { - const string megFileName = "test.meg"; - - var expectedBytes = new byte[] - { - 2, 0, 0, 0, 2, 0, 0, 0, // Header - 4, 0, 102, 105, 108, 101, // "file" - 4, 0, 102, 105, 108, 101, // "file" - 16, 54, 159, 140, 0, 0, 0, 0, 3, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, - 16, 54, 159, 140, 1, 0, 0, 0, 3, 0, 0, 0, 63, 0, 0, 0, 1, 0, 0, 0, - 49, 50, 51, // 123 - 52, 53, 54 // 456 - }; - - - FileSystem.Initialize().WithFile("1.txt").Which(m => m.HasStringContent("123")); - FileSystem.Initialize().WithFile("2.txt").Which(m => m.HasStringContent("456")); - - var builderInfo = new List - { - MegDataEntryBuilderInfo.FromFile(FileSystem.FileInfo.New("1.txt"), "file"), - MegDataEntryBuilderInfo.FromFile(FileSystem.FileInfo.New("2.txt"), "file") - }; - - using (var fs = FileSystem.File.OpenWrite(megFileName)) - { - _megFileService.CreateMegArchive(fs, MegFileVersion.V1, null, builderInfo); - } - - var bytes = FileSystem.File.ReadAllBytes(megFileName); - - Assert.Equal(expectedBytes, bytes); - } - - #endregion - - #region Generic Read - - [Fact] - public void Load_InvalidBinary() - { - const string megFileName = "test.meg"; - const string fileData = "some random data"; - - FileSystem.Initialize().WithFile(megFileName).Which(m => m.HasStringContent(fileData)); - - Assert.Throws(() => _megFileService.Load(megFileName)); - } - - [Fact] - public void Load_ThrowFileNotFound() - { - Assert.Throws(() => _megFileService.Load("notFound.meg")); - } - - [Fact] - public void Load_NullArgs() - { - Assert.Throws(() => _megFileService.Load((string)null!)); - Assert.Throws(() => _megFileService.Load((FileSystemStream)null!)); - } - - #endregion - - #region Read / Write V1 - - [Fact] - public void MegV1_WithEntries() - { - const string megFileName = "test.meg"; - - FileSystem.Initialize().WithFile(megFileName).Which(m => m.HasBytesContent(MegTestConstants.ContentMegFileV1)); - - var expectedData = new ExpectedMegTestData - { - IsMegFileVersion = MegFileVersion.V1, - IsMegEncrypted = false, - MegFileCount = 2, - EntryNames = new List - { - "DATA\\XML\\CAMPAIGNFILES.XML", - "DATA\\XML\\GAMEOBJECTFILES.XML" - }, - NewMegFilePath = "new.meg", - NewMegFileVersion = MegFileVersion.V1, - NewMegIsBinaryEqual = true - }; - TestMegFiles(megFileName, expectedData); - } - - [Fact] - public void MegV1_Empty() - { - const string megFileName = "test.meg"; - const string megResource = "Files.v1_empty.meg"; - - FileSystem.Initialize().WithFile(megFileName) - .Which(m => m.HasBytesContent(TestingHelpers.GetEmbeddedResourceAsByteArray(GetType(), megResource))); - - var expectedData = new ExpectedMegTestData - { - IsMegFileVersion = MegFileVersion.V1, - IsMegEncrypted = false, - MegFileCount = 0, - EntryNames = new List(), - NewMegFilePath = "new.meg", - NewMegFileVersion = MegFileVersion.V1, - NewMegIsBinaryEqual = true - }; - TestMegFiles(megFileName, expectedData); - } - - [Fact] - public void MegV1_EntriesHaveNonAsciiNames() - { - const string megFileName = "test.meg"; - const string megResource = "Files.v1_2_files_with_extended_ascii_name.meg"; - - FileSystem.Initialize().WithFile(megFileName) - .Which(m => m.HasBytesContent(TestingHelpers.GetEmbeddedResourceAsByteArray(GetType(), megResource))); - - var expectedData = new ExpectedMegTestData - { - IsMegFileVersion = MegFileVersion.V1, - IsMegEncrypted = false, - MegFileCount = 2, - EntryNames = new List - { - "TEST?.TXT", - "TEST?.TXT" - }, - NewMegFilePath = "new.meg", - NewMegFileVersion = MegFileVersion.V1, - NewMegIsBinaryEqual = false - }; - - TestMegFiles(megFileName, expectedData); - } - - #endregion - - #region GetFileVersion - - [Fact] - public void GetMegFileVersion() - { - WriteMegFromResources("Files.v1_1_file_data.meg", "v1.meg"); - WriteMegFromResources("Files.v2_2_files_data.meg", "v2.meg"); - WriteMegFromResources("Files.v3n_2_files_data.meg", "v3.meg"); - - Assert.Equal(MegFileVersion.V1, _megFileService.GetMegFileVersion("v1.meg", out var encrypted)); - Assert.False(encrypted); - Assert.Equal(MegFileVersion.V2, _megFileService.GetMegFileVersion("v2.meg", out encrypted)); - Assert.False(encrypted); - Assert.Equal(MegFileVersion.V3, _megFileService.GetMegFileVersion("v3.meg", out encrypted)); - Assert.False(encrypted); - } - - private void WriteMegFromResources(string resourceName, string fileName) - { - using var rs = TestingHelpers.GetEmbeddedResource(GetType(), resourceName); - using var fs = FileSystem.File.OpenWrite(fileName); - rs.CopyTo(fs); - } - - - [Fact] - public void GetMegFileVersion_Throws_FileNotFound() - { - Assert.Throws(() => _megFileService.GetMegFileVersion("test.meg", out _)); - } - - #endregion - - private void TestMegFiles(string megFilePath, ExpectedMegTestData expectedData) - { - var megVersion = _megFileService.GetMegFileVersion(megFilePath, out var encrypted); - Assert.Equal(expectedData.IsMegFileVersion, megVersion); - Assert.Equal(expectedData.IsMegEncrypted, encrypted); - - var meg = _megFileService.Load(megFilePath); - TestMegModelContent(meg, expectedData, false); - - for (var i = 0; i < meg.Archive.Count; i++) - { - var entry = meg.Archive[i]; - var expected = expectedData.EntryNames[i]; - Assert.Equal(expected, entry.Path); - } - - using var param = new MegFileInformation( - expectedData.NewMegFilePath, - expectedData.NewMegFileVersion, - expectedData.EncryptionData); - - var builderInformation = meg.Archive.Select(e => - new MegDataEntryBuilderInfo(new MegDataEntryOriginInfo(new MegDataEntryLocationReference(meg, e)))); - - using (var fs = FileSystem.File.OpenWrite(expectedData.NewMegFilePath)) - { - _megFileService.CreateMegArchive(fs, expectedData.NewMegFileVersion, expectedData.EncryptionData, - builderInformation); - } - - Assert.True(FileSystem.File.Exists(expectedData.NewMegFilePath)); - - var createdVersion = _megFileService.GetMegFileVersion(expectedData.NewMegFilePath, out var newEncrypted); - Assert.Equal(expectedData.NewMegFileVersion, createdVersion); - Assert.Equal(expectedData.EncryptionData is null, !newEncrypted); - - - var actualBytes = FileSystem.File.ReadAllBytes(expectedData.NewMegFilePath); - var expectedBytes = FileSystem.File.ReadAllBytes(megFilePath); - if (expectedData.NewMegIsBinaryEqual) - Assert.Equal(expectedBytes, actualBytes); - else - Assert.NotEqual(expectedBytes, actualBytes); - - var newMeg = _megFileService.Load(megFilePath); - TestMegModelContent(newMeg, expectedData, true); - } - - private static void TestMegModelContent(IMegFile meg, ExpectedMegTestData expectedData, bool isNewMeg) - { - Assert.NotNull(meg); - Assert.Equal(expectedData.MegFileCount, meg.Content.Count); - Assert.Equal(expectedData.IsMegFileVersion, meg.FileInformation.FileVersion); - Assert.Equal(expectedData.EntryNames.Count, meg.Archive.Count); - - if (isNewMeg) - Assert.Equal(expectedData.EncryptionData is null, !meg.FileInformation.HasEncryption); - else - Assert.Equal(expectedData.IsMegEncrypted, meg.FileInformation.HasEncryption); - - for (var i = 0; i < meg.Archive.Count; i++) - { - var entry = meg.Archive[i]; - var expected = expectedData.EntryNames[i]; - Assert.Equal(expected, entry.Path); - } - } - - public enum CreateMegArchiveInvalidOperationType - { - DataEntrySizeMismatch, - FilePositionMismatch, - } - - private record ExpectedMegTestData - { - public MegFileVersion IsMegFileVersion { get; init; } - public bool IsMegEncrypted { get; init; } - public int MegFileCount { get; init; } - public IList EntryNames { get; init; } = null!; - public string NewMegFilePath { get; init; } = null!; - public bool NewMegIsBinaryEqual { get; init; } - public MegFileVersion NewMegFileVersion { get; init; } - public MegEncryptionData? EncryptionData { get; } - } -} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegServiceIntegrationTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegServiceIntegrationTest.cs new file mode 100644 index 00000000..22ea8f3f --- /dev/null +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/MegServiceIntegrationTest.cs @@ -0,0 +1,655 @@ +using AnakinRaW.CommonUtilities.Hashing; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons; +using PG.StarWarsGame.Files.Binary; +using PG.StarWarsGame.Files.MEG.Data; +using PG.StarWarsGame.Files.MEG.Data.Archives; +using PG.StarWarsGame.Files.MEG.Data.EntryLocations; +using PG.StarWarsGame.Files.MEG.Files; +using PG.StarWarsGame.Files.MEG.Services; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using System.Linq; +using AnakinRaW.CommonUtilities.Testing; +using PG.Testing; +using Testably.Abstractions.Testing; +using Xunit; + +namespace PG.StarWarsGame.Files.MEG.Test.Services; + +public class MegServiceIntegrationTest : CommonMegTestBase +{ + private const string ContentMegFileName = "test.meg"; + + private readonly IMegService _megService; + + public MegServiceIntegrationTest() + { + _megService = ServiceProvider.GetRequiredService(); + } + + #region Create Meg Archive + + [Fact] + public void CreateMegArchive_Throws() + { + Assert.Throws(() => _megService.CreateMegArchive(null!, MegVersion.V1, null, new List())); + Assert.Throws(() => + { + using var fs = FileSystem.File.OpenWrite("path"); + _megService.CreateMegArchive(fs, MegVersion.V3, null, null!); + }); + } + + [Fact] + public void CreateMegArchive_DoesNotCreateDirectories_Throws() + { + const string megFileName = "test/a.meg"; + + Assert.False(FileSystem.Directory.Exists("test")); + + Assert.Throws(() => + { + using var fs = FileSystem.File.OpenWrite(megFileName); + _megService.CreateMegArchive(fs, MegVersion.V1, null, []); + }); + } + + [Fact] + public void CreateMegArchive_EntryNotFoundInMeg_Throws() + { + const string megFileName = "test.meg"; + const string dummyMegFile = "dummy.meg"; + const string newFileName = "new.meg"; + + FileSystem.Initialize() + .WithFile(megFileName).Which(m => m.HasBytesContent(MegTestConstants.ContentMegFileV1)) + .WithFile(dummyMegFile).Which(m => m.HasBytesContent([0, 0, 0, 0, 0, 0, 0, 0])); + + var meg = _megService.LoadFile(megFileName); + + var dummyMeg = new MegFile(new MegArchive([]), new MegFileInformation(dummyMegFile, MegVersion.V1), + ServiceProvider); + + var builderInfo = new List + { + new(new MegDataEntryOriginInfo(new MegDataEntryLocationReference(dummyMeg, meg.Archive[0]))) + }; + + Assert.Throws(() => + { + using var fs = FileSystem.File.OpenWrite(newFileName); + _megService.CreateMegArchive(fs, meg.FileInformation.Version, null, builderInfo); + }); + } + + [Fact] + public void CreateMegArchive_FilePositionMismatch_ThrowsInvalidOperationException() + { + const string megFileName = "new.meg"; + const string entryFileName = "file.txt"; + + FileSystem.File.WriteAllBytes(entryFileName, [1, 2, 3]); + var builderInfo = MegDataEntryBuilderInfo.FromFile(FileSystem.FileInfo.New(entryFileName), entryFileName); + + var ex = Assert.Throws(() => + { + using var fs = FileSystem.File.OpenWrite(megFileName); + // Advance the stream so its position no longer matches the expected entry offset. + fs.WriteByte(0); + _megService.CreateMegArchive(fs, MegVersion.V1, null, [builderInfo]); + }); + + Assert.Contains("Actual file position", ex.Message); + } + + [Fact] + public void CreateMegArchive_DataEntrySizeMismatch_ThrowsInvalidOperationException() + { + const string megFileName = "new.meg"; + + // A file whose reported length (4) disagrees with the data its stream actually yields (3 bytes) + var fileInfo = new MegTestConstants.FakeFileInfo("file.txt", length: 4) { ReadBytes = [1, 2, 3] }; + var builderInfo = MegDataEntryBuilderInfo.FromFile(fileInfo, "file"); + + var ex = Assert.Throws(() => + { + using var fs = FileSystem.File.OpenWrite(megFileName); + _megService.CreateMegArchive(fs, MegVersion.V1, null, [builderInfo]); + }); + + Assert.Contains("Actual data entry size", ex.Message); + } + + [Fact] + public void CreateMegArchive_MegWithEntriesOfSameNameButWithDifferentData() + { + const string megFileName = "test.meg"; + + var expectedBytes = new byte[] + { + 2, 0, 0, 0, 2, 0, 0, 0, // Header + 4, 0, 102, 105, 108, 101, // "file" + 4, 0, 102, 105, 108, 101, // "file" + 16, 54, 159, 140, 0, 0, 0, 0, 3, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, + 16, 54, 159, 140, 1, 0, 0, 0, 3, 0, 0, 0, 63, 0, 0, 0, 1, 0, 0, 0, + 49, 50, 51, // 123 + 52, 53, 54 // 456 + }; + + + FileSystem.Initialize().WithFile("1.txt").Which(m => m.HasStringContent("123")); + FileSystem.Initialize().WithFile("2.txt").Which(m => m.HasStringContent("456")); + + var builderInfo = new List + { + MegDataEntryBuilderInfo.FromFile(FileSystem.FileInfo.New("1.txt"), "file"), + MegDataEntryBuilderInfo.FromFile(FileSystem.FileInfo.New("2.txt"), "file") + }; + + using (var fs = FileSystem.File.OpenWrite(megFileName)) + { + _megService.CreateMegArchive(fs, MegVersion.V1, null, builderInfo); + } + + var bytes = FileSystem.File.ReadAllBytes(megFileName); + + Assert.Equal(expectedBytes, bytes); + } + + [Fact] + public void CreateMegArchive_MemoryStream_WritesSameBytesAsFileStream() + { + var expectedBytes = new byte[] + { + 2, 0, 0, 0, 2, 0, 0, 0, // Header + 4, 0, 102, 105, 108, 101, // "file" + 4, 0, 102, 105, 108, 101, // "file" + 16, 54, 159, 140, 0, 0, 0, 0, 3, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, + 16, 54, 159, 140, 1, 0, 0, 0, 3, 0, 0, 0, 63, 0, 0, 0, 1, 0, 0, 0, + 49, 50, 51, // 123 + 52, 53, 54 // 456 + }; + + FileSystem.Initialize().WithFile("1.txt").Which(m => m.HasStringContent("123")); + FileSystem.Initialize().WithFile("2.txt").Which(m => m.HasStringContent("456")); + + var builderInfo = new List + { + MegDataEntryBuilderInfo.FromFile(FileSystem.FileInfo.New("1.txt"), "file"), + MegDataEntryBuilderInfo.FromFile(FileSystem.FileInfo.New("2.txt"), "file") + }; + + using var ms = new MemoryStream(); + _megService.CreateMegArchive(ms, MegVersion.V1, null, builderInfo); + + Assert.Equal(expectedBytes, ms.ToArray()); + } + + #endregion + + #region Generic Read + + [Fact] + public void LoadFile_InvalidBinary() + { + const string megFileName = "test.meg"; + const string fileData = "some random data"; + + FileSystem.Initialize().WithFile(megFileName).Which(m => m.HasStringContent(fileData)); + + Assert.Throws(() => _megService.LoadFile(megFileName)); + } + + [Fact] + public void LoadFile_ThrowFileNotFound() + { + Assert.Throws(() => _megService.LoadFile("notFound.meg")); + } + + [Fact] + public void LoadFile_NullArgs() + { + Assert.Throws(() => _megService.LoadFile((string)null!)); + Assert.Throws(() => _megService.LoadFile((FileSystemStream)null!)); + } + + [Fact] + public void LoadFile_EncryptedArchive_ThrowsNotImplemented() + { + const string megFileName = "encrypted.meg"; + FileSystem.Initialize().WithFile(megFileName).Which(m => m.HasBytesContent(EncryptedV3EmptyMegHeader)); + + Assert.Throws(() => _megService.LoadFile(megFileName)); + + using var fs = FileSystem.File.OpenRead(megFileName); + Assert.Throws(() => _megService.LoadFile(fs)); + } + + [Fact] + public void LoadArchive_EncryptedArchive_ThrowsNotSupported() + { + // File-stream branch — even though LoadArchive(Stream) with a FileSystemStream would otherwise + // produce a file-backed MegFile, an encrypted archive is by-design unsupported on this entry point. + const string megFileName = "encrypted.meg"; + FileSystem.Initialize().WithFile(megFileName).Which(m => m.HasBytesContent(EncryptedV3EmptyMegHeader)); + using (var fs = FileSystem.File.OpenRead(megFileName)) + { + Assert.Throws(() => _megService.LoadArchive(fs)); + } + + // Memory branches + using var ms = new MemoryStream(EncryptedV3EmptyMegHeader); + Assert.Throws(() => _megService.LoadArchive(ms)); + Assert.Throws(() => _megService.LoadArchive(EncryptedV3EmptyMegHeader)); + Assert.Throws(() => _megService.LoadArchive(EncryptedV3EmptyMegHeader.AsSpan())); + } + + private static readonly byte[] EncryptedV3EmptyMegHeader = + [ + 0xff, 0xff, 0xff, 0x8f, + 0xa4, 0x70, 0x7d, 0x3f, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ]; + + #endregion + + #region Read / Write V1 + + [Fact] + public void MegV1_WithEntries() + { + const string megFileName = "test.meg"; + + FileSystem.Initialize().WithFile(megFileName).Which(m => m.HasBytesContent(MegTestConstants.ContentMegFileV1)); + + var expectedData = new ExpectedMegTestData + { + IsMegVersion = MegVersion.V1, + IsMegEncrypted = false, + MegFileCount = 2, + EntryNames = new List + { + "DATA\\XML\\CAMPAIGNFILES.XML", + "DATA\\XML\\GAMEOBJECTFILES.XML" + }, + NewMegFilePath = "new.meg", + NewMegVersion = MegVersion.V1, + NewMegIsBinaryEqual = true + }; + TestMegFiles(megFileName, expectedData); + } + + [Fact] + public void MegV1_Empty() + { + const string megFileName = "test.meg"; + const string megResource = "Files.v1_empty.meg"; + + FileSystem.Initialize().WithFile(megFileName) + .Which(m => m.HasBytesContent(TestingHelpers.GetEmbeddedResourceAsByteArray(GetType(), megResource))); + + var expectedData = new ExpectedMegTestData + { + IsMegVersion = MegVersion.V1, + IsMegEncrypted = false, + MegFileCount = 0, + EntryNames = new List(), + NewMegFilePath = "new.meg", + NewMegVersion = MegVersion.V1, + NewMegIsBinaryEqual = true + }; + TestMegFiles(megFileName, expectedData); + } + + [Fact] + public void MegV1_EntriesHaveNonAsciiNames() + { + const string megFileName = "test.meg"; + const string megResource = "Files.v1_2_files_with_extended_ascii_name.meg"; + + FileSystem.Initialize().WithFile(megFileName) + .Which(m => m.HasBytesContent(TestingHelpers.GetEmbeddedResourceAsByteArray(GetType(), megResource))); + + var expectedData = new ExpectedMegTestData + { + IsMegVersion = MegVersion.V1, + IsMegEncrypted = false, + MegFileCount = 2, + EntryNames = new List + { + "TEST?.TXT", + "TEST?.TXT" + }, + NewMegFilePath = "new.meg", + NewMegVersion = MegVersion.V1, + NewMegIsBinaryEqual = false + }; + + TestMegFiles(megFileName, expectedData); + } + + #endregion + + #region GetMegVersion + + [Theory] + [InlineData("Files.v1_1_file_data.meg", MegVersion.V1)] + [InlineData("Files.v2_2_files_data.meg", MegVersion.V2)] + [InlineData("Files.v3n_2_files_data.meg", MegVersion.V3)] + public void GetMegVersion_AllOverloads_ReturnSameVersion(string megResource, MegVersion expectedVersion) + { + var bytes = TestingHelpers.GetEmbeddedResourceAsByteArray(GetType(), megResource); + FileSystem.File.WriteAllBytes(ContentMegFileName, bytes); + + Assert.Equal(expectedVersion, _megService.GetMegVersion(ContentMegFileName, out var encryptedFromFile)); + Assert.False(encryptedFromFile); + + using (var stream = new MemoryStream(bytes)) + { + Assert.Equal(expectedVersion, _megService.GetMegVersion(stream, out var encryptedFromStream)); + Assert.False(encryptedFromStream); + } + + Assert.Equal(expectedVersion, _megService.GetMegVersion(bytes, out var encryptedFromArray)); + Assert.False(encryptedFromArray); + + Assert.Equal(expectedVersion, _megService.GetMegVersion(bytes.AsSpan(), out var encryptedFromSpan)); + Assert.False(encryptedFromSpan); + } + + [Fact] + public void GetMegVersion_Throws_FileNotFound() + { + Assert.Throws(() => _megService.GetMegVersion("notFound.meg", out _)); + } + + [Fact] + public void GetMegVersion_NullArgs_Throws() + { + Assert.Throws(() => _megService.GetMegVersion((string)null!, out _)); + Assert.Throws(() => _megService.GetMegVersion((Stream)null!, out _)); + Assert.Throws(() => _megService.GetMegVersion((byte[])null!, out _)); + } + + [Fact] + public void LoadArchive_NullArgs_Throws() + { + Assert.Throws(() => _megService.LoadArchive((Stream)null!)); + Assert.Throws(() => _megService.LoadArchive((byte[])null!)); + } + + #endregion + + #region Load Archive + + // The different ways a MEG archive can be loaded through IMegService. They all yield an equivalent + // IMegDataSource; the file-based overloads additionally produce a lazily file-backed IMegFile. + public enum LoadOverload + { + FilePath, + FileStream, + LoadArchiveFileStream, + LoadArchiveStream, + LoadArchiveByteArray, + LoadArchiveSpan + } + + [Theory] + [InlineData(LoadOverload.FilePath)] + [InlineData(LoadOverload.FileStream)] + [InlineData(LoadOverload.LoadArchiveFileStream)] + [InlineData(LoadOverload.LoadArchiveStream)] + [InlineData(LoadOverload.LoadArchiveByteArray)] + [InlineData(LoadOverload.LoadArchiveSpan)] + public void Load_ParsesAndReadsEntries(LoadOverload overload) + { + var source = LoadContentMeg(overload); + + Assert.Equal(2, source.Archive.Count); + Assert.Equal("DATA\\XML\\CAMPAIGNFILES.XML", source.Archive[0].Path); + Assert.Equal("DATA\\XML\\GAMEOBJECTFILES.XML", source.Archive[1].Path); + Assert.Equal(MegTestConstants.CampaignFilesContent, ReadAll(source.GetData(source.Archive[0]))); + Assert.Equal(MegTestConstants.GameObjectFilesContent, ReadAll(source.GetData(source.Archive[1]))); + } + + [Theory] + [InlineData(LoadOverload.FilePath)] + [InlineData(LoadOverload.FileStream)] + [InlineData(LoadOverload.LoadArchiveFileStream)] + public void Load_FromFile_ReturnsFileBackedMeg(LoadOverload overload) + { + Assert.IsAssignableFrom(LoadContentMeg(overload)); + } + + [Theory] + [InlineData(LoadOverload.LoadArchiveStream)] + [InlineData(LoadOverload.LoadArchiveByteArray)] + [InlineData(LoadOverload.LoadArchiveSpan)] + public void LoadArchive_FromMemory_IsNotFileBacked(LoadOverload overload) + { + Assert.False(LoadContentMeg(overload) is IMegFile); + } + + [Fact] + public void LoadArchive_NonFileStream_SourceStreamMayBeDisposed() + { + IMegDataSource source; + using (var stream = new MemoryStream(MegTestConstants.ContentMegFileV1)) + { + source = _megService.LoadArchive(stream); + } + + // The source stream is already disposed; the data is still served from the in-memory copy. + Assert.Equal(MegTestConstants.CampaignFilesContent, ReadAll(source.GetData(source.Archive[0]))); + } + + [Fact] + public void LoadArchive_ByteArray_InputMayBeMutated() + { + var data = (byte[])MegTestConstants.ContentMegFileV1.Clone(); + var source = _megService.LoadArchive(data); + + // Wiping the caller's buffer must not affect the loaded MEG (the buffer was copied defensively). + Array.Clear(data, 0, data.Length); + + Assert.Equal(MegTestConstants.CampaignFilesContent, ReadAll(source.GetData(source.Archive[0]))); + } + + [Fact] + public void LoadArchive_NonSeekableStream_Works() + { + using var nonSeekable = new NonSeekableReadStream(MegTestConstants.ContentMegFileV1); + + var source = _megService.LoadArchive(nonSeekable); + + Assert.Equal(2, source.Archive.Count); + Assert.Equal(MegTestConstants.GameObjectFilesContent, ReadAll(source.GetData(source.Archive[1]))); + } + + [Fact] + public void LoadArchive_InMemoryMegs_HaveDistinctNames() + { + var first = _megService.LoadArchive(MegTestConstants.ContentMegFileV1); + var second = _megService.LoadArchive(MegTestConstants.ContentMegFileV1); + + // Two in-memory MEGs loaded from identical bytes should have different identifiers + var firstDescription = new MegDataEntryLocationReference(first, first.Archive[0]).ToString(); + var secondDescription = new MegDataEntryLocationReference(second, second.Archive[0]).ToString(); + + Assert.NotEqual(firstDescription, secondDescription); + } + + // Opening Stream as input leaves a consumer the option to pass a System.IO.FileStream instance. + // In this case, we still want to ensure the loaded MEG is using the file and not an in-memory MEG. + [Fact] + public void LoadArchive_RealSystemIOFileStream() + { + using var services = CreateRealFileSystemServiceProvider(); + var megService = services.GetRequiredService(); + + var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + try + { + File.WriteAllBytes(tempFile, MegTestConstants.ContentMegFileV1); + + IMegDataSource source; + using (var fileStream = File.OpenRead(tempFile)) + source = megService.LoadArchive(fileStream); + + Assert.IsAssignableFrom(source); + Assert.Equal(2, source.Archive.Count); + Assert.Equal(MegTestConstants.CampaignFilesContent, ReadAll(source.GetData(source.Archive[0]))); + } + finally + { + if (File.Exists(tempFile)) + File.Delete(tempFile); + } + } + + private IMegDataSource LoadContentMeg(LoadOverload overload) + { + switch (overload) + { + case LoadOverload.FilePath: + WriteContentMegToFile(); + return _megService.LoadFile(ContentMegFileName); + case LoadOverload.FileStream: + { + WriteContentMegToFile(); + using var fs = FileSystem.FileStream.New(ContentMegFileName, FileMode.Open, FileAccess.Read); + return _megService.LoadFile(fs); + } + case LoadOverload.LoadArchiveFileStream: + { + WriteContentMegToFile(); + using var fs = FileSystem.FileStream.New(ContentMegFileName, FileMode.Open, FileAccess.Read); + return _megService.LoadArchive(fs); + } + case LoadOverload.LoadArchiveStream: + { + using var stream = new MemoryStream(MegTestConstants.ContentMegFileV1); + return _megService.LoadArchive(stream); + } + case LoadOverload.LoadArchiveByteArray: + return _megService.LoadArchive(MegTestConstants.ContentMegFileV1); + case LoadOverload.LoadArchiveSpan: + return _megService.LoadArchive(MegTestConstants.ContentMegFileV1.AsSpan()); + default: + throw new ArgumentOutOfRangeException(nameof(overload), overload, null); + } + } + + private void WriteContentMegToFile() + { + FileSystem.File.WriteAllBytes(ContentMegFileName, MegTestConstants.ContentMegFileV1); + } + + private static byte[] ReadAll(Stream stream) + { + using (stream) + { + using var ms = new MemoryStream(); + stream.CopyTo(ms); + return ms.ToArray(); + } + } + + private static ServiceProvider CreateRealFileSystemServiceProvider() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(new Testably.Abstractions.RealFileSystem()); + serviceCollection.AddSingleton(sp => new HashingService(sp)); + PetroglyphCommons.ContributeServices(serviceCollection); + serviceCollection.SupportMEG(); + return serviceCollection.BuildServiceProvider(); + } + + #endregion + + private void TestMegFiles(string megFilePath, ExpectedMegTestData expectedData) + { + var megVersion = _megService.GetMegVersion(megFilePath, out var encrypted); + Assert.Equal(expectedData.IsMegVersion, megVersion); + Assert.Equal(expectedData.IsMegEncrypted, encrypted); + + var meg = _megService.LoadFile(megFilePath); + TestMegModelContent(meg, expectedData, false); + + for (var i = 0; i < meg.Archive.Count; i++) + { + var entry = meg.Archive[i]; + var expected = expectedData.EntryNames[i]; + Assert.Equal(expected, entry.Path); + } + + using var param = new MegFileInformation( + expectedData.NewMegFilePath, + expectedData.NewMegVersion, + expectedData.EncryptionData); + + var builderInformation = meg.Archive.Select(e => + new MegDataEntryBuilderInfo(new MegDataEntryOriginInfo(new MegDataEntryLocationReference(meg, e)))); + + using (var fs = FileSystem.File.OpenWrite(expectedData.NewMegFilePath)) + { + _megService.CreateMegArchive(fs, expectedData.NewMegVersion, expectedData.EncryptionData, + builderInformation); + } + + Assert.True(FileSystem.File.Exists(expectedData.NewMegFilePath)); + + var createdVersion = _megService.GetMegVersion(expectedData.NewMegFilePath, out var newEncrypted); + Assert.Equal(expectedData.NewMegVersion, createdVersion); + Assert.Equal(expectedData.EncryptionData is null, !newEncrypted); + + + var actualBytes = FileSystem.File.ReadAllBytes(expectedData.NewMegFilePath); + var expectedBytes = FileSystem.File.ReadAllBytes(megFilePath); + if (expectedData.NewMegIsBinaryEqual) + Assert.Equal(expectedBytes, actualBytes); + else + Assert.NotEqual(expectedBytes, actualBytes); + + var newMeg = _megService.LoadFile(megFilePath); + TestMegModelContent(newMeg, expectedData, true); + } + + private static void TestMegModelContent(IMegFile meg, ExpectedMegTestData expectedData, bool isNewMeg) + { + Assert.NotNull(meg); + Assert.Equal(expectedData.MegFileCount, meg.Content.Count); + Assert.Equal(expectedData.IsMegVersion, meg.FileInformation.Version); + Assert.Equal(expectedData.EntryNames.Count, meg.Archive.Count); + + if (isNewMeg) + Assert.Equal(expectedData.EncryptionData is null, !meg.FileInformation.HasEncryption); + else + Assert.Equal(expectedData.IsMegEncrypted, meg.FileInformation.HasEncryption); + + for (var i = 0; i < meg.Archive.Count; i++) + { + var entry = meg.Archive[i]; + var expected = expectedData.EntryNames[i]; + Assert.Equal(expected, entry.Path); + } + } + + private record ExpectedMegTestData + { + public MegVersion IsMegVersion { get; init; } + public bool IsMegEncrypted { get; init; } + public int MegFileCount { get; init; } + public IList EntryNames { get; init; } = null!; + public string NewMegFilePath { get; init; } = null!; + public bool NewMegIsBinaryEqual { get; init; } + public MegVersion NewMegVersion { get; init; } + public MegEncryptionData? EncryptionData { get; } + } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/VirtualMegArchiveBuilderTest.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/VirtualMegArchiveBuilderTest.cs index 8046cacd..17e46cce 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/VirtualMegArchiveBuilderTest.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/Services/VirtualMegArchiveBuilderTest.cs @@ -1,6 +1,9 @@ -using System; +using System; using System.Collections.Generic; +using System.IO; +using Microsoft.Extensions.DependencyInjection; using PG.Commons.Hashing; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Data.Entries; using PG.StarWarsGame.Files.MEG.Files; @@ -33,7 +36,7 @@ public void BuildFrom_ListOfReferences_DoesNotExists_Throws() var entry1 = MegDataEntryTest.CreateEntry("A", new Crc32(1)); FileSystem.File.Create("test.meg"); - var megFile = new MegFile(new MegArchive([]), new MegFileInformation("test.meg", MegFileVersion.V1), ServiceProvider); + var megFile = new MegFile(new MegArchive([]), new MegFileInformation("test.meg", MegVersion.V1), ServiceProvider); var entries = new List { @@ -54,10 +57,10 @@ public void BuildFrom_ListOfReferences_DoNotReplace() var entry3 = MegDataEntryTest.CreateEntry("C", new Crc32(1)); FileSystem.File.Create("1.meg"); - var meg1 = new MegFile(new MegArchive([entry1]), new MegFileInformation("1.meg", MegFileVersion.V1), ServiceProvider); + var meg1 = new MegFile(new MegArchive([entry1]), new MegFileInformation("1.meg", MegVersion.V1), ServiceProvider); FileSystem.File.Create("2.meg"); - var meg2 = new MegFile(new MegArchive([entry2, entry3]), new MegFileInformation("2.meg", MegFileVersion.V1), ServiceProvider); + var meg2 = new MegFile(new MegArchive([entry2, entry3]), new MegFileInformation("2.meg", MegVersion.V1), ServiceProvider); var entries = new List { @@ -72,10 +75,10 @@ public void BuildFrom_ListOfReferences_DoNotReplace() Assert.Equal(2, archive.Count); Assert.Equal(entry2, archive[0].Location.DataEntry); - Assert.Equal(meg2, archive[0].Location.MegFile); + Assert.Equal(meg2, archive[0].Location.Source); Assert.Equal(entry1, archive[1].Location.DataEntry); - Assert.Equal(meg1, archive[1].Location.MegFile); + Assert.Equal(meg1, archive[1].Location.Source); } [Fact] @@ -88,10 +91,10 @@ public void BuildFrom_ListOfReferences_Replace() var entry3 = MegDataEntryTest.CreateEntry("C", new Crc32(1)); FileSystem.File.Create("1.meg"); - var meg1 = new MegFile(new MegArchive([entry1]), new MegFileInformation("1.meg", MegFileVersion.V1), ServiceProvider); + var meg1 = new MegFile(new MegArchive([entry1]), new MegFileInformation("1.meg", MegVersion.V1), ServiceProvider); FileSystem.File.Create("2.meg"); - var meg2 = new MegFile(new MegArchive([entry2, entry3]), new MegFileInformation("2.meg", MegFileVersion.V1), ServiceProvider); + var meg2 = new MegFile(new MegArchive([entry2, entry3]), new MegFileInformation("2.meg", MegVersion.V1), ServiceProvider); var entries = new List { @@ -106,10 +109,10 @@ public void BuildFrom_ListOfReferences_Replace() Assert.Equal(2, archive.Count); Assert.Equal(entry2, archive[0].Location.DataEntry); - Assert.Equal(meg2, archive[0].Location.MegFile); + Assert.Equal(meg2, archive[0].Location.Source); Assert.Equal(entry3, archive[1].Location.DataEntry); - Assert.Equal(meg2, archive[1].Location.MegFile); + Assert.Equal(meg2, archive[1].Location.Source); } [Fact] @@ -122,7 +125,7 @@ public void BuildFrom_SingleMeg() var entry3 = MegDataEntryTest.CreateEntry("C", new Crc32(1)); FileSystem.File.Create("test.meg"); - var megFile = new MegFile(new MegArchive([entry1, entry2, entry3]), new MegFileInformation("test.meg", MegFileVersion.V1), ServiceProvider); + var megFile = new MegFile(new MegArchive([entry1, entry2, entry3]), new MegFileInformation("test.meg", MegVersion.V1), ServiceProvider); var archive = service.BuildFrom(megFile); @@ -130,10 +133,10 @@ public void BuildFrom_SingleMeg() Assert.Equal(2, archive.Count); Assert.Equal(entry1, archive[0].Location.DataEntry); - Assert.Equal(megFile, archive[0].Location.MegFile); + Assert.Equal(megFile, archive[0].Location.Source); Assert.Equal(entry3, archive[1].Location.DataEntry); - Assert.Equal(megFile, archive[1].Location.MegFile); + Assert.Equal(megFile, archive[1].Location.Source); } [Fact] @@ -149,10 +152,10 @@ public void BuildFrom_ListOfMegs_DoNotReplace() var entry2_3 = MegDataEntryTest.CreateEntry("D", new Crc32(1)); FileSystem.File.Create("1.meg"); - var meg1 = new MegFile(new MegArchive([entry1_1, entry1_2]), new MegFileInformation("1.meg", MegFileVersion.V1), ServiceProvider); + var meg1 = new MegFile(new MegArchive([entry1_1, entry1_2]), new MegFileInformation("1.meg", MegVersion.V1), ServiceProvider); FileSystem.File.Create("2.meg"); - var meg2 = new MegFile(new MegArchive([entry2_1, entry2_2, entry2_3]), new MegFileInformation("2.meg", MegFileVersion.V1), ServiceProvider); + var meg2 = new MegFile(new MegArchive([entry2_1, entry2_2, entry2_3]), new MegFileInformation("2.meg", MegVersion.V1), ServiceProvider); var archive = service.BuildFrom(new List{meg1, meg2}, false); @@ -160,10 +163,10 @@ public void BuildFrom_ListOfMegs_DoNotReplace() Assert.Equal(2, archive.Count); Assert.Equal(entry2_1, archive[0].Location.DataEntry); - Assert.Equal(meg2, archive[0].Location.MegFile); + Assert.Equal(meg2, archive[0].Location.Source); Assert.Equal(entry1_1, archive[1].Location.DataEntry); - Assert.Equal(meg1, archive[1].Location.MegFile); + Assert.Equal(meg1, archive[1].Location.Source); } [Fact] @@ -179,10 +182,10 @@ public void BuildFrom_ListOfMegs_Replace() var entry2_3 = MegDataEntryTest.CreateEntry("D", new Crc32(1)); FileSystem.File.Create("1.meg"); - var meg1 = new MegFile(new MegArchive([entry1_1, entry1_2]), new MegFileInformation("1.meg", MegFileVersion.V1), ServiceProvider); + var meg1 = new MegFile(new MegArchive([entry1_1, entry1_2]), new MegFileInformation("1.meg", MegVersion.V1), ServiceProvider); FileSystem.File.Create("2.meg"); - var meg2 = new MegFile(new MegArchive([entry2_1, entry2_2, entry2_3]), new MegFileInformation("2.meg", MegFileVersion.V1), ServiceProvider); + var meg2 = new MegFile(new MegArchive([entry2_1, entry2_2, entry2_3]), new MegFileInformation("2.meg", MegVersion.V1), ServiceProvider); var archive = service.BuildFrom(new List { meg1, meg2 }, true); @@ -190,9 +193,41 @@ public void BuildFrom_ListOfMegs_Replace() Assert.Equal(2, archive.Count); Assert.Equal(entry2_1, archive[0].Location.DataEntry); - Assert.Equal(meg2, archive[0].Location.MegFile); + Assert.Equal(meg2, archive[0].Location.Source); Assert.Equal(entry2_2, archive[1].Location.DataEntry); - Assert.Equal(meg2, archive[1].Location.MegFile); + Assert.Equal(meg2, archive[1].Location.Source); + } + + [Fact] + public void BuildFrom_InMemoryMeg() + { + var service = new VirtualMegArchiveBuilder(); + + using var stream = new MemoryStream(MegTestConstants.ContentMegFileV1); + var inMemoryMeg = ServiceProvider.GetRequiredService().LoadArchive(stream); + + var archive = service.BuildFrom(inMemoryMeg); + + Assert.Equal(2, archive.Count); + Assert.Same(inMemoryMeg, archive[0].Location.Source); + Assert.False(archive[0].Location.Source is IMegFile); + } + + [Fact] + public void BuildFrom_MixedFileAndInMemoryMegs() + { + var service = new VirtualMegArchiveBuilder(); + + FileSystem.File.Create("file.meg"); + var fileEntry = MegDataEntryTest.CreateEntry("A", new Crc32(1)); + var fileMeg = new MegFile(new MegArchive([fileEntry]), new MegFileInformation("file.meg", MegVersion.V1), ServiceProvider); + + using var stream = new MemoryStream(MegTestConstants.ContentMegFileV1); + var inMemoryMeg = ServiceProvider.GetRequiredService().LoadArchive(stream); + + var archive = service.BuildFrom([fileMeg, inMemoryMeg], false); + + Assert.Equal(3, archive.Count); } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/ConstructingMegArchiveBuilderBase.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/ConstructingMegArchiveBuilderBase.cs index 548c100b..fbb78899 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/ConstructingMegArchiveBuilderBase.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/ConstructingMegArchiveBuilderBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; @@ -15,7 +15,6 @@ using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Data.Entries; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; -using PG.StarWarsGame.Files.MEG.Files; using PG.StarWarsGame.Files.MEG.Utilities; namespace PG.StarWarsGame.Files.MEG.Binary; @@ -26,7 +25,7 @@ internal abstract class ConstructingMegArchiveBuilderBase(IServiceProvider servi internal virtual uint MaxEntryFileSize { get; } = MaxMegSizeProvider.GetMegMaxSize(MaxMegSizeMode.Binary).MaxEntrySize; internal virtual uint MaxFileSize { get; } = MaxMegSizeProvider.GetMegMaxSize(MaxMegSizeMode.Binary).MaxFileSize; - protected abstract MegFileVersion FileVersion { get; } + protected abstract MegVersion MegVersion { get; } // TODO: Test encryption cases public IConstructingMegArchive BuildConstructingMegArchive(IEnumerable builderEntries) @@ -34,7 +33,7 @@ public IConstructingMegArchive BuildConstructingMegArchive(IEnumerable().GetMegSizeCalculator(FileVersion); + var calculator = Services.GetRequiredService().GetMegSizeCalculator(MegVersion); var binaryInformation = GetBinaryInformation(builderEntries, calculator); @@ -54,10 +53,10 @@ public IConstructingMegArchive BuildConstructingMegArchive(IEnumerable entries, IMegSizeCalculator calculator) { @@ -80,7 +79,7 @@ private MegFileBinaryInformation GetBinaryInformation( Debug.Assert(calculator.MetadataSize <= calculator.CurrentSize); - return new MegFileBinaryInformation((uint)calculator.MetadataSize, FileVersion, encryptMeg, entryInfoList); + return new MegBinaryInformation((uint)calculator.MetadataSize, MegVersion, encryptMeg, entryInfoList); } private MegDataEntryBinaryInformation CreateEntryBinaryInformation( diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/MegFileBinaryInformation.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/MegBinaryInformation.cs similarity index 63% rename from PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/MegFileBinaryInformation.cs rename to PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/MegBinaryInformation.cs index 9e778da4..1aea9b46 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/MegFileBinaryInformation.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/MegBinaryInformation.cs @@ -1,14 +1,14 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using PG.StarWarsGame.Files.MEG.Data; using System.Collections.Generic; -using PG.StarWarsGame.Files.MEG.Files; namespace PG.StarWarsGame.Files.MEG.Binary; -internal class MegFileBinaryInformation( +internal class MegBinaryInformation( uint metadataSize, - MegFileVersion megFileVersion, + MegVersion MegVersion, bool encrypted, IEnumerable entries) { @@ -16,7 +16,7 @@ internal class MegFileBinaryInformation( public bool Encrypted { get; } = encrypted; - public MegFileVersion MegFileVersion { get; } = megFileVersion; + public MegVersion MegVersion { get; } = MegVersion; public IEnumerable Entries { get; } = entries; } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/V1/ConstructingMegArchiveBuilderV1.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/V1/ConstructingMegArchiveBuilderV1.cs index 8c8ad712..654ecfb5 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/V1/ConstructingMegArchiveBuilderV1.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Construction/V1/ConstructingMegArchiveBuilderV1.cs @@ -1,8 +1,8 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using PG.StarWarsGame.Files.MEG.Data; using System; -using PG.StarWarsGame.Files.MEG.Files; namespace PG.StarWarsGame.Files.MEG.Binary.V1; @@ -11,5 +11,5 @@ internal sealed class ConstructingMegArchiveBuilderV1(IServiceProvider services) // NB: We do not override the MaxEntryFileSize, because this limitation, so far, // only applies to Empire at War / Forces of Corruption but not to the V1 format in general. - protected override MegFileVersion FileVersion => MegFileVersion.V1; + protected override MegVersion MegVersion => MegVersion.V1; } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/IMegBinaryServiceFactory.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/IMegBinaryServiceFactory.cs index 5f26bfe3..35d4a248 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/IMegBinaryServiceFactory.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/IMegBinaryServiceFactory.cs @@ -1,21 +1,22 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using PG.StarWarsGame.Files.MEG.Binary.Size; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Files; namespace PG.StarWarsGame.Files.MEG.Binary; /// -/// Base service to handle the transformation from binary to a and vice versa. +/// Provides factory methods for the services that transform between binary data and an . /// internal interface IMegBinaryServiceFactory { - IMegFileBinaryReader GetReader(MegFileVersion megVersion); + IMegFileBinaryReader GetReader(MegVersion megVersion); - IMegBinaryConverter GetConverter(MegFileVersion megVersion); + IMegBinaryConverter GetConverter(MegVersion megVersion); - IConstructingMegArchiveBuilder GetConstructionBuilder(MegFileVersion megVersion); + IConstructingMegArchiveBuilder GetConstructionBuilder(MegVersion megVersion); - IMegSizeCalculator GetMegSizeCalculator(MegFileVersion megFileVersion); + IMegSizeCalculator GetMegSizeCalculator(MegVersion megVersion); } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/MegBinaryServiceFactory.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/MegBinaryServiceFactory.cs index 9ecbd5af..bf908e49 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/MegBinaryServiceFactory.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/MegBinaryServiceFactory.cs @@ -1,10 +1,10 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; using PG.StarWarsGame.Files.MEG.Binary.Size; using PG.StarWarsGame.Files.MEG.Binary.V1; -using PG.StarWarsGame.Files.MEG.Files; +using PG.StarWarsGame.Files.MEG.Data; namespace PG.StarWarsGame.Files.MEG.Binary; @@ -12,35 +12,35 @@ internal class MegBinaryServiceFactory(IServiceProvider serviceProvider) : IMegB { private readonly IServiceProvider _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - public IMegFileBinaryReader GetReader(MegFileVersion megVersion) + public IMegFileBinaryReader GetReader(MegVersion megVersion) { - if (megVersion == MegFileVersion.V1) + if (megVersion == MegVersion.V1) return new MegFileBinaryReaderV1(_serviceProvider); throw new NotImplementedException("MEGs other than V1 are currently not supported."); } - public IMegBinaryConverter GetConverter(MegFileVersion megVersion) + public IMegBinaryConverter GetConverter(MegVersion megVersion) { - if (megVersion == MegFileVersion.V1) + if (megVersion == MegVersion.V1) return new MegBinaryConverterV1(_serviceProvider); throw new NotImplementedException("MEGs other than V1 are currently not supported."); } - public IConstructingMegArchiveBuilder GetConstructionBuilder(MegFileVersion megVersion) + public IConstructingMegArchiveBuilder GetConstructionBuilder(MegVersion megVersion) { - if (megVersion == MegFileVersion.V1) + if (megVersion == MegVersion.V1) return new ConstructingMegArchiveBuilderV1(_serviceProvider); throw new NotImplementedException("MEGs other than V1 are currently not supported."); } - public IMegSizeCalculator GetMegSizeCalculator(MegFileVersion megVersion) + public IMegSizeCalculator GetMegSizeCalculator(MegVersion megVersion) { return megVersion switch { - MegFileVersion.V1 => new MegV1SizeCalculator(), + MegVersion.V1 => new MegV1SizeCalculator(), _ => throw new NotImplementedException($"MEG version {megVersion} is currently not supported.") }; } diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/MegFileConstants.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/MegFileConstants.cs index fb3ba111..15d5ed82 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/MegFileConstants.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/MegFileConstants.cs @@ -32,7 +32,7 @@ public class MegFileConstants internal const uint MegMaxFileSize = uint.MaxValue; /// - /// The max number of characters allowed in Empire at War game for MEG entry paths. + /// Represents the maximum number of characters allowed in Empire at War for MEG entry paths. /// public const int EawMaxEntryPathLength = 259; @@ -52,12 +52,12 @@ public class MegFileConstants // // Therefore, we limit the max entry size, and thus the max file size to int.MaxValue (2GB) for Eaw/Foc MEG files. /// - /// The max size of a MEG entry for Empire at War / Forces of Corruption. + /// Represents the maximum size of a MEG entry for Empire at War / Forces of Corruption. /// public const int EawMegMaxEntrySize = int.MaxValue; - + /// - /// The max file size of a MEG file for Empire at War / Forces of Corruption. + /// Represents the maximum size of a MEG file for Empire at War / Forces of Corruption. /// public const int EawMegMaxFileSize = EawMegMaxEntrySize; @@ -73,7 +73,7 @@ public class MegFileConstants // 'ß.txt' --> '?.txt' // 'ä.txt' --> '?.txt' /// - /// ASCII encoding is used for MEG entries. + /// Represents the ASCII encoding used for MEG entries. /// public static readonly Encoding MegDataEntryPathEncoding = Encoding.ASCII; diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegFileDescriptor.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegFileDescriptor.cs index ed09b4fe..105a4e19 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegFileDescriptor.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegFileDescriptor.cs @@ -1,33 +1,48 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; using PG.Commons.Data; using PG.StarWarsGame.Files.Binary; -using PG.StarWarsGame.Files.MEG.Files; +using PG.StarWarsGame.Files.MEG.Data; namespace PG.StarWarsGame.Files.MEG.Binary.Metadata; internal interface IMegFileDescriptor : IBinary, IHasCrc32, IComparable { + /// + /// Gets the offset, in bytes, where the described file's data starts within the .MEG archive. + /// public uint FileOffset { get; } + /// + /// Gets the size, in bytes, of the described file's data. + /// public uint FileSize { get; } + /// + /// Gets the index of the described file's name within the file name table. + /// /// /// The .MEG specification allows , however in .NET we are /// limited to for indexing native list-like structures.
///
- /// For .MEG archives this values type must be treated as .
+ /// For .MEG archives this values type must be treated as .
/// When reading or writing a .MEG binary, assertions should be implemented to verify data validity. ///
public int FileNameIndex { get; } + /// + /// Gets the index of the described file within the file table. + /// /// /// The .MEG specification allows , however in .NET we are /// limited to for indexing native list-like structures.
///
public int Index { get; } - + + /// + /// Gets a value that indicates whether the described file's data is encrypted. + /// public bool Encrypted { get; } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegFileMetadata.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegFileMetadata.cs index bc3ce612..6415ad18 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegFileMetadata.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegFileMetadata.cs @@ -8,9 +8,18 @@ namespace PG.StarWarsGame.Files.MEG.Binary.Metadata; internal interface IMegFileMetadata : IBinaryFile { + /// + /// Gets the header of the .MEG archive. + /// IMegHeader Header { get; } + /// + /// Gets the table that holds the file names of the .MEG archive. + /// BinaryTable FileNameTable { get; } + /// + /// Gets the table that holds the file descriptors of the .MEG archive. + /// IMegFileTable FileTable { get; } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegHeader.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegHeader.cs index 4862c100..fdba60ec 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegHeader.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/IMegHeader.cs @@ -7,9 +7,12 @@ namespace PG.StarWarsGame.Files.MEG.Binary.Metadata; internal interface IMegHeader : IBinary { + /// + /// Gets the number of files contained in the .MEG archive. + /// /// /// The .MEG specification allows , however in .NET we are - /// limited to for indexing native list-like structures. + /// limited to for indexing native list-like structures. /// int FileNumber { get; } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/V1/MegFileTableRecord.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/V1/MegFileTableRecord.cs index f32ae242..3a1f6a3b 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/V1/MegFileTableRecord.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/V1/MegFileTableRecord.cs @@ -106,7 +106,7 @@ int IComparable.CompareTo(IMegFileDescriptor? other) } /// - /// Determines whether one record is greater than or equal to another by its checksum. + /// Determines whether one record is greater than or equal to another by its checksum. /// /// The first record to compare. /// The second record to compare. @@ -117,10 +117,10 @@ int IComparable.CompareTo(IMegFileDescriptor? other) } /// - /// Determines whether one record is less than or equal to another by its checksum. + /// Determines whether one record is less than or equal to another by its checksum. /// - /// The first record checksum to compare. - /// The second record to + /// The first record to compare. + /// The second record to compare. /// if is less than or equal to ; otherwise, . public static bool operator <=(MegFileTableRecord a, MegFileTableRecord b) { diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/V1/MegMetadata.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/V1/MegMetadata.cs index 02ee3963..5abce7fe 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/V1/MegMetadata.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Metadata/V1/MegMetadata.cs @@ -7,7 +7,7 @@ namespace PG.StarWarsGame.Files.MEG.Binary.Metadata.V1; /// -/// Meg archive representation WITHOUT content data. +/// Represents a .MEG archive's metadata, without the actual content data. /// internal class MegMetadata : BinaryFile, IMegFileMetadata { diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Reader/Validation/IMegBinaryValidator.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Reader/Validation/IMegBinaryValidator.cs index 5f7e1a33..85a7fcdd 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Reader/Validation/IMegBinaryValidator.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Reader/Validation/IMegBinaryValidator.cs @@ -15,7 +15,7 @@ internal interface IMegBinaryValidator where TMetadata : IMegFileM { /// /// Validates the integrity and correctness of the specified MEG file metadata against the provided binary data - /// and throws an if the file is invalid. + /// and throws a if the file is invalid. /// /// The metadata of the MEG file to validate. /// The actual size of the metadata in bytes as read from the binary data. diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Version/IMegVersionIdentifier.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Version/IMegVersionIdentifier.cs index d8835104..f73a62fb 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Version/IMegVersionIdentifier.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Version/IMegVersionIdentifier.cs @@ -1,12 +1,12 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using PG.StarWarsGame.Files.MEG.Data; using System.IO; -using PG.StarWarsGame.Files.MEG.Files; namespace PG.StarWarsGame.Files.MEG.Binary; internal interface IMegVersionIdentifier { - MegFileVersion GetMegFileVersion(Stream data, out bool encrypted); + MegVersion GetMegVersion(Stream data, out bool encrypted); } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Version/MegVersionIdentifier.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Version/MegVersionIdentifier.cs index 2e3e54f6..e16806c3 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Version/MegVersionIdentifier.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Binary/Version/MegVersionIdentifier.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; @@ -8,24 +8,25 @@ using Microsoft.Extensions.Logging; using PG.Commons.Services; using PG.StarWarsGame.Files.Binary; -using PG.StarWarsGame.Files.MEG.Files; +using PG.StarWarsGame.Files.MEG.Data; namespace PG.StarWarsGame.Files.MEG.Binary; internal class MegVersionIdentifier(IServiceProvider services) : ServiceBase(services), IMegVersionIdentifier { /// - /// This method is optimized in a way to retrieve the MEGs file version as efficient as possible. - /// If an invalid MEG archive is detected a is thrown. - /// However, this method does not completely verify whether the passed stream is a valid MEG archive or not. + /// Determines the MEG file version of the specified stream as efficiently as possible. /// - /// The MEG archive stream - /// Indicates whether the archive is encrypted or not. - /// The determined of the MEG stream. - /// The is null. - /// The is not readable or seekable. + /// + /// This method does not completely verify whether the passed stream is a valid MEG archive. + /// + /// The MEG archive stream. + /// When this method returns, contains if the archive is encrypted; otherwise, . This parameter is treated as uninitialized. + /// The determined of the MEG stream. + /// is . + /// is not readable or seekable. /// The read data is not a valid MEG archive. - public unsafe MegFileVersion GetMegFileVersion(Stream stream, out bool encrypted) + public unsafe MegVersion GetMegVersion(Stream stream, out bool encrypted) { if (stream == null) throw new ArgumentNullException(nameof(stream)); @@ -51,7 +52,7 @@ public unsafe MegFileVersion GetMegFileVersion(Stream stream, out bool encrypted // Note: In V1 we *could* have the situation where we store as many files in the meg to coincidentally match the magic number. // Thus, we don't check for the magic number as that it would not gain us anything. if (flags == id) - return MegFileVersion.V1; + return MegVersion.V1; Logger.LogTrace("Checking MEG version: Must be V2 or V3."); @@ -63,7 +64,7 @@ public unsafe MegFileVersion GetMegFileVersion(Stream stream, out bool encrypted { Logger.LogTrace("Checking MEG version: MEG has encrypted flag. Version is V3."); encrypted = true; - return MegFileVersion.V3; + return MegVersion.V3; } var dataStart = reader.ReadUInt32(); @@ -125,21 +126,21 @@ public unsafe MegFileVersion GetMegFileVersion(Stream stream, out bool encrypted if (!TryReadUInt32(reader, out var filenamesSize)) { if (numFiles == 0) - return MegFileVersion.V2; + return MegVersion.V2; throw new BinaryCorruptedException("Unrecognized :MEG file version"); } if (numFiles == 0) { if (filenamesSize == 0) - return MegFileVersion.V3; + return MegVersion.V3; // An empty V2 file that has some junk attached. - return MegFileVersion.V2; + return MegVersion.V2; } delegate* recordCheckMethod; - MegFileVersion versionToCheck; + MegVersion versionToCheck; // known start of the FileTable var fileTableOffset = checked(dataStart - numFiles * 20); @@ -150,13 +151,13 @@ public unsafe MegFileVersion GetMegFileVersion(Stream stream, out bool encrypted if (fileTableOffset == 24 + filenamesSize) { Logger.LogTrace("Checking MEG version: Checking V3 case..."); - versionToCheck = MegFileVersion.V3; + versionToCheck = MegVersion.V3; recordCheckMethod = &FileRecordIsV3; } else { Logger.LogTrace("Checking MEG version: Checking V2 case..."); - versionToCheck = MegFileVersion.V2; + versionToCheck = MegVersion.V2; recordCheckMethod = &FileRecordIsV2; } @@ -165,7 +166,7 @@ public unsafe MegFileVersion GetMegFileVersion(Stream stream, out bool encrypted if (CheckFirstAndLastRecord(reader, dataStart, numFiles, recordCheckMethod)) return versionToCheck; - if (versionToCheck == MegFileVersion.V2) + if (versionToCheck == MegVersion.V2) throw new BinaryCorruptedException("Unrecognized .MEG file version."); Logger.LogTrace("Checking MEG version: V3 case did not pass, checking for V2..."); @@ -176,7 +177,7 @@ public unsafe MegFileVersion GetMegFileVersion(Stream stream, out bool encrypted reader.BaseStream.Position = fileTableOffset; if (CheckFirstAndLastRecord(reader, dataStart, numFiles, &FileRecordIsV2)) - return MegFileVersion.V2; + return MegVersion.V2; } throw new BinaryCorruptedException("Unrecognized .MEG file version."); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/ConstructingMegArchive.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/ConstructingMegArchive.cs index 11bae168..bd616cc8 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/ConstructingMegArchive.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/ConstructingMegArchive.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using PG.StarWarsGame.Files.MEG.Data.Entries; -using PG.StarWarsGame.Files.MEG.Files; namespace PG.StarWarsGame.Files.MEG.Data.Archives; @@ -12,7 +11,7 @@ internal sealed class ConstructingMegArchive : MegDataEntryHolderBase virtualEntries, - MegFileVersion megVersion, + MegVersion megVersion, uint expectedFileSize, bool encrypted) : base(virtualEntries) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/IConstructingMegArchive.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/IConstructingMegArchive.cs index d0ad2bdb..ad273082 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/IConstructingMegArchive.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/IConstructingMegArchive.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. using PG.StarWarsGame.Files.MEG.Data.Entries; -using PG.StarWarsGame.Files.MEG.Files; namespace PG.StarWarsGame.Files.MEG.Data.Archives; @@ -13,7 +12,7 @@ internal interface IConstructingMegArchive : IMegDataEntryHolder -/// Contains all data entries of a single, physical .MEG file. +/// Represents the list of data entries contained in a MEG archive. +///
+/// The entries are sorted by their CRC32 checksum, which is calculated over the file name of the entry. ///
public interface IMegArchive : IMegDataEntryHolder; \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/IMegDataEntryHolder.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/IMegDataEntryHolder.cs index 02a99e34..de320cd3 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/IMegDataEntryHolder.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/IMegDataEntryHolder.cs @@ -10,7 +10,7 @@ namespace PG.StarWarsGame.Files.MEG.Data.Archives; /// -/// Holds a CRC32-sorted list of MEG data entries. +/// Represents a CRC32-sorted list of MEG data entries. /// /// The type of the data entry. public interface IMegDataEntryHolder : IReadOnlyList where T : IMegDataEntry @@ -18,8 +18,8 @@ public interface IMegDataEntryHolder : IReadOnlyList where T : IMegDataEnt /// /// Determines whether the contains a specific file entry. /// - /// The entry to locate in the . - /// if the is found in the archive; otherwise, . + /// The entry to locate in the . + /// if is found in the ; otherwise, . bool Contains(T entry); /// @@ -30,24 +30,24 @@ public interface IMegDataEntryHolder : IReadOnlyList where T : IMegDataEnt int IndexOf(T entry); /// - /// Gets a list of data entries with the matching CRC32 checksum or an empty list, if the CRC32 checksum is not found. + /// Gets the list of data entries with the matching CRC32 checksum, or an empty list if the CRC32 checksum is not found. /// - /// The CRC to match. - /// List of matching data entries. + /// The CRC32 checksum to match. + /// The list of matching data entries. ImmutableFrugalList EntriesWithCrc(Crc32 crc); /// - /// Get the first data entry with the matching CRC32 checksum. + /// Gets the first data entry with the matching CRC32 checksum. /// - /// The CRC to match. + /// The CRC32 checksum to match. /// The first entry in the with the specified checksum. /// is not found in the . T FirstEntryWithCrc(Crc32 crc); /// - /// Tries to find any by matching the specified search pattern. + /// Finds all entries matching the specified search pattern. ///
- /// The resulting list is ordered by CRC32 of the file name, just are a MEG archive is ordered. The list is empty, if no matches are found. + /// The resulting list is ordered by CRC32 of the file name, just as a MEG archive is ordered. The list is empty if no matches are found. ///
/// /// The search pattern supports globbing. So **/*.xml is a valid query. @@ -58,8 +58,8 @@ public interface IMegDataEntryHolder : IReadOnlyList where T : IMegDataEnt /// The search pattern might produce false-positive and false negatives, since this method is not designed to resolve paths. /// /// The globbing pattern. - /// When the pattern ignores the character casing. - /// A list with all entries matching the specified pattern. + /// to ignore the character casing of the pattern; otherwise, . + /// A list of all entries matching the specified pattern. /// is empty. /// is . ImmutableFrugalList FindAllEntries(string searchPattern, bool caseInsensitive); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/MegArchive.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/MegArchive.cs index f1f354c5..8a655d3b 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/MegArchive.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/MegArchive.cs @@ -2,16 +2,17 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. using System.Collections.Generic; +using System.Diagnostics; using PG.StarWarsGame.Files.MEG.Data.Entries; namespace PG.StarWarsGame.Files.MEG.Data.Archives; /// +[DebuggerDisplay("{Count} entries")] internal sealed class MegArchive : MegDataEntryHolderBase, IMegArchive { /// - /// Initializes a new instance of the class - /// by coping all elements of the given list. + /// Initializes a new instance of the class by copying all elements of the specified list. /// /// The list of entries in this archive. internal MegArchive(IList entries) : base(entries) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/MegDataEntryHolderBase.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/MegDataEntryHolderBase.cs index f8090e96..59a19e06 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/MegDataEntryHolderBase.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/MegDataEntryHolderBase.cs @@ -19,7 +19,7 @@ namespace PG.StarWarsGame.Files.MEG.Data.Archives; public abstract class MegDataEntryHolderBase : IMegDataEntryHolder where T : IMegDataEntry { /// - /// All data entries of this instance. + /// Represents all data entries of this instance. /// protected readonly ReadOnlyCollection Entries; @@ -34,10 +34,11 @@ public abstract class MegDataEntryHolderBase : IMegDataEntryHolder where T public int Count => Entries.Count; /// - /// Initializes a new instance of the class - /// by coping all elements of the given list. + /// Initializes a new instance of the class by copying all elements of the specified list. /// /// The list of entries in this archive. + /// is . + /// is not sorted by CRC32 checksum. protected MegDataEntryHolderBase(IList entries) { if (entries == null) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/VirtualMegArchive.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/VirtualMegArchive.cs index f44280e6..2beaffbf 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/VirtualMegArchive.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Archives/VirtualMegArchive.cs @@ -2,16 +2,17 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. using System.Collections.Generic; +using System.Diagnostics; using PG.StarWarsGame.Files.MEG.Data.Entries; namespace PG.StarWarsGame.Files.MEG.Data.Archives; /// +[DebuggerDisplay("{Count} entries")] internal sealed class VirtualMegArchive : MegDataEntryHolderBase, IVirtualMegArchive { /// - /// Initializes a new instance of the class - /// by coping all elements of the given list. + /// Initializes a new instance of the class by copying all elements of the specified list. /// /// The list of files in this archive. internal VirtualMegArchive(IList files) : base(files) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/IMegDataEntry.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/IMegDataEntry.cs index 2990cc6b..6edd82dd 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/IMegDataEntry.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/IMegDataEntry.cs @@ -16,7 +16,8 @@ namespace PG.StarWarsGame.Files.MEG.Data.Entries; public interface IMegDataEntry : IHasCrc32, IComparable { /// - /// Gets the path of the entry as defined in the *.MEG file.
+ /// Gets the path of the entry as defined in the *.MEG file. + ///
/// Usually this file path is relative to the game or mod's DATA directory, e.g. Data/My/file.xml ///
/// @@ -31,7 +32,7 @@ public interface IMegDataEntry : IHasCrc32, IComparable public interface IMegDataEntry : IMegDataEntry where T : IDataEntryLocation { /// - /// Get the location information of this data entry. + /// Gets the location information of this data entry. /// public T Location { get; } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntry.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntry.cs index e126fbb0..2f42d0d9 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntry.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntry.cs @@ -23,12 +23,12 @@ public sealed class MegDataEntry : MegDataEntryBase, IEqua public override Crc32 Crc32 { get; } /// - /// The original file path value without the default MEG encoding applied. + /// Gets the original file path value without the default MEG encoding applied. /// public string OriginalPath { get; } /// - /// Indicates whether the file is encrypted + /// Gets a value that indicates whether the file is encrypted. /// public bool Encrypted { get; } @@ -36,12 +36,12 @@ public sealed class MegDataEntry : MegDataEntryBase, IEqua /// Initializes a new instance of the class. ///
/// The file path of the entry. - /// The CRC32 checksum of the filePath - /// The location information of the entry inside it's MEG file. - /// Indicates whether this entry is encrypted or not. + /// The CRC32 checksum of the file path. + /// The location information of the entry inside its MEG file. + /// if this entry is encrypted; otherwise, . /// The original file path value. /// or is . - /// is empty or only consists of while spaces. + /// is empty or only consists of white spaces. /// is empty. internal MegDataEntry(string entryPath, Crc32 crc32, MegDataEntryLocation location, bool encrypted, string originalPath) : base(location) { diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntryBase.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntryBase.cs index ae83b729..314b4999 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntryBase.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntryBase.cs @@ -27,7 +27,7 @@ public abstract class MegDataEntryBase : IMegDataEntry, IEquatable class with a given data entry location. /// /// The location information of this entry. - /// The is . + /// is . protected MegDataEntryBase(T location) { Location = location ?? throw new ArgumentNullException(nameof(location)); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntryReference.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntryReference.cs index 20f70522..b58b578d 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntryReference.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/Entries/MegDataEntryReference.cs @@ -23,7 +23,7 @@ public sealed class MegDataEntryReference : MegDataEntryBase Location.DataEntry.Crc32; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// /// The full location information of this data entry. public MegDataEntryReference(MegDataEntryLocationReference location) : base(location) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/IDataEntryLocation.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/IDataEntryLocation.cs index cfda731c..4caf943a 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/IDataEntryLocation.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/IDataEntryLocation.cs @@ -4,6 +4,6 @@ namespace PG.StarWarsGame.Files.MEG.Data.EntryLocations; /// -/// Denotes a type to be used as a data entry location. +/// Represents a type to be used as a data entry location. /// public interface IDataEntryLocation; \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryLocation.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryLocation.cs index 79d8e19d..a0b098eb 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryLocation.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryLocation.cs @@ -6,7 +6,7 @@ namespace PG.StarWarsGame.Files.MEG.Data.EntryLocations; /// -/// Location of an archived MEG data entry inside a .MEG file. +/// Represents the location of an archived MEG data entry inside a .MEG file. /// public readonly struct MegDataEntryLocation : IDataEntryLocation, IEquatable { @@ -21,9 +21,9 @@ namespace PG.StarWarsGame.Files.MEG.Data.EntryLocations; public uint Size { get; } /// - /// Initializes a new instance of the structure to a given file offset and file size. + /// Initializes a new instance of the structure. /// - /// The offset of the file in bytes from the start its .MEG file. + /// The offset of the file in bytes from the start of its .MEG file. /// The size of the file in bytes. public MegDataEntryLocation(uint offset, uint size) { diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryLocationReference.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryLocationReference.cs index 57904ff6..2150bdba 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryLocationReference.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/EntryLocations/MegDataEntryLocationReference.cs @@ -2,20 +2,21 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. using System; +using System.Diagnostics.CodeAnalysis; using PG.StarWarsGame.Files.MEG.Data.Entries; using PG.StarWarsGame.Files.MEG.Files; namespace PG.StarWarsGame.Files.MEG.Data.EntryLocations; /// -/// Location reference of an existing MEG data entry and its owning .MEG file. +/// Represents a location reference of an existing MEG data entry and its owning MEG. /// public sealed class MegDataEntryLocationReference : IDataEntryLocation, IEquatable { /// - /// Gets the owning .MEG file of . + /// Gets the MEG that owns . /// - public IMegFile MegFile { get; } + public IMegDataSource Source { get; } /// /// Gets the referenced MEG data entry. @@ -23,19 +24,19 @@ public sealed class MegDataEntryLocationReference : IDataEntryLocation, IEquatab public MegDataEntry DataEntry { get; } /// - /// Gets a value indicating whether the data exists in the meg file referenced in this instance. + /// Gets a value that indicates whether the data exists in the MEG referenced in this instance. /// - public bool Exists => MegFile.Archive.Contains(DataEntry); - + public bool Exists => Source.Archive.Contains(DataEntry); + /// - /// Initializes a new instance of the . + /// Initializes a new instance of the class. /// - /// The owning .MEG file - /// The referenced . - /// The or is . - public MegDataEntryLocationReference(IMegFile megFile, MegDataEntry dataEntry) + /// The MEG that owns the entry. + /// The referenced data entry. + /// or is . + public MegDataEntryLocationReference(IMegDataSource source, MegDataEntry dataEntry) { - MegFile = megFile ?? throw new ArgumentNullException(nameof(megFile)); + Source = source ?? throw new ArgumentNullException(nameof(source)); DataEntry = dataEntry ?? throw new ArgumentNullException(nameof(dataEntry)); } @@ -46,7 +47,7 @@ public bool Equals(MegDataEntryLocationReference? other) return false; if (ReferenceEquals(this, other)) return true; - return MegFile.Equals(other.MegFile) && DataEntry.Equals(other.DataEntry); + return Source.Equals(other.Source) && DataEntry.Equals(other.DataEntry); } /// @@ -58,12 +59,23 @@ public override bool Equals(object? obj) /// public override int GetHashCode() { - return HashCode.Combine(MegFile, DataEntry); + return HashCode.Combine(Source, DataEntry); } /// public override string ToString() { - return $"{MegFile.FilePath}::{DataEntry.Path}"; + return $"{DescribeSource(Source)}::{DataEntry.Path}"; + } + + [ExcludeFromCodeCoverage] + internal static string DescribeSource(IMegDataSource source) + { + return source switch + { + IMegFile megFile => megFile.FilePath, + InMemoryMeg inMemoryMeg => inMemoryMeg.Name, + _ => "" + }; } -} \ No newline at end of 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 3456b855..32d3b8d4 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 @@ -3,17 +3,18 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.IO.Abstractions; namespace PG.StarWarsGame.Files.MEG.Data.EntryLocations; /// -/// 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. +/// Represents the origin of a MEG data entry which is either packed in a MEG archive, present on the file system, or as an in-memory byte buffer. /// public sealed class MegDataEntryOriginInfo : IDataEntryLocation, IEquatable { /// - /// Gets a value indicating whether the data entry originates from a local file on the file system. + /// Gets a value that indicates whether the data entry originates from a local file on the file system. /// /// /// If this property returns , the property is guaranteed to be non-. @@ -25,19 +26,19 @@ public sealed class MegDataEntryOriginInfo : IDataEntryLocation, IEquatable FileInfo != null; /// - /// Gets a value indicating whether the data entry originates from a MEG archive reference. + /// Gets a value that indicates whether the data entry originates from a MEG archive reference. /// /// /// If this property returns , the property is guaranteed to be non-. /// /// - /// if the data entry is from a referend MEG entry; otherwise, . + /// if the data entry is from a referenced MEG entry; otherwise, . /// [MemberNotNullWhen(true, nameof(MegFileLocation))] public bool IsEntryReference => MegFileLocation != null; /// - /// Gets a value indicating whether the data entry originates from an in-memory byte buffer. + /// Gets a value that indicates whether the data entry originates from an in-memory byte buffer. /// /// /// If this property returns , the property is guaranteed to be non-. @@ -46,12 +47,12 @@ public sealed class MegDataEntryOriginInfo : IDataEntryLocation, IEquatable Bytes != null; /// - /// Gets the MEG file's data entry. if not present. + /// Gets the MEG file's data entry. if not present. /// public MegDataEntryLocationReference? MegFileLocation { get; } /// - /// Gets the file's path on the file system. if not present. + /// Gets the file's path on the file system. if not present. /// public IFileInfo? FileInfo { get; } @@ -78,7 +79,7 @@ public MegDataEntryOriginInfo(IFileInfo fileInfo) /// Initializes a new instance of the class to the specified MEG file's data entry. /// /// The MEG file's data entry. - /// If is . + /// is . public MegDataEntryOriginInfo(MegDataEntryLocationReference locationReference) { MegFileLocation = locationReference ?? throw new ArgumentNullException(nameof(locationReference)); @@ -132,6 +133,7 @@ public override int GetHashCode() } /// + [ExcludeFromCodeCoverage] public override string ToString() { if (IsLocalFile) @@ -140,4 +142,15 @@ public override string ToString() return $"MEG Entry: '{MegFileLocation}'"; return $"Bytes: {Bytes!.Length} bytes"; } + + internal Stream GetDataStream() + { + if (IsBytes) + return new MemoryStream(Bytes, writable: false); + + if (IsLocalFile) + return FileInfo.OpenRead(); + + return MegFileLocation!.Source.GetData(MegFileLocation.DataEntry); + } } diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/IMegDataSource.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/IMegDataSource.cs new file mode 100644 index 00000000..a339f44a --- /dev/null +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/IMegDataSource.cs @@ -0,0 +1,29 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using PG.StarWarsGame.Files.MEG.Data.Archives; +using PG.StarWarsGame.Files.MEG.Data.Entries; +using PG.StarWarsGame.Files.MEG.Utilities; + +namespace PG.StarWarsGame.Files.MEG.Data; + +/// +/// Represents a MEG archive along with the necessary means to access its data. +/// +public interface IMegDataSource : IDisposable +{ + /// + /// Gets the archive metadata: the table of contents describing the data entries contained in this MEG. + /// + IMegArchive Archive { get; } + + /// + /// Gets a read-only stream over the data of the specified . + /// + /// The data entry to read. + /// A read-only stream containing the entry's data. + /// is . + /// is not contained in this MEG. + MegEntryStream GetData(MegDataEntry entry); +} diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/InMemoryMeg.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/InMemoryMeg.cs new file mode 100644 index 00000000..a19526cc --- /dev/null +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/InMemoryMeg.cs @@ -0,0 +1,54 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Diagnostics; +using System.IO; +using AnakinRaW.CommonUtilities; +using PG.StarWarsGame.Files.MEG.Data.Archives; +using PG.StarWarsGame.Files.MEG.Data.Entries; +using PG.StarWarsGame.Files.MEG.Utilities; + +namespace PG.StarWarsGame.Files.MEG.Data; + +/// +/// An that holds a whole MEG in memory and serves entry data from a byte buffer. +/// +[DebuggerDisplay("{Name} ({Archive.Count})")] +internal sealed class InMemoryMeg : DisposableObject, IMegDataSource +{ + private readonly byte[] _megData; + + /// + public IMegArchive Archive { get; } + + internal string Name { get; } = $""; + + /// + /// Initializes a new instance of the class with the specified archive and data. + /// + /// The table of contents of the MEG. + /// The raw bytes of the whole MEG archive. + internal InMemoryMeg(IMegArchive archive, byte[] megData) + { + Archive = archive ?? throw new ArgumentNullException(nameof(archive)); + _megData = megData ?? throw new ArgumentNullException(nameof(megData)); + } + + /// + public MegEntryStream GetData(MegDataEntry entry) + { + if (entry is null) + throw new ArgumentNullException(nameof(entry)); + if (!Archive.Contains(entry)) + throw new EntryNotInMegException(this, entry); + if (entry.Encrypted) + throw new NotImplementedException("Encrypted archives are currently not supported"); + + if (entry.Location.Size == 0) + return MegEntryStream.CreateEmptyStream(entry.Path); + + var stream = new MemoryStream(_megData, writable: false); + return new MegEntryStream(entry.Path, stream, entry.Location.Offset, entry.Location.Size); + } +} 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 e0ac2b58..2309d8f2 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 @@ -14,12 +14,12 @@ namespace PG.StarWarsGame.Files.MEG.Data; /// -/// Represents a container with information for of an MEG entry used for building .MEG files. +/// Represents a container with information for an MEG entry used for building .MEG files. /// public sealed class MegDataEntryBuilderInfo { /// - /// The actual location of a MEG data entry. + /// Gets the actual location of a MEG data entry. /// public MegDataEntryOriginInfo OriginInfo { get; } @@ -29,7 +29,7 @@ public sealed class MegDataEntryBuilderInfo public string EntryPath { get; } /// - /// Gets whether the data entry shall be encrypted or not within the constructed MEG file. + /// Gets a value that indicates whether the data entry is encrypted within the constructed MEG file. /// public bool Encrypted { get; } @@ -66,7 +66,7 @@ internal MegDataEntryBuilderInfo(MegDataEntryOriginInfo originInfo, string? over /// -or- /// does not exist in . /// - /// or is . + /// or is . public static MegDataEntryBuilderInfo FromEntry(IMegFile megFile, MegDataEntry dataEntry, string? overrideEntryPath = null, bool? overrideEncrypted = null) { if (megFile == null) @@ -111,7 +111,7 @@ public static MegDataEntryBuilderInfo FromEntryReference(MegDataEntryLocationRef /// The path of the entry within the MEG archive. /// When not , the specified path will be used; otherwise the full path of path will be used. /// - /// Sets whether the data shall be encrypted or not. Default is . + /// to encrypt the data; otherwise, . The default is . /// or is . /// /// does not exist. @@ -135,7 +135,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 . + /// to encrypt the data; otherwise, . The default is . /// is . /// is empty. public static MegDataEntryBuilderInfo FromBytes(ReadOnlySpan bytes, string entryPath, bool encrypt = false) @@ -150,7 +150,7 @@ public static MegDataEntryBuilderInfo FromBytes(ReadOnlySpan bytes, string /// /// 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 . + /// to encrypt the data; otherwise, . The default is . /// or is . /// is empty. public static MegDataEntryBuilderInfo FromBytes(byte[] bytes, string entryPath, bool encrypt = false) diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegEncryptionData.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegEncryptionData.cs similarity index 91% rename from PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegEncryptionData.cs rename to PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegEncryptionData.cs index f6472206..a54d0db1 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegEncryptionData.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegEncryptionData.cs @@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis; using AnakinRaW.CommonUtilities; -namespace PG.StarWarsGame.Files.MEG.Files; +namespace PG.StarWarsGame.Files.MEG.Data; /// /// Stores an AES-CBC 128-bit encryption key and initialization vector. @@ -24,7 +24,7 @@ public byte[] IV get { if (_ivValue is null) - throw new ObjectDisposedException(GetType().Name); + throw new ObjectDisposedException(nameof(MegEncryptionData)); return (byte[])_ivValue.Clone(); } } @@ -38,7 +38,7 @@ public byte[] Key get { if (_keyValue is null) - throw new ObjectDisposedException(GetType().Name); + throw new ObjectDisposedException(nameof(MegEncryptionData)); return (byte[])_keyValue.Clone(); } } @@ -83,7 +83,7 @@ protected override void DisposeResources() internal MegEncryptionData Copy() { if (_keyValue is null || _ivValue is null) - throw new ObjectDisposedException(GetType().Name); + throw new ObjectDisposedException(nameof(MegEncryptionData)); return new MegEncryptionData(_keyValue, _ivValue); } diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegFileVersion.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegVersion.cs similarity index 92% rename from PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegFileVersion.cs rename to PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegVersion.cs index f927bca4..0ac15d13 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegFileVersion.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Data/MegVersion.cs @@ -1,13 +1,13 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -namespace PG.StarWarsGame.Files.MEG.Files; +namespace PG.StarWarsGame.Files.MEG.Data; /// /// Represents the available file versions of a PG .MEG archive as defined in the /// .MEG file specification. /// -public enum MegFileVersion +public enum MegVersion { /// /// .MEG file version 1. diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Exceptions/EntryNotInMegException.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Exceptions/EntryNotInMegException.cs index f85a792c..c4101495 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Exceptions/EntryNotInMegException.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Exceptions/EntryNotInMegException.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. using System; +using PG.StarWarsGame.Files.MEG.Data; +using PG.StarWarsGame.Files.MEG.Data.Entries; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; namespace PG.StarWarsGame.Files.MEG; @@ -22,8 +24,18 @@ public sealed class EntryNotInMegException : Exception /// /// The non-existing data entry location. internal EntryNotInMegException(MegDataEntryLocationReference locationReference) + : this(locationReference.Source, locationReference.DataEntry) { - _entry = locationReference.DataEntry.Path; - _megFile = locationReference.MegFile.FilePath; + } + + /// + /// Initializes a new instance of the class for an entry that is not contained in the given MEG source. + /// + /// The MEG source that does not contain . + /// The data entry that is not contained in . + internal EntryNotInMegException(IMegDataSource source, MegDataEntry entry) + { + _entry = entry.Path; + _megFile = MegDataEntryLocationReference.DescribeSource(source); } } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Exceptions/MegSizeException.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Exceptions/MegSizeException.cs index 2ff0f336..4db8fc61 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Exceptions/MegSizeException.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Exceptions/MegSizeException.cs @@ -13,7 +13,8 @@ public class MegSizeException : Exception { /// /// Initializes a new instance of the class with a specified error message. - /// The message that describes the error. + /// + /// The message that describes the error. public MegSizeException(string? message) : base(message) { } diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/IMegFile.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/IMegFile.cs index 77ff05e7..87df0e45 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/IMegFile.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/IMegFile.cs @@ -1,19 +1,15 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; namespace PG.StarWarsGame.Files.MEG.Files; /// -/// The provided holder for a Petroglyph -/// .MEG file. -/// *.MEG or Mega files are a proprietary archive type bundling files together in a RAM friendly way. +/// Represents a Petroglyph Mega File. /// -public interface IMegFile : IPetroglyphFileHolder -{ - /// - /// Gets the archive model of this MEG file. - /// - IMegArchive Archive { get; } -} \ No newline at end of file +/// +/// Mega files are an archive type bundling files together in a RAM friendly way. +/// +public interface IMegFile : IPetroglyphFileHolder, IMegDataSource; diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegFile.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegFile.cs index 2f029d15..77bdeae5 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegFile.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegFile.cs @@ -1,23 +1,28 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using System; using PG.StarWarsGame.Files.MEG.Data.Archives; +using PG.StarWarsGame.Files.MEG.Data.Entries; +using PG.StarWarsGame.Files.MEG.Utilities; +using System; +using System.Diagnostics; +using System.IO; namespace PG.StarWarsGame.Files.MEG.Files; -/// /// -/// This class does not hold the actual data of the files packaged in a *.MEG file, -/// but all necessary meta-information to extract a requested file on-demand. +/// This class does not hold the actual data of the files packaged in a *.MEG file, +/// but all necessary meta-information to extract a requested file on-demand. /// +/// +[DebuggerDisplay("{FilePath} ({Archive.Count})")] internal sealed class MegFile : PetroglyphFileHolder, IMegFile { /// public IMegArchive Archive => Content; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// It is safe to dispose the after an instance of this class has been created. @@ -29,4 +34,26 @@ public MegFile(IMegArchive model, MegFileInformation fileInformation, IServicePr base(model, fileInformation, serviceProvider) { } -} \ No newline at end of file + + /// + public MegEntryStream GetData(MegDataEntry entry) + { + if (entry is null) + throw new ArgumentNullException(nameof(entry)); + if (!Archive.Contains(entry)) + throw new EntryNotInMegException(this, entry); + if (entry.Encrypted) + throw new NotImplementedException("Encrypted archives are currently not supported"); + + if (!FileSystem.File.Exists(FilePath)) + throw new FileNotFoundException($"MEG file '{FilePath}' does not exist", FilePath); + + // Cause MIKE.NL's tool uses the offset megFile[megSize + 1] for empty Entries we would cause an ArgumentOutOfRangeException + // when trying to access this index on a real file. Therefore, we return the Null stream. + if (entry.Location.Size == 0) + return MegEntryStream.CreateEmptyStream(entry.Path); + + var megFileStream = FileSystem.FileStream.New(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read); + return new MegEntryStream(entry.Path, megFileStream, entry.Location.Offset, entry.Location.Size); + } +} diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegFileInformation.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegFileInformation.cs index b298b1ab..aa2a96b2 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegFileInformation.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Files/MegFileInformation.cs @@ -1,6 +1,7 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using PG.StarWarsGame.Files.MEG.Data; using System; using System.Diagnostics.CodeAnalysis; @@ -15,12 +16,12 @@ namespace PG.StarWarsGame.Files.MEG.Files; public sealed record MegFileInformation : PetroglyphFileInformation { /// - /// Gets the MEG file version of the MEG file. + /// Gets the MEG version of the MEG file. /// - public MegFileVersion FileVersion { get; } + public MegVersion Version { get; } /// - /// Gets a value indicating whether an is encrypted. + /// Gets a value that indicates whether the is encrypted. /// [MemberNotNullWhen(true, nameof(EncryptionData))] public bool HasEncryption => EncryptionData is not null; @@ -34,19 +35,19 @@ public sealed record MegFileInformation : PetroglyphFileInformation /// Initializes a new instance of the class. /// /// The file path of the MEG file. - /// The file version of the MEG file. - /// The encryption data of MEG file or if the MEG file is not encrypted. - /// is null. + /// The MEG version of the file. + /// The encryption data of the MEG file, or if the MEG file is not encrypted. + /// is . /// is empty. /// - /// is not but is not . + /// is not but is not . /// [SetsRequiredMembers] - public MegFileInformation(string path, MegFileVersion fileVersion, MegEncryptionData? encryptionData = null) : base(path) + public MegFileInformation(string path, MegVersion version, MegEncryptionData? encryptionData = null) : base(path) { - if (encryptionData is not null && fileVersion != MegFileVersion.V3) - throw new ArgumentException("Encrypted MEG files are required to be version V3.", nameof(fileVersion)); - FileVersion = fileVersion; + if (encryptionData is not null && version != MegVersion.V3) + throw new ArgumentException("Encrypted MEG files are required to be version V3.", nameof(version)); + Version = version; EncryptionData = encryptionData; } @@ -55,7 +56,7 @@ public MegFileInformation(string path, MegFileVersion fileVersion, MegEncryption [SetsRequiredMembers] private MegFileInformation(MegFileInformation original) : base(original) { - FileVersion = original.FileVersion; + Version = original.Version; EncryptionData = original.EncryptionData?.Copy(); } #pragma warning restore IDE0051 diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/MegServiceContribution.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/MegServiceContribution.cs index 2ecd172b..03c6bcff 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/MegServiceContribution.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/MegServiceContribution.cs @@ -17,14 +17,13 @@ public static class MegServiceContribution /// /// Adds all necessary services provided by this library to the specified . /// - /// The to add services to. + /// The collection to add services to. public static void SupportMEG(this IServiceCollection serviceCollection) { - serviceCollection.AddSingleton(sp => new MegFileService(sp)); + serviceCollection.AddSingleton(sp => new MegService(sp)); serviceCollection.AddSingleton(sp => new MegFileExtractor(sp)); serviceCollection.AddSingleton(sp => new MegBinaryServiceFactory(sp)); serviceCollection.AddSingleton(sp => new MegVersionIdentifier(sp)); - serviceCollection.AddSingleton(sp => new MegDataStreamFactory(sp)); serviceCollection.AddSingleton(_ => new VirtualMegArchiveBuilder()); serviceCollection.AddSingleton(sp => new PetroglyphRelativeDataEntryPathResolver(sp)); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Builders/EmpireAtWarMegBuilder.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Builders/EmpireAtWarMegBuilder.cs index a9eebc1e..21e479f4 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Builders/EmpireAtWarMegBuilder.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Builders/EmpireAtWarMegBuilder.cs @@ -18,21 +18,17 @@ public sealed class EmpireAtWarMegBuilder : PetroglyphGameMegBuilder /// public override IMegDataEntryPathNormalizer DataEntryPathNormalizer { get; } = new EmpireAtWarMegDataEntryPathNormalizer(); - /// - /// Gets the data entry validator to validate MEG data entries to be compliant to Empire at War - /// + /// public override IMegDataEntryValidator DataEntryValidator { get; } = new EmpireAtWarMegDataEntryValidator(); - /// - /// Gets the validator to validate whether an is compliant to Empire at War - /// + /// public override IMegFileInformationValidator MegFileInformationValidator { get; } + /// /// - /// 2GB (2^31 - 1 bytes), which is the safe limit for Petroglyph's games + /// The maximum file size, in bytes, which is 2GB (2^31 - 1 bytes), the safe limit for Petroglyph's games /// Star Wars: Empire at War and its extension Empire at War: Forces of Corruption. /// - /// public override uint MaxMegFileSize { get; } = MaxMegSizeProvider.GetMegMaxSize(MaxMegSizeMode.EawFoc).MaxFileSize; /// @@ -44,7 +40,7 @@ public sealed class EmpireAtWarMegBuilder : PetroglyphGameMegBuilder /// The path for this . /// The service provider. /// or is . - /// is empty. + /// is empty. public EmpireAtWarMegBuilder(string baseDirectory, IServiceProvider services) : base(baseDirectory, services) { MegFileInformationValidator = new EmpireAtWarMegFileInformationValidator(services); diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Builders/PetroglyphGameMegBuilder.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Builders/PetroglyphGameMegBuilder.cs index b01d6c0b..e0be38b5 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Builders/PetroglyphGameMegBuilder.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Builders/PetroglyphGameMegBuilder.cs @@ -68,7 +68,7 @@ public abstract class PetroglyphGameMegBuilder : MegBuilderBase /// The path for this . /// The service provider. /// or is . - /// is empty. + /// is empty. protected PetroglyphGameMegBuilder(string baseDirectory, IServiceProvider services) : base(services) { ThrowHelper.ThrowIfNullOrEmpty(baseDirectory); @@ -78,9 +78,10 @@ protected PetroglyphGameMegBuilder(string baseDirectory, IServiceProvider servic } /// - /// Returns a relative path from a path and the of the . - /// Returns if is invalid or not a part of . - ///
+ /// Resolves a relative entry path from the specified path and the of the . + ///
+ /// + /// The returned path is neither fully normalized nor validated by the rules of the . ///
/// For example: ///
@@ -89,10 +90,11 @@ protected PetroglyphGameMegBuilder(string baseDirectory, IServiceProvider servic /// "/gameBasePath/xml/file.xml" --> "xml/file.xml" /// "/NOTgamePath/xml/file.xml" --> null /// "../xml/file.xml" --> null - ///
- /// The returned path is neither fully normalized nor validated by the rules of the . + ///
/// The path to get the relative path from. - /// The resolved, relative entry path. + /// + /// The resolved, relative entry path, or if is invalid or not a part of . + /// public string? ResolveEntryPath(string? path) { return _pathResolver.ResolvePath(path, BaseDirectory); 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 062d6a6d..04ab8862 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 @@ -14,12 +14,12 @@ namespace PG.StarWarsGame.Files.MEG.Services.Builder; /// -/// Service to create MEG files from local files or other MEG data entries ensuring custom validation and normalization rules. +/// Represents a service that creates MEG files from local files or other MEG data entries, ensuring custom validation and normalization rules. /// public interface IMegBuilder : IFileBuilder, MegFileInformation> { /// - /// Gets a value indicating whether the normalizes a data entry's path before adding it. + /// Gets a value that indicates whether the normalizes a data entry's path before adding it. /// /// /// Path normalization is performed before encoding. @@ -27,7 +27,7 @@ public interface IMegBuilder : IFileBuilder - /// Gets a value indicating whether the overwrites an already existing data entry with the new version when trying to a data entry + /// Gets a value that indicates whether the overwrites an already existing data entry with the new version when trying to a data entry /// or does not add the new data entry. /// /// @@ -63,7 +63,7 @@ public interface IMegBuilder : IFileBuilder /// The local path to the file which get added as an data entry. /// The desired file path of the data entry inside the MEG archive. - /// Indicates whether the data entry shall be encrypted. + /// to encrypt the data entry; otherwise, . /// The result of this operation. /// or is . /// or is empty. @@ -93,7 +93,7 @@ MegDataEntryAddResult AddEntry(MegDataEntryLocationReference entryReference, str /// /// 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. + /// to encrypt the data entry; otherwise, . /// The result of this operation. /// is . /// is empty. @@ -108,7 +108,7 @@ MegDataEntryAddResult AddEntry(MegDataEntryLocationReference entryReference, str /// /// 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. + /// to encrypt the data entry; otherwise, . /// The result of this operation. /// or is . /// is empty. @@ -142,7 +142,7 @@ MegDataEntryAddResult AddEntry(MegDataEntryLocationReference entryReference, str /// A function that generates the file path for each part based on its index (1-based). /// Not invoked if only a single file is created. /// - /// A value indicating whether to overwrite existing files. + /// to overwrite existing files; otherwise, . /// /// is . /// -or- @@ -163,9 +163,10 @@ MegDataEntryAddResult AddEntry(MegDataEntryLocationReference entryReference, str /// due to the size constraints of MEG files the builder produces. /// /// - /// The method will return at least value 1. + /// The method will return at least value 1. /// + /// The version of the MEG file, which determines the size constraints. /// The minimum number of MEG files required to accommodate the provided data entries. /// It is impossible to create MEG files because of the current state of the builder. - int GetMinRequiredMegFiles(MegFileVersion megVersion); + int GetMinRequiredMegFiles(MegVersion megVersion); } \ No newline at end of file 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 b1db398a..5222981f 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 @@ -43,25 +43,25 @@ public abstract class MegBuilderBase /// /// - /// by default. + /// The default is . /// public virtual bool OverwritesDuplicateEntries => true; /// /// - /// by default. + /// The file information validator. The default is a . /// public virtual IMegFileInformationValidator MegFileInformationValidator { get; } /// /// - /// by default. + /// The data entry validator. The default is a . /// public virtual IMegDataEntryValidator DataEntryValidator { get; } = new BinaryMegDataEntryValidator(); /// /// - /// , meaning no normalizer is specified. + /// The data entry path normalizer, or if no normalizer is specified. The default is . /// public virtual IMegDataEntryPathNormalizer? DataEntryPathNormalizer => null; @@ -69,7 +69,7 @@ public abstract class MegBuilderBase /// Gets the maximum allowed size, in bytes, for a MEG file created by this builder. /// /// - /// 4GB (2^32 - 1 bytes) by default + /// The maximum allowed size, in bytes, for a MEG file. The default is 4GB (2^32 - 1 bytes). /// public virtual uint MaxMegFileSize { get; } = MaxMegSizeProvider.GetMegMaxSize(MaxMegSizeMode.Binary).MaxFileSize; @@ -167,7 +167,7 @@ public void BuildMany(MegFileInformation initialFileInformation, Func entries) } /// - public int GetMinRequiredMegFiles(MegFileVersion megVersion) + public int GetMinRequiredMegFiles(MegVersion megVersion) { return SplitIntoMinRequiredParts(megVersion, DataEntries).Count; } @@ -225,7 +225,7 @@ public int GetMinRequiredMegFiles(MegFileVersion megVersion) /// It is impossible to split into multiple MEG files because of the current state of the builder. /// protected ICollection> SplitIntoMinRequiredParts( - MegFileVersion megVersion, + MegVersion megVersion, IEnumerable builderInfo) { var metadataSizeCalculator = Services.GetRequiredService() @@ -264,8 +264,8 @@ protected ICollection> SplitIntoMinRequired /// protected sealed override void BuildFileCore(FileSystemStream fileStream, MegFileInformation fileInformation, IReadOnlyCollection data) { - var megService = Services.GetRequiredService(); - megService.CreateMegArchive(fileStream, fileInformation.FileVersion, fileInformation.EncryptionData, data); + var megService = Services.GetRequiredService(); + megService.CreateMegArchive(fileStream, fileInformation.Version, fileInformation.EncryptionData, data); } /// diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegDataEntryAddResult.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegDataEntryAddResult.cs index ca84dd48..830f12da 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegDataEntryAddResult.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegDataEntryAddResult.cs @@ -10,12 +10,12 @@ namespace PG.StarWarsGame.Files.MEG.Services.Builder; /// -/// Status information whether a file or data entry was added to an . +/// Represents the result of adding a file or data entry to an . /// public readonly struct MegDataEntryAddResult { /// - /// Gets whether the file or data entry was added or not. + /// Gets a value that indicates whether the file or data entry was added. /// [MemberNotNullWhen(true, nameof(AddedBuilderInfo))] public bool Added => Status == MegDataEntryAddStatus.Added && AddedBuilderInfo is not null; @@ -26,23 +26,23 @@ public readonly struct MegDataEntryAddResult public MegDataEntryAddStatus Status { get; } /// - /// Indicates whether a previous data entry was overwritten. + /// Gets a value that indicates whether a previous data entry was overwritten. /// [MemberNotNullWhen(true, nameof(OverwrittenBuilderInfo))] public bool WasOverwrite => OverwrittenBuilderInfo is not null; /// - /// The data entry info which was added or if no entry was added. + /// Gets the data entry information that was added, or if no entry was added. /// public MegDataEntryBuilderInfo? AddedBuilderInfo { get; } /// - /// The data entry info which was overwritten or if no data entry was overwritten. + /// Gets the data entry information that was overwritten, or if no data entry was overwritten. /// public MegDataEntryBuilderInfo? OverwrittenBuilderInfo { get; } /// - /// A user readable message why the entry was not added. if the entry was added successfully or no message was provided. + /// Gets a user-readable message why the entry was not added, or if the entry was added successfully or no message was provided. /// public string? Message { get; } diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegDataEntryAddStatus.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegDataEntryAddStatus.cs index 63e373ef..fd6b2f48 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegDataEntryAddStatus.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/MegDataEntryAddStatus.cs @@ -4,7 +4,7 @@ namespace PG.StarWarsGame.Files.MEG.Services.Builder; /// -/// Status of adding a file or data entry to an . +/// Specifies the status of adding a file or data entry to an . /// public enum MegDataEntryAddStatus { diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/EmpireAtWarMegDataEntryPathNormalizer.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/EmpireAtWarMegDataEntryPathNormalizer.cs index a009b038..0c685f08 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/EmpireAtWarMegDataEntryPathNormalizer.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/EmpireAtWarMegDataEntryPathNormalizer.cs @@ -20,8 +20,8 @@ public sealed class EmpireAtWarMegDataEntryPathNormalizer : PetroglyphMegDataEnt /// /// Normalizes the specified MEG data entry path the same way as the Empire at War Alamo engine normalizes meg entry paths. /// - /// The normalized entry path. /// The read-only span containing the entry's file path to normalize. + /// The normalized entry path. public override string Normalize(ReadOnlySpan entryPath) { if (entryPath.Length == 0) @@ -86,7 +86,7 @@ private static void AppendRosToSb(ReadOnlySpan value, StringBuilder sb) /// The number of characters written to the destination buffer after normalization. ///
/// - /// Thrown if the destination buffer is not large enough to store the normalized path. + /// is not large enough to store the normalized path. /// protected override int Normalize(ReadOnlySpan entryPath, Span destination) { diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/IMegDataEntryPathNormalizer.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/IMegDataEntryPathNormalizer.cs index c624b739..1477ca4f 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/IMegDataEntryPathNormalizer.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/IMegDataEntryPathNormalizer.cs @@ -34,7 +34,7 @@ public interface IMegDataEntryPathNormalizer /// /// The read-only span containing the entry's file path to normalize. /// The span to write the normalized path into. - /// The number of chars written to are stored to this variable. + /// When this method returns, contains the number of characters written to . This parameter is treated as uninitialized. /// if the normalization was completed and copied to ; otherwise, . bool TryNormalize(ReadOnlySpan entryPath, Span destination, out int charsWritten); } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/MegDataEntryPathNormalizerBase.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/MegDataEntryPathNormalizerBase.cs index f3b29326..f2845d00 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/MegDataEntryPathNormalizerBase.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/MegDataEntryPathNormalizerBase.cs @@ -42,7 +42,7 @@ public bool TryNormalize(ReadOnlySpan entryPath, Span destination, o /// /// The read-only span containing the entry's file path to normalize. /// The span to write the normalized path into. - /// The number of chars written to are stored to this variable.. + /// The number of characters written to . /// is too short. protected abstract int Normalize(ReadOnlySpan entryPath, Span destination); } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/PetroglyphMegDataEntryPathNormalizer.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/PetroglyphMegDataEntryPathNormalizer.cs index 4f30afe0..83a11966 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/PetroglyphMegDataEntryPathNormalizer.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Normalization/PetroglyphMegDataEntryPathNormalizer.cs @@ -27,8 +27,8 @@ public class PetroglyphMegDataEntryPathNormalizer : MegDataEntryPathNormalizerBa /// /// Normalizes the specified MEG data entry path according to Petroglyph game requirements. /// - /// The normalized entry path. /// The read-only span containing the entry's file path to normalize. + /// The normalized entry path. /// /// This method ensures that the path is converted to uppercase and uses Windows-style directory separators (backslash). /// @@ -48,7 +48,7 @@ public override string Normalize(ReadOnlySpan entryPath) /// The number of characters written to the destination buffer after normalization. /// /// - /// Thrown if the destination buffer is not large enough to store the normalized path. + /// is not large enough to store the normalized path. /// /// /// This method ensures that the path is converted to uppercase and uses Windows-style directory separators (backslash). diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/Entry/MegDataEntryValidationResult.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/Entry/MegDataEntryValidationResult.cs index 60f8d45b..389e31b6 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/Entry/MegDataEntryValidationResult.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/Entry/MegDataEntryValidationResult.cs @@ -32,7 +32,7 @@ public readonly struct MegDataEntryValidationResult public MegDataEntryValidationStatus Status { get; } /// - /// Gets a value whether the validation was successful. + /// Gets a value that indicates whether the validation was successful. /// public bool IsValid => Status == MegDataEntryValidationStatus.Valid; diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidator.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidator.cs index 585a8e6f..6423c952 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidator.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/BinaryMegFileInformationValidator.cs @@ -31,8 +31,8 @@ public class BinaryMegFileInformationValidator : IMegFileInformationValidator /// that are validated by this implementation. /// For , this includes all versions /// - protected virtual IReadOnlyCollection SupportedVersions { get; } - = [MegFileVersion.V1, MegFileVersion.V2, MegFileVersion.V3]; + protected virtual IReadOnlyCollection SupportedVersions { get; } + = [MegVersion.V1, MegVersion.V2, MegVersion.V3]; /// /// Gets the maximum allowed size for a MEG file in bytes, as defined by the MEG specification. @@ -74,8 +74,8 @@ public MegFileInfoValidationResult Validate(MegFileInformation fileInformation, if (dataEntries == null) throw new ArgumentNullException(nameof(dataEntries)); - if (!SupportedVersions.Contains(fileInformation.FileVersion)) - return new MegFileInfoValidationResult(false, $"MEG version {fileInformation.FileVersion} is currently not supported."); + if (!SupportedVersions.Contains(fileInformation.Version)) + return new MegFileInfoValidationResult(false, $"MEG version {fileInformation.Version} is currently not supported."); var isEncrypted = dataEntries.Any(e => e.Encrypted); var hasEncryptionData = fileInformation.HasEncryption; @@ -90,11 +90,11 @@ public MegFileInfoValidationResult Validate(MegFileInformation fileInformation, try { sizeCalculator = ServiceProvider.GetRequiredService() - .GetMegSizeCalculator(fileInformation.FileVersion); + .GetMegSizeCalculator(fileInformation.Version); } catch (NotImplementedException) { - return new MegFileInfoValidationResult(false, $"MEG version {fileInformation.FileVersion} is currently not supported."); + return new MegFileInfoValidationResult(false, $"MEG version {fileInformation.Version} is currently not supported."); } try diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/EmpireAtWarMegFileInformationValidator.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/EmpireAtWarMegFileInformationValidator.cs index fb9f7584..ff33074b 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/EmpireAtWarMegFileInformationValidator.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/EmpireAtWarMegFileInformationValidator.cs @@ -16,26 +16,26 @@ namespace PG.StarWarsGame.Files.MEG.Services.Builder.Validation; /// -/// Validates a MEG file information whether it is compliant to a Petroglyph Star Wars game. +/// Validates whether a MEG file information is compliant to a Petroglyph Star Wars game. /// public sealed class EmpireAtWarMegFileInformationValidator : BinaryMegFileInformationValidator { // The game arbitrary varies between 260 and 256, so we chose the larger value here. Mind that the value is 260 - 1, // because we need to reserve one byte for the zero-terminator '\0'. /// - /// The max number of characters allowed in a PG game for file paths. + /// Represents the maximum number of characters allowed in a Petroglyph game for file paths. /// public const int PetroglyphMaxFilePathLength = 259; /// - /// Gets the collection of supported values for the validator. + /// Gets the collection of supported values for the validator. /// /// /// A read-only collection containing the supported versions of the .MEG file format /// that are validated by this implementation. - /// For , this includes only . + /// For , this includes only . /// - protected override IReadOnlyCollection SupportedVersions { get; } = [MegFileVersion.V1]; + protected override IReadOnlyCollection SupportedVersions { get; } = [MegVersion.V1]; private readonly IFileSystem _fileSystem; diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/MegFileInfoValidationResult.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/MegFileInfoValidationResult.cs index dd4b7cc0..b522adc6 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/MegFileInfoValidationResult.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Builder/Validation/FileInfo/MegFileInfoValidationResult.cs @@ -16,14 +16,14 @@ public readonly struct MegFileInfoValidationResult public string? FailReason { get; } /// - /// Gets a value whether the validation was successful. + /// Gets a value that indicates whether the validation was successful. /// public bool IsValid { get; } /// /// Initializes a new instance of the struct. /// - /// A value indicating whether the validation result is valid. + /// if the validation result is valid; otherwise, . /// The reason for validation failure, or if the validation is successful. public MegFileInfoValidationResult(bool valid, string? failReason) { diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegFileExtractor.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegFileExtractor.cs index a29cb725..bded7243 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegFileExtractor.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegFileExtractor.cs @@ -5,13 +5,15 @@ using System.IO; using PG.StarWarsGame.Files.MEG.Data.Entries; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; -using PG.StarWarsGame.Files.MEG.Utilities; namespace PG.StarWarsGame.Files.MEG.Services; /// -/// Service for extracting file from a .MEG archive. +/// Represents a service for extracting files from a .MEG archive to the file system. /// +/// +/// To read an entry's data into a stream (without writing it to disk), use . +/// public interface IMegFileExtractor { /// @@ -27,7 +29,7 @@ public interface IMegFileExtractor /// /// If is , /// - /// result := + filename(). + /// result := rootPath + filename(dataEntry). /// ///
///
@@ -37,13 +39,13 @@ public interface IMegFileExtractor /// a) if is not rooted (see here) ///
/// - /// result := + path(). + /// result := rootPath + path(dataEntry). /// /// ///
/// b) if is rooted. /// - /// result := path() + /// result := path(dataEntry) /// /// ///
@@ -58,8 +60,8 @@ public interface IMegFileExtractor /// It's the consumers responsibility to prevent path traversals. /// /// The file to get the path from. - /// Base directory of the built file path. - /// option to preserve the directory hierarchy of the file name. + /// The base directory of the built file path. + /// to preserve the directory hierarchy of the file name; otherwise, . /// The absolute file path. /// or is . /// is empty or contains only whitespace. @@ -67,18 +69,6 @@ public interface IMegFileExtractor string GetAbsolutePath(IMegDataEntry dataEntry, string rootPath, bool preserveDirectoryHierarchy); - /// - /// Gets the data stream of the given . - /// - /// The data entry information. - /// A stream containing the files contents. - /// is . - /// has properties. - /// The data entry does not exist in the .MEG file. - /// The operation is not permitted by the operating system due to missing permissions. - MegEntryStream GetData(MegDataEntryLocationReference dataEntryLocation); - - /// /// Extracts a single file from a .MEG archive to a given location. /// @@ -92,8 +82,8 @@ public interface IMegFileExtractor /// /// The data entry information. /// The destination file path. - /// When set to existing files will be overwritten; otherwise the extraction will be skipped. - /// if the file was extracted. if and only if the extraction was skipped. + /// to overwrite existing files; otherwise, to skip the extraction. + /// if the file was extracted; otherwise, if the extraction was skipped. /// or is . /// is empty, contains only whitespace or is not a legal file path in general. /// has properties. diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegFileService.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegFileService.cs deleted file mode 100644 index 0eaab17b..00000000 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegFileService.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Abstractions; -using PG.StarWarsGame.Files.Binary; -using PG.StarWarsGame.Files.MEG.Data; -using PG.StarWarsGame.Files.MEG.Files; -using PG.StarWarsGame.Files.MEG.Services.Builder; - -namespace PG.StarWarsGame.Files.MEG.Services; - -/// -/// A service to load and create Petroglyph .MEG files -/// -public interface IMegFileService -{ - /// - /// Creates a MEG file from a collection of data entries and writes it to a specified file stream. - /// - /// - /// This is a low-level operation. It's recommended to use instead, as this provides data validation and normalization. - /// - /// Notes: - ///
- /// - Any MEG entry file path will be re-encoded to ASCII automatically. - ///
- /// - In the case references an encrypted MEG data entry, the entry will be decrypted first. - ///
- /// - The items of will be correctly sorted by this operation. - ///
- ///
- /// The destination file stream to write the MEG archive to. - /// The MEG file version to use. - /// Optional encryption data. - /// A collection of file references to be packed into the MEG archive. - /// or is . - /// The MEG file could not be created. - /// A data entry file was not found. - /// This library does not support creating the MEG archive from the specified arguments. - /// The MEG archive or its entries are exceeding the supported file size. - /// Attempted to create MEG archive which does not match the expected binary result. - void CreateMegArchive(FileSystemStream fileStream, MegFileVersion fileVersion, MegEncryptionData? encryptionData, IEnumerable builderInformation); - - /// - /// Loads a *.MEG file's metadata into a . - /// - /// The MEG file path. - /// The MEG file's metadata. - /// This library does not support the specified MEG archive. - /// The MEG archive or its entries are exceeding the supported file size. - /// is not a MEG archive. - /// is not found. - /// is . - /// is empty. - /// Attempts to load an encrypted MEG archive. - IMegFile Load(string filePath); - - /// - /// Loads a *.MEG metadata stream into a . - /// - /// The MEG file path. - /// The MEG file's metadata. - /// - /// - /// This library does not support the specified MEG archive. - /// - /// - /// is not readable or seekable. - /// - /// - /// The MEG archive or its entries are exceeding the supported file size. - /// is not a MEG archive. - /// is . - /// Attempts to load an encrypted MEG archive. - IMegFile Load(FileSystemStream stream); - - /// - /// Retrieves the from a .MEG file. - /// - /// The .MEG file. - /// Indicates whether the .MEG archive is encrypted or not. - /// The version of the .MEG archive. - /// The input stream was not recognized as a valid MEG archive. - /// is not found. - MegFileVersion GetMegFileVersion(string file, out bool encrypted); -} diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegService.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegService.cs new file mode 100644 index 00000000..fe888a11 --- /dev/null +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IMegService.cs @@ -0,0 +1,194 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using PG.StarWarsGame.Files.Binary; +using PG.StarWarsGame.Files.MEG.Data; +using PG.StarWarsGame.Files.MEG.Files; +using PG.StarWarsGame.Files.MEG.Services.Builder; + +namespace PG.StarWarsGame.Files.MEG.Services; + +/// +/// Represents a service to load and create Petroglyph MEG archives. +/// +public interface IMegService +{ + /// + /// Creates a binary MEG archive from a collection of data entries and writes it to a specified stream. + /// + /// + /// This is a low-level operation. It's recommended to use instead, as this provides data validation and normalization. + /// + /// Notes: + ///
+ /// - Any MEG entry file path will be re-encoded to ASCII automatically. + ///
+ /// - In the case references an encrypted MEG data entry, the entry will be decrypted first. + ///
+ /// - The items of will be correctly sorted by this operation. + ///
+ ///
+ /// The destination stream to write the MEG archive to. + /// The MEG version to use. + /// Optional encryption data. + /// A collection of file references to be packed into the MEG archive. + /// or is . + /// The MEG could not be created. + /// A data entry file was not found. + /// This library does not support creating the MEG archive from the specified arguments. + /// The MEG archive or its entries are exceeding the supported size. + /// Attempted to create MEG archive which does not match the expected binary result. + void CreateMegArchive(Stream stream, MegVersion megVersion, MegEncryptionData? encryptionData, IEnumerable builderInformation); + + /// + /// Loads a MEG file into a . + /// + /// The MEG file path. + /// The loaded . + /// This library does not support the specified MEG archive. + /// The MEG archive or its entries are exceeding the supported file size. + /// is not a MEG archive. + /// is not found. + /// is . + /// is empty. + /// The archive is encrypted. + IMegFile LoadFile(string filePath); + + /// + /// Loads a MEG file from stream into a . + /// + /// The MEG file stream. + /// The loaded . + /// + /// + /// This library does not support the specified MEG archive. + /// + /// + /// OR + /// + /// + /// is not readable or seekable. + /// + /// + /// The MEG archive or its entries are exceeding the supported file size. + /// is not a MEG archive. + /// is . + /// The archive is encrypted. + IMegFile LoadFile(FileSystemStream stream); + + /// + /// Loads a MEG archive from a stream. + /// + /// + /// If is a file system stream the method returns an ; otherwise the + /// whole stream is copied into memory and the returned source is self-contained. The caller may dispose + /// immediately afterwards. + /// + /// The stream containing the MEG archive. + /// The loaded . + /// is . + /// + /// + /// This library does not support the specified MEG archive. + /// + /// + /// OR + /// + /// + /// The archive is encrypted. + /// + /// + /// The MEG archive or its entries are exceeding the supported file size. + /// is not a MEG archive. + IMegDataSource LoadArchive(Stream stream); + + /// + /// Loads a MEG archive from an in-memory byte buffer. + /// + /// + /// The buffer is copied, so that subsequent mutations of are not reflected. + /// + /// The bytes of the whole MEG archive. + /// The loaded . + /// is . + /// + /// + /// This library does not support the specified MEG archive. + /// + /// + /// OR + /// + /// + /// The archive is encrypted. + /// + /// + /// The MEG archive or its entries are exceeding the supported size. + /// is not a MEG archive. + IMegDataSource LoadArchive(byte[] data); + + /// + /// Loads a MEG archive from a read-only span of bytes. + /// + /// + /// The span is copied, so that subsequent mutations of the underlying memory are not reflected. + /// + /// The bytes of the whole MEG archive. + /// The loaded . + /// + /// + /// This library does not support the specified MEG archive. + /// + /// + /// OR + /// + /// + /// The archive is encrypted. + /// + /// + /// The MEG archive or its entries are exceeding the supported size. + /// is not a MEG archive. + IMegDataSource LoadArchive(ReadOnlySpan data); + + /// + /// Retrieves the from a MEG file. + /// + /// The MEG file path. + /// When this method returns, contains a value that indicates whether the MEG archive is encrypted. This parameter is treated as uninitialized. + /// The version of the MEG archive. + /// The input stream was not recognized as a valid MEG archive. + /// is not found. + MegVersion GetMegVersion(string file, out bool encrypted); + + /// + /// Retrieves the from a MEG archive stream. + /// + /// The stream containing the MEG archive. Read from its current position. + /// When this method returns, contains a value that indicates whether the MEG archive is encrypted. This parameter is treated as uninitialized. + /// The version of the MEG archive. + /// is . + /// The input stream was not recognized as a valid MEG archive. + MegVersion GetMegVersion(Stream stream, out bool encrypted); + + /// + /// Retrieves the from an in-memory MEG archive buffer. + /// + /// The bytes of the MEG archive. + /// When this method returns, contains a value that indicates whether the MEG archive is encrypted. This parameter is treated as uninitialized. + /// The version of the MEG archive. + /// is . + /// The input was not recognized as a valid MEG archive. + MegVersion GetMegVersion(byte[] data, out bool encrypted); + + /// + /// Retrieves the from a read-only span of MEG archive bytes. + /// + /// The bytes of the MEG archive. + /// When this method returns, contains a value that indicates whether the MEG archive is encrypted. This parameter is treated as uninitialized. + /// The version of the MEG archive. + /// The input was not recognized as a valid MEG archive. + MegVersion GetMegVersion(ReadOnlySpan data, out bool encrypted); +} diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IVirtualMegArchiveBuilder.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IVirtualMegArchiveBuilder.cs index 977fcb66..8bbe465a 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IVirtualMegArchiveBuilder.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/IVirtualMegArchiveBuilder.cs @@ -1,10 +1,10 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System.Collections.Generic; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Data.Entries; -using PG.StarWarsGame.Files.MEG.Files; namespace PG.StarWarsGame.Files.MEG.Services; @@ -20,24 +20,26 @@ public interface IVirtualMegArchiveBuilder /// The resulting archive is correctly sorted as specified. /// /// The collection of data references. - /// When , entries with the same CRC32 checksum get replaced. + /// to replace entries with the same CRC32 checksum; otherwise, . /// The virtual MEG archive. - /// When a does not point to a real location. + /// A does not point to a real location. IVirtualMegArchive BuildFrom(IEnumerable fileEntries, bool replaceExisting); /// - /// Converts an into a virtual, in-memory archive. The archive does not contain duplicates. + /// Converts an into a virtual, MEG archive. + /// The archive does not contain duplicates. /// - /// The MEG file to convert. + /// The MEG to convert. /// The virtual MEG archive. - IVirtualMegArchive BuildFrom(IMegFile megFile); + IVirtualMegArchive BuildFrom(IMegDataSource meg); /// - /// Merges a list of physical MEG files into a virtual, in-memory archive. The archive does not contain duplicates. + /// Merges a collection of into a virtual MEG archive. + /// The archive does not contain duplicates. /// - /// The physical MEG files to merge into a virtual MEG archive. - /// When , entries from different files with the same CRC32 checksum get replaced. - /// Duplicate entries within the same MEG file are ignored, so that only the first entry is recognized. + /// The MEGs to merge into a virtual MEG archive. + /// to replace entries from different MEGs with the same CRC32 checksum; otherwise, . + /// Duplicate entries within the same MEG are ignored, so that only the first entry is recognized. /// The virtual MEG archive. - IVirtualMegArchive BuildFrom(IList megFiles, bool replaceExisting); -} \ No newline at end of file + IVirtualMegArchive BuildFrom(IEnumerable megs, bool replaceExisting); +} diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/IMegDataStreamFactory.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/IMegDataStreamFactory.cs deleted file mode 100644 index ad5f2b72..00000000 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/IMegDataStreamFactory.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using System.IO; -using PG.StarWarsGame.Files.MEG.Data.EntryLocations; -using PG.StarWarsGame.Files.MEG.Utilities; - -namespace PG.StarWarsGame.Files.MEG.Services; - -internal interface IMegDataStreamFactory -{ - Stream GetStream(MegDataEntryOriginInfo originInfo); - - MegEntryStream GetStream(MegDataEntryLocationReference locationReference); -} \ No newline at end of file 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 deleted file mode 100644 index c0f2decb..00000000 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegDataStreamFactory.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using System; -using System.IO; -using System.IO.Abstractions; -using Microsoft.Extensions.DependencyInjection; -using PG.Commons.Services; -using PG.StarWarsGame.Files.MEG.Data.Entries; -using PG.StarWarsGame.Files.MEG.Data.EntryLocations; -using PG.StarWarsGame.Files.MEG.Utilities; - -namespace PG.StarWarsGame.Files.MEG.Services; - -internal sealed class MegDataStreamFactory(IServiceProvider serviceProvider) - : ServiceBase(serviceProvider), IMegDataStreamFactory -{ - public Stream GetStream(MegDataEntryOriginInfo originInfo) - { - 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(); - - return GetStream(originInfo.MegFileLocation!); - } - - public MegEntryStream GetStream(MegDataEntryLocationReference locationReference) - { - if (locationReference == null) - throw new ArgumentNullException(nameof(locationReference)); - - if (!locationReference.Exists) - throw new EntryNotInMegException(locationReference); - - if (locationReference.DataEntry.Encrypted) - { - throw new NotImplementedException("Encrypted archives are currently not supported"); - } - - return CreateStream(locationReference.MegFile.FilePath, locationReference.DataEntry); - } - - private MegEntryStream CreateStream(string megFilePath, MegDataEntry entry) - { - if (!FileSystem.File.Exists(megFilePath)) - throw new FileNotFoundException($"MEG file '{megFilePath}' does not exist", megFilePath); - - // Cause MIKE.NL's tool uses the offset megFile[megSize + 1] for empty Entries we would cause an ArgumentOutOfRangeException - // when trying to access this index on a real file. Therefore, we return the Null stream. - if (entry.Location.Size == 0) - return MegEntryStream.CreateEmptyStream(entry.Path); - - var fs = Services.GetRequiredService(); - - var megFileStream = fs.FileStream.New(megFilePath, FileMode.Open, FileAccess.Read, FileShare.Read); - return new MegEntryStream(entry.Path, megFileStream, entry.Location.Offset, entry.Location.Size); - } -} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegFileExtractor.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegFileExtractor.cs index 4a63aa86..6718c8dc 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegFileExtractor.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegFileExtractor.cs @@ -3,12 +3,10 @@ using System; using System.IO; -using Microsoft.Extensions.DependencyInjection; using PG.Commons.Services; using PG.StarWarsGame.Files.MEG.Data.Entries; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; using AnakinRaW.CommonUtilities; -using PG.StarWarsGame.Files.MEG.Utilities; namespace PG.StarWarsGame.Files.MEG.Services; @@ -47,15 +45,6 @@ public string GetAbsolutePath(IMegDataEntry dataEntry, string rootPath, bool pre return FileSystem.Path.GetFullPath(FileSystem.Path.Combine(absoluteRootPath, dataEntry.Path)); } - /// - public MegEntryStream GetData(MegDataEntryLocationReference dataEntryLocation) - { - if (dataEntryLocation is null) - throw new ArgumentNullException(nameof(dataEntryLocation)); - - return Services.GetRequiredService().GetStream(dataEntryLocation); - } - /// public bool ExtractEntry(MegDataEntryLocationReference dataEntryLocation, string filePath, bool overwrite) { @@ -77,7 +66,7 @@ public bool ExtractEntry(MegDataEntryLocationReference dataEntryLocation, string using var destinationStream = FileSystem.FileStream.New(fullFilePath, fileMode, FileAccess.Write, FileShare.None); - using var dataStream = Services.GetRequiredService().GetStream(dataEntryLocation); + using var dataStream = dataEntryLocation.Source.GetData(dataEntryLocation.DataEntry); dataStream.CopyTo(destinationStream); return true; diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegFileService.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegFileService.cs deleted file mode 100644 index d905ef8e..00000000 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegFileService.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Abstractions; -using Microsoft.Extensions.DependencyInjection; -using PG.Commons.Services; -using PG.StarWarsGame.Files.MEG.Binary; -using PG.StarWarsGame.Files.MEG.Binary.Metadata; -using PG.StarWarsGame.Files.MEG.Data; -using PG.StarWarsGame.Files.MEG.Files; -using AnakinRaW.CommonUtilities; - -namespace PG.StarWarsGame.Files.MEG.Services; - -/// -internal sealed class MegFileService(IServiceProvider services) : ServiceBase(services), IMegFileService -{ - private IMegBinaryServiceFactory BinaryServiceFactory { get; } = services.GetRequiredService(); - - public void CreateMegArchive( - FileSystemStream fileStream, - MegFileVersion fileVersion, - MegEncryptionData? encryptionData, - IEnumerable builderInformation) - { - if (fileStream == null) - throw new ArgumentNullException(nameof(fileStream)); - - if (builderInformation == null) - throw new ArgumentNullException(nameof(builderInformation)); - - var constructionArchive = BinaryServiceFactory.GetConstructionBuilder(fileVersion) - .BuildConstructingMegArchive(builderInformation); - - if (constructionArchive.Encrypted) - throw new NotImplementedException("Encrypted archives are currently not supported"); - - if (constructionArchive.Encrypted) - { - if (encryptionData is null) - throw new NotSupportedException("Creating an encrypted MEG archive requires encryption key."); - if (fileVersion == MegFileVersion.V3) - throw new NotSupportedException("Creating an encrypted MEG archive requires the MEG version to be V3."); - } - - - var metadata = BinaryServiceFactory.GetConverter(constructionArchive.MegVersion) - .ModelToBinary(constructionArchive.Archive); - - metadata.WriteTo(fileStream); - - long dataBytesWritten = metadata.Size; - - var streamFactory = Services.GetRequiredService(); - - foreach (var file in constructionArchive) - { - using var dataStream = streamFactory.GetStream(file.Location); - - // TODO: Test in encryption case - if (dataStream.Length != file.DataEntry.Location.Size) - throw new InvalidOperationException( - $"Actual data entry size '{dataStream.Length}' does not match expected value: {file.DataEntry.Location.Size}"); - - if (fileStream.Position != file.DataEntry.Location.Offset) - throw new InvalidOperationException( - $"Actual file position '{fileStream.Position}' does not match expected entry offset: {file.DataEntry.Location.Offset}"); - - dataStream.CopyTo(fileStream); - - dataBytesWritten += dataStream.Length; - } - - Debug.Assert(dataBytesWritten == constructionArchive.ExpectedFileSize); - } - - public IMegFile Load(string filePath) - { - ThrowHelper.ThrowIfNullOrEmpty(filePath); - var fullPath = FileSystem.Path.GetFullPath(filePath); - using var fs = FileSystem.FileStream.New(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read); - return Load(fs); - } - - public IMegFile Load(FileSystemStream stream) - { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - - var startPosition = stream.Position; - var megVersion = GetMegFileVersion(stream, out var encrypted); - - if (encrypted) - throw new NotImplementedException("Encrypted archives are currently not supported"); - - using var megFileInfo = new MegFileInformation(FileSystem.Path.GetFullPath(stream.Name), megVersion); - - stream.Seek(startPosition, SeekOrigin.Begin); - - var megMetadata = Load(stream, megFileInfo); - - var converter = BinaryServiceFactory.GetConverter(megVersion); - var megArchive = converter.BinaryToModel(megMetadata); - return new MegFile(megArchive, megFileInfo, Services); - } - - private IMegFileMetadata Load(Stream megStream, MegFileInformation megFileInfo) - { - using var binaryReader = BinaryServiceFactory.GetReader(megFileInfo.FileVersion); - return binaryReader.ReadBinary(megStream); - } - - public MegFileVersion GetMegFileVersion(string file, out bool encrypted) - { - ThrowHelper.ThrowIfNullOrWhiteSpace(file); - - using var fs = FileSystem.FileStream.New(file, FileMode.Open, FileAccess.Read, FileShare.Read); - return GetMegFileVersion(fs, out encrypted); - } - - private MegFileVersion GetMegFileVersion(Stream stream, out bool encrypted) - { - return Services.GetRequiredService().GetMegFileVersion(stream, out encrypted); - } -} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegService.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegService.cs new file mode 100644 index 00000000..28fa4e90 --- /dev/null +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/MegService.cs @@ -0,0 +1,216 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.IO.Abstractions; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Services; +using PG.StarWarsGame.Files.MEG.Binary; +using PG.StarWarsGame.Files.MEG.Data; +using PG.StarWarsGame.Files.MEG.Data.Archives; +using PG.StarWarsGame.Files.MEG.Files; +using AnakinRaW.CommonUtilities; + +namespace PG.StarWarsGame.Files.MEG.Services; + +/// +internal sealed class MegService(IServiceProvider services) : ServiceBase(services), IMegService +{ + private const string LoadArchiveEncryptedMessage = "Loading an encrypted MEG archive via LoadArchive is not supported."; + + private IMegBinaryServiceFactory BinaryServiceFactory { get; } = services.GetRequiredService(); + + public void CreateMegArchive( + Stream stream, + MegVersion megVersion, + MegEncryptionData? encryptionData, + IEnumerable builderInformation) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + if (builderInformation == null) + throw new ArgumentNullException(nameof(builderInformation)); + + var constructionArchive = BinaryServiceFactory.GetConstructionBuilder(megVersion) + .BuildConstructingMegArchive(builderInformation); + + if (constructionArchive.Encrypted) + throw new NotImplementedException("Encrypted archives are currently not supported"); + + if (constructionArchive.Encrypted) + { + if (encryptionData is null) + throw new NotSupportedException("Creating an encrypted MEG archive requires encryption key."); + if (megVersion == MegVersion.V3) + throw new NotSupportedException("Creating an encrypted MEG archive requires the MEG version to be V3."); + } + + + var metadata = BinaryServiceFactory.GetConverter(constructionArchive.MegVersion) + .ModelToBinary(constructionArchive.Archive); + + metadata.WriteTo(stream); + + long dataBytesWritten = metadata.Size; + + foreach (var file in constructionArchive) + { + using var dataStream = file.Location.GetDataStream(); + + // TODO: Test in encryption case + if (dataStream.Length != file.DataEntry.Location.Size) + throw new InvalidOperationException( + $"Actual data entry size '{dataStream.Length}' does not match expected value: {file.DataEntry.Location.Size}"); + + if (stream.Position != file.DataEntry.Location.Offset) + throw new InvalidOperationException( + $"Actual file position '{stream.Position}' does not match expected entry offset: {file.DataEntry.Location.Offset}"); + + dataStream.CopyTo(stream); + + dataBytesWritten += dataStream.Length; + } + + Debug.Assert(dataBytesWritten == constructionArchive.ExpectedFileSize); + } + + public IMegFile LoadFile(string filePath) + { + ThrowHelper.ThrowIfNullOrEmpty(filePath); + var fullPath = FileSystem.Path.GetFullPath(filePath); + using var fs = FileSystem.FileStream.New(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read); + return LoadFile(fs); + } + + public IMegFile LoadFile(FileSystemStream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + return LoadMegFromFile(stream, stream.Name); + } + + public IMegDataSource LoadArchive(Stream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + if (TryGetFileName(stream, out var fileName)) + { + var (version, archive, encrypted) = ReadArchive(stream); + if (encrypted) + throw new NotSupportedException(LoadArchiveEncryptedMessage); + using var megFileInfo = new MegFileInformation(FileSystem.Path.GetFullPath(fileName), version); + return new MegFile(archive!, megFileInfo, Services); + } + + using var buffer = new MemoryStream(stream.CanSeek ? (int)(stream.Length - stream.Position) : 0); + stream.CopyTo(buffer); + buffer.Position = 0; + + var (_, memArchive, memEncrypted) = ReadArchive(buffer); + if (memEncrypted) + throw new NotSupportedException(LoadArchiveEncryptedMessage); + return new InMemoryMeg(memArchive!, buffer.ToArray()); + } + + public IMegDataSource LoadArchive(byte[] data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + return LoadArchive(data.AsSpan()); + } + + public IMegDataSource LoadArchive(ReadOnlySpan data) + { + return LoadMegFromMemory(data); + } + + public MegVersion GetMegVersion(string file, out bool encrypted) + { + ThrowHelper.ThrowIfNullOrWhiteSpace(file); + + using var fs = FileSystem.FileStream.New(file, FileMode.Open, FileAccess.Read, FileShare.Read); + return GetMegVersion(fs, out encrypted); + } + + public MegVersion GetMegVersion(Stream stream, out bool encrypted) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + return Services.GetRequiredService().GetMegVersion(stream, out encrypted); + } + + public MegVersion GetMegVersion(byte[] data, out bool encrypted) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + + using var stream = new MemoryStream(data, writable: false); + return GetMegVersion(stream, out encrypted); + } + + public MegVersion GetMegVersion(ReadOnlySpan data, out bool encrypted) + { + return GetMegVersion(data.ToArray(), out encrypted); + } + + private MegFile LoadMegFromFile(Stream stream, string name) + { + var (version, archive, encrypted) = ReadArchive(stream); + if (encrypted) + throw new NotImplementedException("Loading an encrypted MEG archive is not yet implemented."); + + using var megFileInfo = new MegFileInformation(FileSystem.Path.GetFullPath(name), version); + return new MegFile(archive!, megFileInfo, Services); + } + + private InMemoryMeg LoadMegFromMemory(ReadOnlySpan megData) + { + var copiedMegData = megData.ToArray(); + using var dataStream = new MemoryStream(copiedMegData, writable: false); + var (_, archive, encrypted) = ReadArchive(dataStream); + if (encrypted) + throw new NotSupportedException(LoadArchiveEncryptedMessage); + + return new InMemoryMeg(archive!, copiedMegData); + } + + // Reads the version and the archive model from a seekable stream positioned at the start of the MEG. + // Does not throw on encrypted input; the caller decides which exception is appropriate. + private (MegVersion Version, IMegArchive? Archive, bool Encrypted) ReadArchive(Stream stream) + { + var startPosition = stream.Position; + var megVersion = GetMegVersion(stream, out var encrypted); + + if (encrypted) + return (megVersion, null, true); + + stream.Seek(startPosition, SeekOrigin.Begin); + + using var binaryReader = BinaryServiceFactory.GetReader(megVersion); + var metadata = binaryReader.ReadBinary(stream); + + var archive = BinaryServiceFactory.GetConverter(megVersion).BinaryToModel(metadata); + return (megVersion, archive, false); + } + + private static bool TryGetFileName(Stream stream, [NotNullWhen(true)] out string? fileName) + { + // NB: We cannot use stream.TryGetFileName extension cause that would + // include MegDataStream which we do not support here. + fileName = stream switch + { + FileSystemStream fileSystemStream => fileSystemStream.Name, + FileStream fileStream => fileStream.Name, + _ => null + }; + return fileName is not null; + } +} diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/VirtualMegArchiveBuilder.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/VirtualMegArchiveBuilder.cs index 99e987ae..6960104f 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/VirtualMegArchiveBuilder.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Services/Internal/VirtualMegArchiveBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. using System; @@ -7,10 +7,10 @@ using PG.Commons.Data; using PG.Commons.Hashing; using PG.Commons.Utilities; +using PG.StarWarsGame.Files.MEG.Data; using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Data.Entries; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; -using PG.StarWarsGame.Files.MEG.Files; namespace PG.StarWarsGame.Files.MEG.Services; @@ -18,14 +18,14 @@ namespace PG.StarWarsGame.Files.MEG.Services; internal sealed class VirtualMegArchiveBuilder : IVirtualMegArchiveBuilder { /// - public IVirtualMegArchive BuildFrom(IMegFile megFile) + public IVirtualMegArchive BuildFrom(IMegDataSource meg) { - if (megFile == null) - throw new ArgumentNullException(nameof(megFile)); + if (meg == null) + throw new ArgumentNullException(nameof(meg)); - var entryReferences = megFile.Archive + var entryReferences = meg.Archive .Distinct(CrcBasedEqualityComparer.Instance) - .Select(entry => new MegDataEntryReference(new MegDataEntryLocationReference(megFile, entry))); + .Select(entry => new MegDataEntryReference(new MegDataEntryLocationReference(meg, entry))); return new VirtualMegArchive(Crc32Utilities.SortByCrc32(entryReferences)); } @@ -33,20 +33,20 @@ public IVirtualMegArchive BuildFrom(IMegFile megFile) /// public IVirtualMegArchive BuildFrom(IEnumerable fileEntries, bool replaceExisting) { - if (fileEntries == null) + if (fileEntries == null) throw new ArgumentNullException(nameof(fileEntries)); return BuildFrom(fileEntries, replaceExisting, true); } /// - public IVirtualMegArchive BuildFrom(IList megFiles, bool replaceExisting) + public IVirtualMegArchive BuildFrom(IEnumerable megs, bool replaceExisting) { - if (megFiles == null) - throw new ArgumentNullException(nameof(megFiles)); + if (megs == null) + throw new ArgumentNullException(nameof(megs)); - var entries = megFiles.SelectMany(f => f.Archive.Distinct(CrcBasedEqualityComparer.Instance) - .Select(entry => new MegDataEntryReference(new MegDataEntryLocationReference(f, entry)))); + var entries = megs.SelectMany(m => m.Archive.Distinct(CrcBasedEqualityComparer.Instance) + .Select(entry => new MegDataEntryReference(new MegDataEntryLocationReference(m, entry)))); return BuildFrom(entries, replaceExisting, false); } @@ -72,4 +72,4 @@ private static IVirtualMegArchive BuildFrom(IEnumerable f return new VirtualMegArchive(sortedEntries.Values); } -} \ No newline at end of file +} diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Utilities/MegEntryStream.cs b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Utilities/MegEntryStream.cs index 746869ac..71b928ef 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Utilities/MegEntryStream.cs +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/Utilities/MegEntryStream.cs @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Files.MEG.Utilities; /// -/// Represent a read-only, non-seekable file stream that points to a single data entry inside a MEG file. +/// Represents a read-only stream that exposes the bytes of a single data entry inside a MEG archive. /// public sealed class MegEntryStream : Stream, IMegFileDataStream { 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 23975d0d..12a85ad0 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 @@ -17,7 +17,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/Services/MtdFileServiceTest.cs b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/Services/MtdFileServiceTest.cs deleted file mode 100644 index 47eb9b44..00000000 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/Services/MtdFileServiceTest.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using AnakinRaW.CommonUtilities.Testing; -using AnakinRaW.CommonUtilities.Testing.Extensions; -using Microsoft.Extensions.DependencyInjection; -using PG.Commons.Hashing; -using PG.StarWarsGame.Files.Binary; -using PG.StarWarsGame.Files.MTD.Files; -using PG.StarWarsGame.Files.MTD.Services; -using PG.Testing; -using Testably.Abstractions.Testing; -using Xunit; - -namespace PG.StarWarsGame.Files.MTD.Test.Services; - -public class MtdFileServiceTest : CommonMtdTestBase -{ - private readonly MtdFileService _mtdFileService; - - public MtdFileServiceTest() - { - _mtdFileService = new MtdFileService(ServiceProvider); - } - - [Fact] - public void Load_ArgumentException_Throws() - { - Assert.Throws(() => _mtdFileService.Load("")); - Assert.Throws(() => _mtdFileService.Load((string)null!)); - Assert.Throws(() => _mtdFileService.Load((Stream)null!)); - } - - - [Fact] - public void Load_FileNotFound_Throws() - { - Assert.Throws(() => _mtdFileService.Load("test.mtd")); - } - - [Theory] - [MemberData(nameof(MtdTestData.InvalidMtdData), MemberType = typeof(MtdTestData))] - public void Load_CorruptedFile_Throws(byte[] data) - { - FileSystem.Initialize().WithFile("test.mtd").Which(m => m.HasBytesContent(data)); - - Assert.Throws(() => _mtdFileService.Load("test.mtd")); - Assert.Throws(() => _mtdFileService.Load(new TestMegDataStream("test.mtd", data))); - } - - [Theory] - [MemberData(nameof(MtdTestData.ValidMtdData), MemberType = typeof(MtdTestData))] - public void Load_ValidBinary(byte[] data, IList files) - { - FileSystem.Initialize().WithFile("test.mtd").Which(m => m.HasBytesContent(data)); - - CompareFileWithExpected(files, _mtdFileService.Load("test.mtd")); - CompareFileWithExpected(files, _mtdFileService.Load(new TestMegDataStream("test.mtd", data))); - } - - [Theory] - [MemberData(nameof(MtdTestData.ValidMtdData), MemberType = typeof(MtdTestData))] - public void Load_StreamStaysOpen(byte[] data, IList files) - { - FileSystem.Initialize().WithFile("test.mtd").Which(m => m.HasBytesContent(data)); - - using var fs = FileSystem.File.OpenRead("test.mtd"); - - CompareFileWithExpected(files, _mtdFileService.Load(fs)); - - // Resetting the position should not throw - fs.Position = 0; - } - - [Fact] - public void Load_FocMtd() - { - var focFile = TestingHelpers.GetEmbeddedResource(GetType(), "Files.MT_COMMANDBAR.MTD"); - Assert.DoesNotThrow(() => _mtdFileService.Load(new TestMegDataStream("MT_COMMANDBAR.MTD", focFile))); - } - - private void CompareFileWithExpected(IList expectedFiles, IMtdFile mtdFile) - { - var hashingService = ServiceProvider.GetRequiredService(); - for (var i = 0; i < mtdFile.Content.Count; i++) - { - var expected = expectedFiles[i]; - var crc = hashingService.GetCrc32(expected.ExpectedName, Encoding.ASCII); - - Assert.True(mtdFile.Content.Contains(crc)); - Assert.True(mtdFile.Content.TryGetEntry(crc, out var actual)); - expected.AsserEquals(actual); - } - } - - [Fact] - public void MTD_FileWithCollision() - { - var testStream = new TestMegDataStream("MT_COMMANDBAR.MTD", MtdTestData.MtdWithKnownCollision()); - var fileWithCollision = _mtdFileService.Load(testStream); - - var expectedCrc = new Crc32(3596410486); - - Assert.Equal(2, fileWithCollision.Content.Count); - Assert.True(fileWithCollision.Content.Contains(expectedCrc)); - Assert.Equal(2, fileWithCollision.Content.EntriesWithCrc(expectedCrc).Count); - } -} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/Services/MtdServiceTest.cs b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/Services/MtdServiceTest.cs new file mode 100644 index 00000000..1a13500d --- /dev/null +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD.Test/Services/MtdServiceTest.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using System.Text; +using AnakinRaW.CommonUtilities.Testing; +using AnakinRaW.CommonUtilities.Testing.Extensions; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Hashing; +using PG.StarWarsGame.Files.Binary; +using PG.StarWarsGame.Files.MTD.Data; +using PG.StarWarsGame.Files.MTD.Files; +using PG.StarWarsGame.Files.MTD.Services; +using PG.Testing; +using Testably.Abstractions.Testing; +using Xunit; + +namespace PG.StarWarsGame.Files.MTD.Test.Services; + +public class MtdServiceTest : CommonMtdTestBase +{ + private readonly IMtdService _mtdService; + + public MtdServiceTest() + { + _mtdService = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void LoadFile_ArgumentException_Throws() + { + Assert.Throws(() => _mtdService.LoadFile("")); + Assert.Throws(() => _mtdService.LoadFile((string)null!)); + Assert.Throws(() => _mtdService.LoadFile((FileSystemStream)null!)); + Assert.Throws(() => _mtdService.LoadModel((Stream)null!)); + Assert.Throws(() => _mtdService.LoadModel((byte[])null!)); + } + + + [Fact] + public void LoadFile_FileNotFound_Throws() + { + Assert.Throws(() => _mtdService.LoadFile("test.mtd")); + } + + [Theory] + [MemberData(nameof(MtdTestData.InvalidMtdData), MemberType = typeof(MtdTestData))] + public void LoadFile_CorruptedFile_Throws(byte[] data) + { + FileSystem.Initialize().WithFile("test.mtd").Which(m => m.HasBytesContent(data)); + + Assert.Throws(() => _mtdService.LoadFile("test.mtd")); + Assert.Throws(() => _mtdService.LoadModel(new TestMegDataStream("test.mtd", data))); + Assert.Throws(() => _mtdService.LoadModel(new MemoryStream(data))); + Assert.Throws(() => _mtdService.LoadModel(data)); + Assert.Throws(() => _mtdService.LoadModel(data.AsSpan())); + } + + [Theory] + [MemberData(nameof(MtdTestData.ValidMtdData), MemberType = typeof(MtdTestData))] + public void LoadFile_ValidBinary(byte[] data, IList files) + { + FileSystem.Initialize().WithFile("test.mtd").Which(m => m.HasBytesContent(data)); + + CompareFileWithExpected(files, _mtdService.LoadFile("test.mtd")); + + // The same data parses identically through the in-memory model overloads. + CompareDirectoryWithExpected(files, _mtdService.LoadModel(new TestMegDataStream("test.mtd", data))); + CompareDirectoryWithExpected(files, _mtdService.LoadModel(new MemoryStream(data))); + CompareDirectoryWithExpected(files, _mtdService.LoadModel(data)); + CompareDirectoryWithExpected(files, _mtdService.LoadModel(data.AsSpan())); + } + + [Theory] + [MemberData(nameof(MtdTestData.ValidMtdData), MemberType = typeof(MtdTestData))] + public void LoadFile_StreamStaysOpen(byte[] data, IList files) + { + FileSystem.Initialize().WithFile("test.mtd").Which(m => m.HasBytesContent(data)); + + using var fs = FileSystem.File.OpenRead("test.mtd"); + + CompareFileWithExpected(files, _mtdService.LoadFile(fs)); + + // Resetting the position should not throw + fs.Position = 0; + } + + [Fact] + public void LoadModel_FocMtd() + { + var focFile = TestingHelpers.GetEmbeddedResource(GetType(), "Files.MT_COMMANDBAR.MTD"); + Assert.DoesNotThrow(() => _mtdService.LoadModel(new TestMegDataStream("MT_COMMANDBAR.MTD", focFile))); + } + + [Theory] + [MemberData(nameof(MtdTestData.ValidMtdData), MemberType = typeof(MtdTestData))] + public void LoadModel_NonSeekableStream(byte[] data, IList files) + { + using var nonSeekable = new NonSeekableReadStream(data); + + CompareDirectoryWithExpected(files, _mtdService.LoadModel(nonSeekable)); + } + + private void CompareFileWithExpected(IList expectedFiles, IMtdFile mtdFile) + { + CompareDirectoryWithExpected(expectedFiles, mtdFile.Content); + } + + private void CompareDirectoryWithExpected(IList expectedFiles, IMegaTextureDirectory directory) + { + var hashingService = ServiceProvider.GetRequiredService(); + for (var i = 0; i < directory.Count; i++) + { + var expected = expectedFiles[i]; + var crc = hashingService.GetCrc32(expected.ExpectedName, Encoding.ASCII); + + Assert.True(directory.Contains(crc)); + Assert.True(directory.TryGetEntry(crc, out var actual)); + expected.AsserEquals(actual); + } + } + + [Fact] + public void MTD_FileWithCollision() + { + var testStream = new TestMegDataStream("MT_COMMANDBAR.MTD", MtdTestData.MtdWithKnownCollision()); + var directory = _mtdService.LoadModel(testStream); + + var expectedCrc = new Crc32(3596410486); + + Assert.Equal(2, directory.Count); + Assert.True(directory.Contains(expectedCrc)); + Assert.Equal(2, directory.EntriesWithCrc(expectedCrc).Count); + } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Binary/MtdFileConstants.cs b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Binary/MtdFileConstants.cs index ce09ab42..710b0fa8 100644 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Binary/MtdFileConstants.cs +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Binary/MtdFileConstants.cs @@ -6,7 +6,7 @@ namespace PG.StarWarsGame.Files.MTD.Binary; /// -/// Simple class wrapper around global defaults used for all MTD file definitions. +/// Provides global default values used for all MTD file definitions. /// public static class MtdFileConstants { diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Data/IMegaTextureDirectory.cs b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Data/IMegaTextureDirectory.cs index d2a61068..89d107ca 100644 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Data/IMegaTextureDirectory.cs +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Data/IMegaTextureDirectory.cs @@ -21,7 +21,7 @@ public interface IMegaTextureDirectory : IReadOnlyCollection - /// Get the last data entry with the matching CRC32 checksum. + /// Gets the last data entry with the matching CRC32 checksum. ///
/// The CRC to match. /// The last entry in the with the specified checksum. @@ -33,9 +33,9 @@ public interface IMegaTextureDirectory : IReadOnlyCollection /// The checksum of the entry to get. /// - /// When this method returns, the entry associated with the specified key, if the key is found; + /// When this method returns, contains the entry associated with the specified checksum, if the checksum is found; /// otherwise, the default value for the type of the parameter. - /// This parameter is passed uninitialized. + /// This parameter is treated as uninitialized. /// if the contains an element with the specified checksum; otherwise, . bool TryGetEntry(Crc32 crc32, [NotNullWhen(true)] out MegaTextureFileIndex? entry); @@ -43,6 +43,6 @@ public interface IMegaTextureDirectory : IReadOnlyCollection /// The CRC to match. - /// List of matching data entries. + /// The matching data entries, or an empty list if the checksum is not found. ImmutableFrugalList EntriesWithCrc(Crc32 crc); } \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Data/MegaTextureFileIndex.cs b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Data/MegaTextureFileIndex.cs index ef6da9ea..4744fcc1 100644 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Data/MegaTextureFileIndex.cs +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Data/MegaTextureFileIndex.cs @@ -27,7 +27,7 @@ public sealed class MegaTextureFileIndex : IEquatable, IHa public Rectangle Area { get; } /// - /// Gets a value indicating whether the entry has an alpha channel. + /// Gets a value that indicates whether the entry has an alpha channel. /// public bool HasAlpha { get; } @@ -37,12 +37,12 @@ public sealed class MegaTextureFileIndex : IEquatable, IHa public Crc32 Crc32 { get; } /// - /// Initializes a new instance of the class with the specified information. + /// Initializes a new instance of the class. /// /// The file name of the entry. /// The checksum of the file name. /// The area of the entry within the Mega Texture. - /// Information whether this entry has an alpha channel. + /// if the entry has an alpha channel; otherwise, . /// is longer than 63 characters. /// is empty. /// does contain non-ASCII characters. diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/MtdServiceContribution.cs b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/MtdServiceContribution.cs index 9ffa7884..46bd40be 100644 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/MtdServiceContribution.cs +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/MtdServiceContribution.cs @@ -19,7 +19,7 @@ public static class MtdServiceContribution /// The to add services to. public static void SupportMTD(this IServiceCollection serviceCollection) { - serviceCollection.AddSingleton(sp => new MtdFileService(sp)); + serviceCollection.AddSingleton(sp => new MtdService(sp)); serviceCollection.AddSingleton(sp => new MtdBinaryConverter(sp)); serviceCollection.AddSingleton(sp => new MdtFileReader(sp)); } diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/IMtdFileService.cs b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/IMtdFileService.cs deleted file mode 100644 index 5e2ad651..00000000 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/IMtdFileService.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using System; -using System.IO; -using PG.StarWarsGame.Files.Binary; -using PG.StarWarsGame.Files.MTD.Files; - -namespace PG.StarWarsGame.Files.MTD.Services; - -/// -/// A service to load Petroglyph .MTD files -/// -public interface IMtdFileService -{ - /// - /// Loads a *.MTD file into a . - /// - /// The MTD file path. - /// A representation of the MTD file. - /// is not found. - /// is . - /// is empty. - /// is not a valid MTD file. - IMtdFile Load(string filePath); - - /// - /// Loads a *.MTD stream into a . - /// - /// The MTD file stream. - /// A representation of the MTD file. - /// is not readable or seekable. - /// is not a valid MTD file. - /// is . - IMtdFile Load(Stream stream); -} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/IMtdService.cs b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/IMtdService.cs new file mode 100644 index 00000000..a786ef98 --- /dev/null +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/IMtdService.cs @@ -0,0 +1,64 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.IO; +using System.IO.Abstractions; +using PG.StarWarsGame.Files.Binary; +using PG.StarWarsGame.Files.MTD.Data; +using PG.StarWarsGame.Files.MTD.Files; + +namespace PG.StarWarsGame.Files.MTD.Services; + +/// +/// A service to load Petroglyph Mega-Texture Directories. +/// +public interface IMtdService +{ + /// + /// Loads an MTD file into a . + /// + /// The MTD file path. + /// A representation of the MTD file. + /// is not found. + /// is . + /// is empty. + /// is not a valid MTD file. + IMtdFile LoadFile(string filePath); + + /// + /// Loads an MTD file stream into a . + /// + /// The MTD file stream. + /// A representation of the MTD file. + /// is not readable or seekable. + /// is not a valid MTD file. + /// is . + IMtdFile LoadFile(FileSystemStream fileStream); + + /// + /// Loads MTD data from a stream into a . + /// + /// The stream containing the MTD data. + /// A representation of the MTD data. + /// is not valid MTD data. + /// is . + IMegaTextureDirectory LoadModel(Stream stream); + + /// + /// Loads MTD data from an in-memory byte buffer into a . + /// + /// The MTD bytes. + /// A representation of the MTD data. + /// is not valid MTD data. + /// is . + IMegaTextureDirectory LoadModel(byte[] data); + + /// + /// Loads MTD data from a read-only span of bytes into a . + /// + /// The MTD bytes. + /// A representation of the MTD data. + /// is not valid MTD data. + IMegaTextureDirectory LoadModel(ReadOnlySpan data); +} diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/MtdFileService.cs b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/MtdFileService.cs deleted file mode 100644 index fcfcf663..00000000 --- a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/MtdFileService.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using System; -using System.IO; -using AnakinRaW.CommonUtilities; -using Microsoft.Extensions.DependencyInjection; -using PG.Commons.Services; -using PG.Commons.Utilities; -using PG.StarWarsGame.Files.MTD.Binary; -using PG.StarWarsGame.Files.MTD.Files; - -namespace PG.StarWarsGame.Files.MTD.Services; - -internal class MtdFileService(IServiceProvider serviceProvider) : ServiceBase(serviceProvider), IMtdFileService -{ - private readonly IMtdBinaryConverter _binaryConverter = serviceProvider.GetRequiredService(); - private readonly IMtdFileReader _fileReader = serviceProvider.GetRequiredService(); - - public IMtdFile Load(string filePath) - { - ThrowHelper.ThrowIfNullOrEmpty(filePath); - var fullPath = FileSystem.Path.GetFullPath(filePath); - using var fs = FileSystem.FileStream.New(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read); - return Load(fs); - } - - public IMtdFile Load(Stream stream) - { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - - var binaryModel = _fileReader.ReadBinary(stream); - var model = _binaryConverter.BinaryToModel(binaryModel); - - var filePath = stream.GetFilePath(out var isInMeg); - - // If the .MTD file is not embedded in a MEG, we want the absolute path. - if (!isInMeg) - filePath = FileSystem.Path.GetFullPath(filePath); - - var megFileInfo = new MtdFileInformation { FilePath = filePath, IsInsideMeg = isInMeg}; - - return new MtdFile(model, megFileInfo, Services); - } -} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/MtdService.cs b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/MtdService.cs new file mode 100644 index 00000000..e8529455 --- /dev/null +++ b/PG.StarWarsGame.Files.MTD/PG.StarWarsGame.Files.MTD/Services/MtdService.cs @@ -0,0 +1,74 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.IO; +using System.IO.Abstractions; +using AnakinRaW.CommonUtilities; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Services; +using PG.Commons.Utilities; +using PG.StarWarsGame.Files.MTD.Binary; +using PG.StarWarsGame.Files.MTD.Data; +using PG.StarWarsGame.Files.MTD.Files; + +namespace PG.StarWarsGame.Files.MTD.Services; + +internal class MtdService(IServiceProvider serviceProvider) : ServiceBase(serviceProvider), IMtdService +{ + private readonly IMtdBinaryConverter _binaryConverter = serviceProvider.GetRequiredService(); + private readonly IMtdFileReader _fileReader = serviceProvider.GetRequiredService(); + + public IMtdFile LoadFile(string filePath) + { + ThrowHelper.ThrowIfNullOrEmpty(filePath); + var fullPath = FileSystem.Path.GetFullPath(filePath); + using var fs = FileSystem.FileStream.New(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read); + return LoadFile(fs); + } + + public IMtdFile LoadFile(FileSystemStream fileStream) + { + if (fileStream == null) + throw new ArgumentNullException(nameof(fileStream)); + + var model = ReadModel(fileStream); + + var filePath = fileStream.GetFilePath(out var isInMeg); + + // If the .MTD file is not embedded in a MEG, we want the absolute path. + if (!isInMeg) + filePath = FileSystem.Path.GetFullPath(filePath); + + var megFileInfo = new MtdFileInformation { FilePath = filePath, IsInsideMeg = isInMeg}; + + return new MtdFile(model, megFileInfo, Services); + } + + public IMegaTextureDirectory LoadModel(Stream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + return ReadModel(stream); + } + + public IMegaTextureDirectory LoadModel(byte[] data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + using var stream = new MemoryStream(data, writable: false); + return LoadModel(stream); + } + + public IMegaTextureDirectory LoadModel(ReadOnlySpan data) + { + using var stream = new MemoryStream(data.ToArray(), writable: false); + return LoadModel(stream); + } + + private IMegaTextureDirectory ReadModel(Stream stream) + { + var binaryModel = _fileReader.ReadBinary(stream); + return _binaryConverter.BinaryToModel(binaryModel); + } +} diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/FileBuilderTestBase.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/FileBuilderTestBase.cs index 3954daab..fce21903 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/FileBuilderTestBase.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files.Testing/FileBuilderTestBase.cs @@ -8,17 +8,56 @@ namespace PG.StarWarsGame.Files.Testing; +/// +/// Provides a shared set of tests for verifying the behavior of an implementation. +/// +/// The type of the file builder under test. +/// The type of the model that the builder produces output from. +/// The type of the file information that describes the builder's output. public abstract class FileBuilderTestBase : PGTestBase where TBuilder : IFileBuilder - where TModel : notnull + where TModel : notnull where TFileInfo : PetroglyphFileInformation { + /// + /// Gets the default name of the file that the builder writes during the tests. + /// + /// The default file name. protected virtual string DefaultFileName => "file.txt"; + /// + /// Gets a value that indicates whether the file information produced by the builder is always considered valid. + /// + /// + /// if the file information is always valid; otherwise, . The default is . + /// protected virtual bool FileInfoIsAlwaysValid => false; + + /// + /// Creates a new instance of the file builder under test. + /// + /// The newly created file builder. protected abstract TBuilder CreateBuilder(); + + /// + /// Creates the file information for the specified path. + /// + /// to create valid file information; otherwise, . + /// The path of the file the information describes. + /// The created file information. protected abstract TFileInfo CreateFileInfo(bool valid, string path); + + /// + /// Adds the specified data to the builder. + /// + /// The data to add to the builder. + /// The builder the data is added to. protected abstract void AddDataToBuilder(TModel data, TBuilder builder); + + /// + /// Creates a model and its expected serialized byte representation that together form valid builder input. + /// + /// A tuple containing the valid model and the bytes the builder is expected to produce from it. protected abstract (TModel Data, byte[] Bytes) CreateValidData(); [Fact] diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryBase.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryBase.cs index dac0146d..66562972 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryBase.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryBase.cs @@ -42,8 +42,8 @@ public int Size public abstract void GetBytes(Span bytes); /// - /// Calculates the size in bytes of this instance + /// Calculates the size, in bytes, of this instance. /// - /// The size in bytes. + /// The size, in bytes. protected abstract int GetSizeCore(); } \ No newline at end of file diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryFile.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryFile.cs index d9484186..b0be1008 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryFile.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryFile.cs @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Files.Binary; /// -/// Base class for a Petroglyph binary file. +/// Provides the base class for a Petroglyph binary file. /// public abstract class BinaryFile : BinaryBase, IBinaryFile { diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryTable.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryTable.cs index 677df2bd..0d8abd0e 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryTable.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/BinaryTable.cs @@ -28,7 +28,7 @@ public class BinaryTable : BinaryBase, IBinaryTable where T : IBinary public int Count => Items.Count; /// - /// Initializes a new instance of the that contains elements copied from the specified collection. + /// Initializes a new instance of the class that contains elements copied from the specified collection. /// /// The collection whose elements are copied to the new table. /// is . diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/File/IBinaryFileReader.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/File/IBinaryFileReader.cs index 6e699d28..4a700797 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/File/IBinaryFileReader.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/File/IBinaryFileReader.cs @@ -7,7 +7,7 @@ namespace PG.StarWarsGame.Files.Binary.File; /// -/// A reader that is capable of converting a stream of bytes into a binary model. +/// Represents a reader that is capable of converting a stream of bytes into a binary model. /// /// The type of the binary model. public interface IBinaryFileReader where TBinaryModel : IBinaryFile @@ -15,7 +15,7 @@ public interface IBinaryFileReader where TBinaryModel : IBinar /// /// Builds a binary file from a given byte array. /// - /// The binary data + /// The stream that contains the binary data. /// The converted binary file. /// is . /// cannot be converted to . diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinary.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinary.cs index 436f7ca7..690cdc91 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinary.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinary.cs @@ -23,6 +23,6 @@ public interface IBinary /// /// Fills the specified span with the bytes of the binary. /// - /// The span of bites to fill. + /// The span to fill with the bytes of the binary. void GetBytes(Span bytes); } \ No newline at end of file diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinaryConverter.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinaryConverter.cs index c5975043..5cf63a3b 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinaryConverter.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinaryConverter.cs @@ -7,7 +7,7 @@ namespace PG.StarWarsGame.Files.Binary; /// -/// A builder that is capable of converting a generic file model to its binary +/// Represents a builder that is capable of converting a generic file model to its binary /// representation and vice versa. /// /// The type of the binary model. diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinaryTable.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinaryTable.cs index af7a09b9..02b2515d 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinaryTable.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/IBinaryTable.cs @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Files.Binary; // limited to *signed* int32 for indexing native list-like structures. /// -/// A read-only list-like table used by Petroglyph binary models to hold index-based metadata information. +/// Represents a read-only list-like table used by Petroglyph binary models to hold index-based metadata information. /// /// The type of the binary elements of the table. public interface IBinaryTable : IBinary, IReadOnlyList where T : IBinary; \ No newline at end of file diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/PetroglyphBinaryReader.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/PetroglyphBinaryReader.cs index b67d1a5d..adffc5ac 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/PetroglyphBinaryReader.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Binary/PetroglyphBinaryReader.cs @@ -17,7 +17,7 @@ namespace PG.StarWarsGame.Files.Binary; public sealed class PetroglyphBinaryReader : BinaryReader { /// - /// Initializes a new instance of the based on the specified stream and optionally leaves the stream open. + /// Initializes a new instance of the class based on the specified stream and optionally leaves the stream open. /// /// The input stream. /// to leave the stream open after the BinaryReader object is disposed; otherwise, . @@ -43,12 +43,12 @@ public PetroglyphBinaryReader(Stream input, bool leaveOpen) : base(input, Invali /// does not restore the file position after an unsuccessful read. /// /// The number of characters to read. - /// The encoding to produce the string. - /// When set to , the resulting string is truncated to the first found null-terminator ('\0'). Default is . - /// The string being read. - /// The number of bytes read, mismatches the expected number of bytes. + /// The encoding used to produce the string. + /// to truncate the resulting string at the first null-terminator ('\0'); otherwise, . The default is . + /// The string that was read. + /// The number of bytes read mismatches the expected number of bytes. /// An I/O error occurred. - /// is but the string read did not contain a null-terminator. + /// is but the string read did not contain a null-terminator. /// is not supported. public string ReadString(Encoding encoding, int numberOfChars, bool isZeroTerminated = false) { @@ -92,18 +92,18 @@ public string ReadString(Encoding encoding, int numberOfChars, bool isZeroTermin /// does not restore the file position after an unsuccessful read. /// /// The character span to write the string into. - /// The encoding to produce the string. + /// The encoding used to produce the string. /// The number of characters to read. - /// When set to , the resulting string is truncated to the first found null-terminator ('\0'). Default is . + /// to truncate the resulting string at the first null-terminator ('\0'); otherwise, . The default is . /// /// The total number of characters read into the buffer. /// This might be less than if is /// and the read string contained multiple zero-terminators. /// - /// The number of bytes read, mismatches the expected number of bytes. + /// The number of bytes read mismatches the expected number of bytes. /// does not have enough capacity to accommodate the resulting characters. /// An I/O error occurred. - /// is but the string read did not contain a null-terminator. + /// is but the string read did not contain a null-terminator. /// is not supported. public int ReadString(Span destination, Encoding encoding, int numberOfChars, bool isZeroTerminated = false) { diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/IPetroglyphFileHolder.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/IPetroglyphFileHolder.cs index 313b3b39..a5f2a282 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/IPetroglyphFileHolder.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/IPetroglyphFileHolder.cs @@ -6,7 +6,7 @@ namespace PG.StarWarsGame.Files; /// -/// A wrapper around Petroglyph game files that holds the file's content in an accessible data structure as well as other file information. +/// Represents a wrapper around Petroglyph game files that holds the file's content in an accessible data structure as well as other file information. /// public interface IPetroglyphFileHolder : IDisposable { @@ -73,7 +73,7 @@ public interface IPetroglyphFileHolder : IDisposable } /// -/// A generic wrapper around Petroglyph game files that holds the file's content in an accessible data structure as well as other file information. +/// Represents a generic wrapper around Petroglyph game files that holds the file's content in an accessible data structure as well as other file information. /// /// The type of the content this file holds. /// The type of the file information. diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphFileHolder.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphFileHolder.cs index f3f40b2e..f15e9354 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphFileHolder.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphFileHolder.cs @@ -51,22 +51,22 @@ public TFileInfo FileInformation public string FilePath { get; } /// - /// The logger of this service. + /// Gets the logger of this service. /// protected internal ILogger Logger { get; } /// - /// The file system implementation to be used. + /// Gets the file system implementation to be used. /// protected internal IFileSystem FileSystem { get; } /// - /// Returns the service provider. + /// Gets the service provider. /// protected internal IServiceProvider Services { get; } /// - /// Initializes a new instance of the class with the specified model and file information. + /// Initializes a new instance of the class with the specified model and file information. /// /// /// can be safely disposed after initialization without affecting . diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphFileInformation.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphFileInformation.cs index 0127db3a..2988f627 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphFileInformation.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphFileInformation.cs @@ -8,14 +8,14 @@ namespace PG.StarWarsGame.Files; /// -/// Contains file information about a . +/// Contains file information about a . /// public abstract record PetroglyphFileInformation : IDisposable { private readonly string _filePath; /// - /// Gets or sets the path of the file e.g, "c:/my/path/myfile.txt" + /// Gets or sets the path of the file, e.g, "c:/my/path/myfile.txt". /// /// /// The path is taken as-is and may be relative. @@ -71,7 +71,7 @@ public void Dispose() /// /// Disposes this instance and frees managed resources. /// - /// When set to managed resources get disposed. + /// to release managed resources; otherwise, . protected virtual void Dispose(bool disposing) { } diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphMegPackableFileInformation.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphMegPackableFileInformation.cs index 75bdd392..291ab7c9 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphMegPackableFileInformation.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/PetroglyphMegPackableFileInformation.cs @@ -7,20 +7,21 @@ namespace PG.StarWarsGame.Files; /// -/// Contains file information about a that may be packed inside a MEG archive. +/// Contains file information about a that may be packed inside a MEG archive. /// public abstract record PetroglyphMegPackableFileInformation : PetroglyphFileInformation { /// - /// Gets a value indicating whether the file is packed inside a MEG archive. + /// Gets a value that indicates whether the file is packed inside a MEG archive. /// + /// if the file is packed inside a MEG archive; otherwise, . public bool IsInsideMeg { get; init; } /// - /// Initializes a new instance of the class with a given path. + /// Initializes a new instance of the class with a given path. /// /// The fully qualified name of the new file, or the relative file name. - /// Information whether the file is inside a MEG archive. + /// if the file is packed inside a MEG archive; otherwise, . /// is . /// is empty. [SetsRequiredMembers] @@ -30,7 +31,7 @@ protected PetroglyphMegPackableFileInformation(string path, bool isInMeg) : base } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// protected PetroglyphMegPackableFileInformation() { diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Services/Builder/FileBuilderBase.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Services/Builder/FileBuilderBase.cs index b23ff186..dba0eec1 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Services/Builder/FileBuilderBase.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Services/Builder/FileBuilderBase.cs @@ -10,7 +10,7 @@ namespace PG.StarWarsGame.Files.Services.Builder; /// -/// Base class for a service providing the fundamental implementations. +/// Provides the base class for a service with the fundamental implementations. /// /// The type of the file information data. /// The type of the data to build files from. @@ -87,8 +87,8 @@ public bool ValidateFileInformation(TFileInformation fileInformation) ///
/// The file information to validate. /// The data of this builder for additional context. - /// Stores an optional message into the variable to reason the validation result. - /// if is valid; otherwise, . + /// When this method returns, contains an optional message that explains the validation result, or if no reason is available. This parameter is treated as uninitialized. + /// if is valid; otherwise, . protected abstract bool ValidateFileInformationCore(TFileInformation fileInformation, TData builderData, out string? failedReason); } \ No newline at end of file diff --git a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Services/Builder/IFileBuilder.cs b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Services/Builder/IFileBuilder.cs index 8fd1e255..0f631241 100644 --- a/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Services/Builder/IFileBuilder.cs +++ b/PG.StarWarsGame.Files/PG.StarWarsGame.Files/Services/Builder/IFileBuilder.cs @@ -7,7 +7,7 @@ namespace PG.StarWarsGame.Files.Services.Builder; /// -/// Service to write data into their file representation. +/// Defines a service that writes data into its file representation. /// /// The type of the file information data. /// The type of the data to build files from. @@ -24,7 +24,7 @@ public interface IFileBuilder : IDisposable /// Creates a file from the . /// /// The file parameters of the new file. - /// When set to an existing file of the same path will be overwritten; otherwise an is thrown if the file already exists. + /// to overwrite an existing file of the same path; otherwise, to throw an if the file already exists. /// is . /// is not valid. /// The file could not be created due to an IO error. @@ -33,8 +33,8 @@ public interface IFileBuilder : IDisposable /// /// Checks whether the specified file information is valid for the . /// - /// The file information to validate - /// if the passed file information are valid; otherwise, . + /// The file information to validate. + /// if the passed file information is valid; otherwise, . /// is . public bool ValidateFileInformation(TFileInformation fileInformation); } \ No newline at end of file diff --git a/PG.Testing/Hashing/ParseIntCrc32HashingService.cs b/PG.Testing/Hashing/ParseIntCrc32HashingService.cs index 2f406bc1..eb6c417f 100644 --- a/PG.Testing/Hashing/ParseIntCrc32HashingService.cs +++ b/PG.Testing/Hashing/ParseIntCrc32HashingService.cs @@ -6,31 +6,39 @@ namespace PG.Testing.Hashing; +/// +/// Represents a test that derives the CRC32 value by parsing the input as an integer. +/// public class ParseIntCrc32HashingService : ICrc32HashingService { + /// public Crc32 GetCrc32(string value, Encoding encoding) { var intValue = int.Parse(value); return new Crc32(intValue); } + /// public Crc32 GetCrc32(ReadOnlySpan data) { var sum = data.ToArray().Sum(x => x); return new Crc32(sum); } + /// public Crc32 GetCrc32(Stream data) { var bytes = new byte[data.Length]; return GetCrc32(bytes.AsSpan()); } + /// public Crc32 GetCrc32(ReadOnlySpan value, Encoding encoding) { throw new NotImplementedException(); } + /// public Crc32 GetCrc32Upper(ReadOnlySpan value, Encoding encoding) { throw new NotImplementedException(); diff --git a/PG.Testing/NonSeekableReadStream.cs b/PG.Testing/NonSeekableReadStream.cs new file mode 100644 index 00000000..27777efb --- /dev/null +++ b/PG.Testing/NonSeekableReadStream.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; + +namespace PG.Testing; + +/// +/// Represents a read-only, forward-only wrapper that reports as . +/// +/// +/// Useful for exercising code paths that must handle non-seekable streams. +/// +/// The data exposed by the stream. +public sealed class NonSeekableReadStream(byte[] data) : Stream +{ + private readonly MemoryStream _inner = new(data, writable: false); + + /// + public override bool CanRead => true; + /// + public override bool CanSeek => false; + /// + public override bool CanWrite => false; + /// + public override long Length => throw new NotSupportedException(); + /// + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) => _inner.Read(buffer, offset, count); + /// + public override void Flush() { } + /// + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + /// + public override void SetLength(long value) => throw new NotSupportedException(); + /// + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + _inner.Dispose(); + base.Dispose(disposing); + } +} diff --git a/PG.Testing/PG.Testing.csproj b/PG.Testing/PG.Testing.csproj index 093b568b..02117377 100644 --- a/PG.Testing/PG.Testing.csproj +++ b/PG.Testing/PG.Testing.csproj @@ -7,7 +7,7 @@ false - + diff --git a/PG.Testing/PGTestBase.cs b/PG.Testing/PGTestBase.cs index e14a4d85..5241f977 100644 --- a/PG.Testing/PGTestBase.cs +++ b/PG.Testing/PGTestBase.cs @@ -5,8 +5,12 @@ namespace PG.Testing; +/// +/// Provides a base class for tests that require the Petroglyph commons services and an in-memory file system. +/// public abstract class PGTestBase : TestBaseWithFileSystem { + /// protected override void SetupServices(IServiceCollection serviceCollection) { base.SetupServices(serviceCollection); diff --git a/PG.Testing/TestMegDataStream.cs b/PG.Testing/TestMegDataStream.cs index 3d5e1619..486d3ac3 100644 --- a/PG.Testing/TestMegDataStream.cs +++ b/PG.Testing/TestMegDataStream.cs @@ -3,55 +3,79 @@ namespace PG.Testing; +/// +/// Represents a test that wraps an inner stream and associates it with a MEG entry path. +/// public class TestMegDataStream : Stream, IMegFileDataStream { private readonly Stream _innerStream; + /// public override bool CanRead => _innerStream.CanRead; + /// public override bool CanSeek => _innerStream.CanSeek; + /// public override bool CanWrite => _innerStream.CanWrite; + /// public override long Length => _innerStream.Length; + /// public override long Position { get => _innerStream.Position; set => _innerStream.Position = value; } + /// + /// Initializes a new instance of the class. + /// + /// The path of the entry used in the MEG archive. + /// The data exposed by the stream. public TestMegDataStream(string entryPath, byte[] data) { EntryPath = entryPath; _innerStream = new MemoryStream(data); } + /// + /// Initializes a new instance of the class. + /// + /// The path of the entry used in the MEG archive. + /// The inner stream that supplies the data. public TestMegDataStream(string entryPath, Stream dataStream) { EntryPath = entryPath; _innerStream = dataStream; } + /// public string EntryPath { get; } + /// public override void Flush() { _innerStream.Flush(); } + /// public override int Read(byte[] buffer, int offset, int count) { return _innerStream.Read(buffer, offset, count); } + /// public override long Seek(long offset, SeekOrigin origin) { return _innerStream.Seek(offset, origin); } + /// public override void SetLength(long value) { _innerStream.SetLength(value); } + /// public override void Write(byte[] buffer, int offset, int count) { _innerStream.Write(buffer, offset, count); diff --git a/version.json b/version.json index a55aab6a..fec756f5 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.1", + "version": "4.0", "assemblyVersion": { "precision": "major" }, From 7469ea27072c2c9e32f00e43be7c8456ba8ee102 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Jun 2026 11:15:03 +0000 Subject: [PATCH 4/4] Bump actions/checkout from 6 to 7 in the actions-deps group (#605) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the actions-deps group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 6 to 7
Release notes

Sourced from actions/checkout's releases.

v7.0.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v6.0.3...v7.0.0

v6.0.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v6...v6.0.3

v6.0.2

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v6.0.1...v6.0.2

v6.0.1

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v6...v6.0.1

Changelog

Sourced from actions/checkout's changelog.

Changelog

v7.0.0

v6.0.3

v6.0.2

v6.0.1

v6.0.0

v5.0.1

v5.0.0

v4.3.1

v4.3.0

v4.2.2

v4.2.1

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AnakinRaW --- .github/workflows/codeql.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/test.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c2aa8df5..8b942127 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,7 +23,7 @@ jobs: language: [ 'csharp' ] steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Setup .NET Core SDK diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f0e609a..8430c50e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Setup .NET @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Setup .NET diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 49e29520..0b9ea23e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - uses: actions/setup-dotnet@v5