From f9b27ead006e9b7408abec42455ed4556b068b4e Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Thu, 26 Mar 2026 18:16:15 +0100 Subject: [PATCH 1/3] ted impl skeleton --- ModVerify.slnx | 1 + .../Binary/Reader/ITedFileReader.cs | 6 +++ .../Binary/Reader/ITedFileReaderFactory.cs | 8 ++++ .../Binary/Reader/TedFileReader.cs | 13 ++++++ .../Binary/Reader/TedFileReaderFactory.cs | 12 ++++++ .../Data/IMapData.cs | 8 ++++ .../Files/ITedFile.cs | 6 +++ .../Files/TedFile.cs | 7 +++ .../Files/TedFileInformation.cs | 19 ++++++++ .../PG.StarWarsGame.Files.TED.csproj | 27 ++++++++++++ .../Services/ITedFileService.cs | 14 ++++++ .../Services/TedFileService.cs | 43 +++++++++++++++++++ .../TedServiceContribution.cs | 14 ++++++ 13 files changed, 178 insertions(+) create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/ITedFileReader.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/ITedFileReaderFactory.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReader.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReaderFactory.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Data/IMapData.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/ITedFile.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/TedFile.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/TedFileInformation.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/PG.StarWarsGame.Files.TED.csproj create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Services/ITedFileService.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Services/TedFileService.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/TedServiceContribution.cs diff --git a/ModVerify.slnx b/ModVerify.slnx index 3527ff4..35980ab 100644 --- a/ModVerify.slnx +++ b/ModVerify.slnx @@ -20,6 +20,7 @@ + diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/ITedFileReader.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/ITedFileReader.cs new file mode 100644 index 0000000..619e29c --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/ITedFileReader.cs @@ -0,0 +1,6 @@ +using PG.StarWarsGame.Files.ChunkFiles.Binary.Reader; +using PG.StarWarsGame.Files.TED.Data; + +namespace PG.StarWarsGame.Files.TED.Binary.Reader; + +internal interface ITedFileReader : IChunkFileReader; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/ITedFileReaderFactory.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/ITedFileReaderFactory.cs new file mode 100644 index 0000000..a0cc082 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/ITedFileReaderFactory.cs @@ -0,0 +1,8 @@ +using System.IO; + +namespace PG.StarWarsGame.Files.TED.Binary.Reader; + +internal interface ITedFileReaderFactory +{ + ITedFileReader GetReader(Stream dataStream); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReader.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReader.cs new file mode 100644 index 0000000..92e0c5f --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReader.cs @@ -0,0 +1,13 @@ +using System.IO; +using PG.StarWarsGame.Files.ChunkFiles.Binary.Reader; +using PG.StarWarsGame.Files.TED.Data; + +namespace PG.StarWarsGame.Files.TED.Binary.Reader; + +internal sealed class TedFileReader(Stream stream) : ChunkFileReaderBase(stream), ITedFileReader +{ + public override IMapData Read() + { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReaderFactory.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReaderFactory.cs new file mode 100644 index 0000000..d9be9eb --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReaderFactory.cs @@ -0,0 +1,12 @@ +using System; +using System.IO; + +namespace PG.StarWarsGame.Files.TED.Binary.Reader; + +internal class TedFileReaderFactory(IServiceProvider serviceProvider) : ITedFileReaderFactory +{ + public ITedFileReader GetReader(Stream dataStream) + { + return new TedFileReader(dataStream); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Data/IMapData.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Data/IMapData.cs new file mode 100644 index 0000000..bd33593 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Data/IMapData.cs @@ -0,0 +1,8 @@ +using AnakinRaW.CommonUtilities; +using PG.StarWarsGame.Files.ChunkFiles.Data; + +namespace PG.StarWarsGame.Files.TED.Data; + +public class IMapData : DisposableObject, IChunkData +{ +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/ITedFile.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/ITedFile.cs new file mode 100644 index 0000000..76f610d --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/ITedFile.cs @@ -0,0 +1,6 @@ +using PG.StarWarsGame.Files.ChunkFiles.Files; +using PG.StarWarsGame.Files.TED.Data; + +namespace PG.StarWarsGame.Files.TED.Files; + +public interface ITedFile : IChunkFile; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/TedFile.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/TedFile.cs new file mode 100644 index 0000000..5b075d6 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/TedFile.cs @@ -0,0 +1,7 @@ +using System; +using PG.StarWarsGame.Files.TED.Data; + +namespace PG.StarWarsGame.Files.TED.Files; + +public sealed class TedFile(IMapData data, TedFileInformation fileInformation, IServiceProvider serviceProvider) + : PetroglyphFileHolder(data, fileInformation, serviceProvider), ITedFile; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/TedFileInformation.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/TedFileInformation.cs new file mode 100644 index 0000000..22a532e --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Files/TedFileInformation.cs @@ -0,0 +1,19 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace PG.StarWarsGame.Files.TED.Files; + +public sealed record TedFileInformation : PetroglyphMegPackableFileInformation +{ + /// + /// Initializes a new instance of the class. + /// + /// The file path of the alo file. + /// Information whether this file info is created from a meg data entry. + /// is null. + /// is empty. + [SetsRequiredMembers] + public TedFileInformation(string path, bool isInMeg) : base(path, isInMeg) + { + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/PG.StarWarsGame.Files.TED.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/PG.StarWarsGame.Files.TED.csproj new file mode 100644 index 0000000..0071f91 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/PG.StarWarsGame.Files.TED.csproj @@ -0,0 +1,27 @@ + + + netstandard2.0;netstandard2.1;net10.0 + PG.StarWarsGame.Files.TED + PG.StarWarsGame.Files.TED + AlamoEngineTools.PG.StarWarsGame.Files.TED + alamo,petroglyph,glyphx + + + + true + + + true + snupkg + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Services/ITedFileService.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Services/ITedFileService.cs new file mode 100644 index 0000000..72b96d4 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Services/ITedFileService.cs @@ -0,0 +1,14 @@ +using System.IO; +using System.IO.Abstractions; +using PG.StarWarsGame.Files.TED.Files; + +namespace PG.StarWarsGame.Files.TED.Services; + +public interface ITedFileService +{ + void RemoveMapPreview(Stream tedStream, FileSystemStream destination, bool extract, out byte[]? previewImageBytes); + + ITedFile Load(string path); + + ITedFile Load(Stream stream); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Services/TedFileService.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Services/TedFileService.cs new file mode 100644 index 0000000..9e97c4f --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Services/TedFileService.cs @@ -0,0 +1,43 @@ +using PG.Commons.Services; +using PG.Commons.Utilities; +using PG.StarWarsGame.Files.TED.Files; +using System; +using System.IO; +using System.IO.Abstractions; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Files.TED.Binary.Reader; + +namespace PG.StarWarsGame.Files.TED.Services; + +internal class TedFileService(IServiceProvider serviceProvider) : ServiceBase(serviceProvider), ITedFileService +{ + public void RemoveMapPreview(Stream tedStream, FileSystemStream destination, bool extract, out byte[]? previewImageBytes) + { + if (tedStream == null) + throw new ArgumentNullException(nameof(tedStream)); + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + + throw new NotImplementedException(); + } + + public ITedFile Load(string path) + { + using var fileStream = FileSystem.FileStream.New(path, FileMode.Open, FileAccess.Read, FileShare.Read); + return Load(fileStream); + } + + public ITedFile Load(Stream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + using var reader = Services.GetRequiredService().GetReader(stream); + var map = reader.Read(); + + var filePath = stream.GetFilePath(out var isInMeg); + var fileInfo = new TedFileInformation(filePath, isInMeg); + + return new TedFile(map, fileInfo, Services); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/TedServiceContribution.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/TedServiceContribution.cs new file mode 100644 index 0000000..d2ba314 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/TedServiceContribution.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Files.TED.Binary.Reader; +using PG.StarWarsGame.Files.TED.Services; + +namespace PG.StarWarsGame.Files.TED; + +public static class TedServiceContribution +{ + public static void SupportTED(this IServiceCollection serviceCollection) + { + serviceCollection.AddSingleton(sp => new TedFileReaderFactory(sp)); + serviceCollection.AddSingleton(sp => new TedFileService(sp)); + } +} \ No newline at end of file From 8272e1e7ac94a0de3fc48e781a4e87d6313f80c8 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 28 Mar 2026 10:13:44 +0100 Subject: [PATCH 2/3] start implementing TED support --- ModVerify.slnx | 1 + .../Identifier/AloContentInfoIdentifier.cs | 10 +- .../Reader/Animations/AnimationReaderBase.cs | 28 +-- .../Binary/Reader/Models/ModelFileReader.cs | 44 ++-- .../Reader/Particles/ParticleReaderV1.cs | 26 +-- .../Binary/Metadata/Chunk.cs | 189 ++++++++++++++++++ .../Binary/Metadata/ChunkFile.cs | 48 +++++ .../Binary/Metadata/ChunkMetadata.cs | 50 +++-- .../Binary/Reader/ChunkFileReaderBase.cs | 9 +- .../Binary/Reader/ChunkReader.cs | 38 +++- .../CommonDatTestBase.cs | 13 ++ .../PG.StarWarsGame.Files.TED.Test.csproj | 37 ++++ .../Services/TedFileServiceTest.cs | 52 +++++ .../Binary/Reader/TedFileReader.cs | 5 +- .../Binary/Writer/MapPreviewExtractor.cs | 57 ++++++ .../Properties/AssemblyAttributes.cs | 3 + .../Services/ITedFileService.cs | 4 +- .../Services/TedFileService.cs | 14 +- 18 files changed, 544 insertions(+), 84 deletions(-) create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/Chunk.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkFile.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/CommonDatTestBase.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/PG.StarWarsGame.Files.TED.Test.csproj create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/Services/TedFileServiceTest.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Writer/MapPreviewExtractor.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.TED/Properties/AssemblyAttributes.cs diff --git a/ModVerify.slnx b/ModVerify.slnx index 35980ab..dd53573 100644 --- a/ModVerify.slnx +++ b/ModVerify.slnx @@ -20,6 +20,7 @@ + diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/AloContentInfoIdentifier.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/AloContentInfoIdentifier.cs index 6fce5c0..3024efd 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/AloContentInfoIdentifier.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/AloContentInfoIdentifier.cs @@ -19,7 +19,7 @@ public AloContentInfo GetContentInfo(Stream stream) case ChunkType.Skeleton: case ChunkType.Mesh: case ChunkType.Light: - return FromModel(chunk.Size, chunkReader); + return FromModel(chunk.BodySize, chunkReader); case ChunkType.Connections: return FromConnection(chunkReader); case ChunkType.Particle: @@ -41,14 +41,14 @@ private static AloContentInfo FromAnimation(ChunkReader chunkReader) switch ((ChunkType)chunk.Value.Type) { case ChunkType.AnimationInformation: - return chunk.Value.Size switch + return chunk.Value.BodySize switch { 36 => new AloContentInfo(AloType.Animation, AloVersion.V2), 18 => new AloContentInfo(AloType.Animation, AloVersion.V1), _ => throw new BinaryCorruptedException("Invalid ALA animation.") }; default: - chunkReader.Skip(chunk.Value.Size); + chunkReader.Skip(chunk.Value.BodySize); break; } chunk = chunkReader.TryReadChunk(); @@ -66,7 +66,7 @@ private static AloContentInfo FromConnection(ChunkReader chunkReader) case ChunkType.ProxyConnection: case ChunkType.ObjectConnection: case ChunkType.ConnectionCounts: - chunkReader.Skip(chunk.Value.Size); + chunkReader.Skip(chunk.Value.BodySize); break; case ChunkType.Dazzle: return new AloContentInfo(AloType.Model, AloVersion.V2); @@ -92,7 +92,7 @@ private static AloContentInfo FromModel(int size, ChunkReader chunkReader) case ChunkType.Skeleton: case ChunkType.Mesh: case ChunkType.Light: - return FromModel(chunk.Value.Size, chunkReader); + return FromModel(chunk.Value.BodySize, chunkReader); default: throw new BinaryCorruptedException("Invalid ALO model."); } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderBase.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderBase.cs index 632caf3..dbcfc4d 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderBase.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderBase.cs @@ -27,11 +27,11 @@ public sealed override AlamoAnimation Read() { var chunk = ChunkReader.ReadChunk(ref actualSize); ReadAnimation(chunk, ref info, bones); - actualSize += chunk.Size; + actualSize += chunk.BodySize; - } while (actualSize < rootChunk.Size); + } while (actualSize < rootChunk.BodySize); - if (actualSize != rootChunk.Size) + if (actualSize != rootChunk.BodySize) throw new BinaryCorruptedException(); if (info.NumberBones != bones.Count) @@ -54,13 +54,15 @@ protected virtual void ReadAnimation( switch (chunk.Type) { case (int)AnimationChunkTypes.AnimationInfo: - animationInformation = ReadAnimationInfo(chunk.Size); + if (chunk.RawSize < 0) + ThrowChunkSizeTooLargeException(); + animationInformation = ReadAnimationInfo(chunk.BodySize); break; case (int)AnimationChunkTypes.BoneData: - ReadBonesData(chunk.Size, bones); + ReadBonesData(chunk.BodySize, bones); break; default: - ChunkReader.Skip(chunk.Size); + ChunkReader.Skip(chunk.BodySize); break; } } @@ -70,10 +72,12 @@ protected virtual void ReadBoneDataCore(ChunkMetadata chunk, List bones) { var chunk = ChunkReader.ReadChunk(ref actualSize); ReadBoneDataCore(chunk, bones); - actualSize += chunk.Size; + actualSize += chunk.BodySize; } while (actualSize < chunkSize); @@ -108,13 +112,13 @@ private void ReadBoneInfo(int chunkSize, List bones) switch (chunk.Type) { case (int)AnimationChunkTypes.BoneName: - name = ChunkReader.ReadString(chunk.Size, Encoding.ASCII, true, ref actualSize); + name = ChunkReader.ReadString(chunk.BodySize, Encoding.ASCII, true, ref actualSize); break; case (int)AnimationChunkTypes.BoneIndex: index = ChunkReader.ReadDword(ref actualSize); break; default: - ChunkReader.Skip(chunk.Size, ref actualSize); + ChunkReader.Skip(chunk.BodySize, ref actualSize); break; } @@ -156,7 +160,7 @@ private AnimationInformationData ReadAnimationInfo(int chunkSize) info.ScaleBlockSize = ChunkReader.ReadDword(ref actualSize); break; default: - ChunkReader.Skip(chunk.Size, ref actualSize); + ChunkReader.Skip(chunk.BodySize, ref actualSize); break; } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Models/ModelFileReader.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Models/ModelFileReader.cs index 13da425..6aee4b6 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Models/ModelFileReader.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Models/ModelFileReader.cs @@ -25,16 +25,16 @@ public override AlamoModel Read() switch (chunk.Value.Type) { case (int)ModelChunkTypes.Skeleton: - ReadSkeleton(chunk.Value.Size, bones); + ReadSkeleton(chunk.Value.BodySize, bones); break; case (int)ModelChunkTypes.Mesh: - ReadMesh(chunk.Value.Size, textures, shaders); + ReadMesh(chunk.Value.BodySize, textures, shaders); break; case (int)ModelChunkTypes.Connections: - ReadConnections(chunk.Value.Size, proxies); + ReadConnections(chunk.Value.BodySize, proxies); break; default: - ChunkReader.Skip(chunk.Value.Size); + ChunkReader.Skip(chunk.Value.BodySize); break; } @@ -59,14 +59,16 @@ private void ReadConnections(int size, HashSet proxies) do { var chunk = ChunkReader.ReadChunk(ref actualSize); + if (chunk.RawSize < 0) + ThrowChunkSizeTooLargeException(); if (chunk.Type == (int)ModelChunkTypes.ProxyConnection) { - ReadProxy(chunk.Size, out var proxy, ref actualSize); + ReadProxy(chunk.BodySize, out var proxy, ref actualSize); proxies.Add(proxy); } else - ChunkReader.Skip(chunk.Size, ref actualSize); + ChunkReader.Skip(chunk.BodySize, ref actualSize); } while (actualSize < size); @@ -84,9 +86,9 @@ private void ReadProxy(int size, out string proxy, ref int readSize) var chunk = ChunkReader.ReadMiniChunk(ref actualSize); if (chunk.Type == 5) - proxy = ChunkReader.ReadString(chunk.Size, Encoding.ASCII, true, ref actualSize); + proxy = ChunkReader.ReadString(chunk.BodySize, Encoding.ASCII, true, ref actualSize); else - ChunkReader.Skip(chunk.Size, ref actualSize); + ChunkReader.Skip(chunk.BodySize, ref actualSize); } while (actualSize < size); @@ -110,9 +112,9 @@ private void ReadMesh(int size, ISet textures, ISet shaders) var chunk = ChunkReader.ReadChunk(ref actualSize); if (chunk.Type == (int)ModelChunkTypes.SubMeshMaterialInformation) - ReadSubMeshMaterialInformation(chunk.Size, textures, shaders, ref actualSize); + ReadSubMeshMaterialInformation(chunk.BodySize, textures, shaders, ref actualSize); else - ChunkReader.Skip(chunk.Size, ref actualSize); + ChunkReader.Skip(chunk.BodySize, ref actualSize); } while (actualSize < size); @@ -132,15 +134,17 @@ private void ReadSubMeshMaterialInformation(int size, ISet textures, ISe { case (int)ModelChunkTypes.ShaderFileName: { - var shader = ChunkReader.ReadString(chunk.Size, Encoding.ASCII, true, ref actualSize); + var shader = ChunkReader.ReadString(chunk.BodySize, Encoding.ASCII, true, ref actualSize); shaders.Add(shader); break; } case (int)ModelChunkTypes.ShaderTexture: - ReadShaderTexture(chunk.Size, textures, ref actualSize); + if (chunk.RawSize < 0) + ThrowChunkSizeTooLargeException(); + ReadShaderTexture(chunk.BodySize, textures, ref actualSize); break; default: - ChunkReader.Skip(chunk.Size, ref actualSize); + ChunkReader.Skip(chunk.BodySize, ref actualSize); break; } @@ -162,11 +166,11 @@ private void ReadShaderTexture(int size, ISet textures, ref int readSize if (mini.Type == 2) { - var texture = ChunkReader.ReadString(mini.Size, Encoding.ASCII, true, ref actualTextureChunkSize); + var texture = ChunkReader.ReadString(mini.BodySize, Encoding.ASCII, true, ref actualTextureChunkSize); textures.Add(texture); } else - ChunkReader.Skip(mini.Size, ref actualTextureChunkSize); + ChunkReader.Skip(mini.BodySize, ref actualTextureChunkSize); } while (actualTextureChunkSize != size); @@ -191,7 +195,7 @@ private void ReadSkeleton(int size, IList bones) var boneCountChunk = ChunkReader.ReadChunk(ref actualSize); - Debug.Assert(boneCountChunk is { Size: 128, Type: (int)ModelChunkTypes.BoneCount }); + Debug.Assert(boneCountChunk is { BodySize: 128, Type: (int)ModelChunkTypes.BoneCount }); var boneCount = ChunkReader.ReadDword(ref actualSize); @@ -201,24 +205,24 @@ private void ReadSkeleton(int size, IList bones) { var bone = ChunkReader.ReadChunk(ref actualSize); - Debug.Assert(bone is { Type: (int)ModelChunkTypes.Bone, IsContainer: true }); + Debug.Assert(bone is { Type: (int)ModelChunkTypes.Bone, HasChildrenHint: true }); var boneReadSize = 0; - while (boneReadSize < bone.Size) + while (boneReadSize < bone.BodySize) { var innerBoneChunk = ChunkReader.ReadChunk(ref boneReadSize); if (innerBoneChunk.Type == (int)ModelChunkTypes.BoneName) { - var nameSize = innerBoneChunk.Size; + var nameSize = innerBoneChunk.BodySize; var name = ChunkReader.ReadString(nameSize, Encoding.ASCII, true, ref boneReadSize); bones.Add(name); } else { - ChunkReader.Skip(innerBoneChunk.Size, ref boneReadSize); + ChunkReader.Skip(innerBoneChunk.BodySize, ref boneReadSize); } } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleReaderV1.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleReaderV1.cs index ed6de4b..bbca381 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleReaderV1.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleReaderV1.cs @@ -30,23 +30,23 @@ public override AlamoParticle Read() switch (chunk.Type) { case (int)ParticleChunkType.Name: - ReadName(chunk.Size, out name); + ReadName(chunk.BodySize, out name); break; case (int)ParticleChunkType.Emitters: - ReadEmitters(chunk.Size, textures); + ReadEmitters(chunk.BodySize, textures); break; default: - ChunkReader.Skip(chunk.Size); + ChunkReader.Skip(chunk.BodySize); break; } - actualSize += chunk.Size; + actualSize += chunk.BodySize; - } while (actualSize < rootChunk.Size); + } while (actualSize < rootChunk.BodySize); - if (actualSize != rootChunk.Size) + if (actualSize != rootChunk.BodySize) throw new BinaryCorruptedException(); if (string.IsNullOrEmpty(name)) @@ -70,9 +70,9 @@ private void ReadEmitters(int size, HashSet textures) if (chunk.Type != (int)ParticleChunkType.Emitter) throw new BinaryCorruptedException("Unable to read particle"); - ReadEmitter(chunk.Size, textures); + ReadEmitter(chunk.BodySize, textures); - actualSize += chunk.Size; + actualSize += chunk.BodySize; } while (actualSize < size); @@ -92,24 +92,24 @@ private void ReadEmitter(int chunkSize, HashSet textures) if (chunk.Type == (int)ParticleChunkType.Properties) { var shader = ChunkReader.ReadDword(); - ChunkReader.Skip(chunk.Size - sizeof(uint)); + ChunkReader.Skip(chunk.BodySize - sizeof(uint)); } else if (chunk.Type == (int)ParticleChunkType.ColorTextureName) { - var texture = ChunkReader.ReadString(chunk.Size, Encoding.ASCII, true); + var texture = ChunkReader.ReadString(chunk.BodySize, Encoding.ASCII, true); textures.Add(texture); } else if (chunk.Type == (int)ParticleChunkType.BumpTextureName) { - var bump = ChunkReader.ReadString(chunk.Size, Encoding.ASCII, true); + var bump = ChunkReader.ReadString(chunk.BodySize, Encoding.ASCII, true); textures.Add(bump); } else { - ChunkReader.Skip(chunk.Size); + ChunkReader.Skip(chunk.BodySize); } - actualSize += chunk.Size; + actualSize += chunk.BodySize; } while (actualSize < chunkSize); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/Chunk.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/Chunk.cs new file mode 100644 index 0000000..c708838 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/Chunk.cs @@ -0,0 +1,189 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using PG.StarWarsGame.Files.Binary; + +namespace PG.StarWarsGame.Files.ChunkFiles.Binary.Metadata; + +public sealed class Chunk : IBinary +{ + public ChunkMetadata Info { get; } + + public byte[]? Data { get; } + + public IReadOnlyList Children { get; } + + public byte[] Bytes + { + get + { + var bytes = new byte[Size]; + GetBytes(bytes); + return bytes; + } + } + + public int Size => Info.IsMiniChunk + ? 2 + Data!.Length + : Data is not null + ? 8 + Data.Length + : 8 + Children.Sum(c => c.Size); + + public Chunk(ChunkMetadata info, byte[] data) + { + Info = info; + Data = data ?? throw new ArgumentNullException(nameof(data)); + Children = []; + } + + public Chunk(ChunkMetadata info, IReadOnlyList children) + { + if (info.IsMiniChunk) + throw new ArgumentException("MiniChunks cannot have child chunks", nameof(info)); + Info = info; + Data = null; + Children = children ?? throw new ArgumentNullException(nameof(children)); + } + + public void GetBytes(Span bytes) + { + if (Info.IsMiniChunk) + { + bytes[0] = (byte)Info.Type; + bytes[1] = (byte)Data!.Length; + Data.AsSpan().CopyTo(bytes[2..]); + return; + } + + BinaryPrimitives.WriteUInt32LittleEndian(bytes, Info.Type); + + if (Data is not null) + { + BinaryPrimitives.WriteInt32LittleEndian(bytes[4..], Data.Length); + Data.AsSpan().CopyTo(bytes[8..]); + } + else + { + // .Sum is a checked operation and will already throw an overflow exception + var bodySize = Children.Sum(c => c.Size); + var hasMiniChunkChildren = Children.Count > 0 && Children[0].Info.IsMiniChunk; + + var sizeField = hasMiniChunkChildren + ? bodySize + : (int)(bodySize | 0x8000_0000u); + + BinaryPrimitives.WriteInt32LittleEndian(bytes[4..], sizeField); + + var offset = 8; + foreach (var child in Children) + { + child.GetBytes(bytes[offset..]); + offset += child.Size; + } + } + } +} + +/// +/// Provides factory methods for creating chunks and chunk files. +/// +/// +/// +/// This class provides static methods for creating hierarchical chunk structures. +/// Three chunk types are supported: +/// +/// +/// Data chunks - Regular chunks containing binary data +/// Mini-chunks - Chunks with 2-byte headers for data up to 255 bytes +/// Node chunks - Container chunks that hold child chunks +/// +/// +public static class ChunkFactory +{ + /// + /// Creates a data chunk with the specified type and binary data. + /// + /// The chunk type identifier. + /// The binary data to store in the chunk. + /// A containing the specified data. + /// is . + public static Chunk Data(uint type, byte[] data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + + var metadata = new ChunkMetadata(type, (uint)data.Length, false); + return new Chunk(metadata, data); + } + + /// + /// Creates a mini-chunk with the specified type and binary data. + /// + /// The mini-chunk type identifier. + /// The binary data to store in the mini-chunk. Maximum length is 255 bytes. + /// A representing a mini-chunk with a 2-byte header. + /// is . + /// The length of exceeds 255 bytes. + public static Chunk Mini(byte type, byte[] data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + + if (data.Length > byte.MaxValue) + throw new ArgumentException( + $"Mini-chunk data cannot exceed {byte.MaxValue} bytes. Provided data length: {data.Length}.", + nameof(data)); + + var metadata = new ChunkMetadata(type, (uint)data.Length, true); + return new Chunk(metadata, data); + } + + /// + /// Creates a chunk node that contains child chunks. + /// + /// The chunk type identifier. + /// The child chunks to include in this node. + /// A containing the specified children. + /// is . + public static Chunk Node(uint type, params Chunk[] children) + { + if (children == null) + throw new ArgumentNullException(nameof(children)); + + var size = (uint)children.Sum(c => c.Size); + var metadata = new ChunkMetadata(type, size, false); + return new Chunk(metadata, children); + } + + /// + /// Creates a chunk node that contains child chunks built using a configuration action. + /// + /// The chunk type identifier. + /// An action that populates a list with child chunks. + /// A containing the configured children. + /// is . + public static Chunk Node(uint type, Action> configure) + { + if (configure == null) + throw new ArgumentNullException(nameof(configure)); + + var children = new List(); + configure(children); + return Node(type, children.ToArray()); + } + + /// + /// Creates a chunk file containing the specified root chunks. + /// + /// The top-level chunks to include in the file. + /// A containing the specified root chunks. + /// is . + public static ChunkFile File(params Chunk[] rootChunks) + { + if (rootChunks == null) + throw new ArgumentNullException(nameof(rootChunks)); + + return new ChunkFile(rootChunks); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkFile.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkFile.cs new file mode 100644 index 0000000..1c38ddb --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkFile.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using PG.StarWarsGame.Files.Binary.File; + +namespace PG.StarWarsGame.Files.ChunkFiles.Binary.Metadata; + +public sealed class ChunkFile : IBinaryFile +{ + public IReadOnlyList RootChunks { get; } + + public int Size => RootChunks.Sum(c => c.Size); + + public byte[] Bytes + { + get + { + var bytes = new byte[Size]; + GetBytes(bytes); + return bytes; + } + } + + public ChunkFile(IReadOnlyList rootChunks) + { + if (rootChunks == null) + throw new ArgumentNullException(nameof(rootChunks)); + if (rootChunks.Count == 0) + throw new ArgumentOutOfRangeException(nameof(rootChunks), "A chunk file must contain at least one chunk"); + RootChunks = rootChunks; + } + + public void GetBytes(Span bytes) + { + var offset = 0; + foreach (var chunk in RootChunks) + { + chunk.GetBytes(bytes[offset..]); + offset += chunk.Size; + } + } + + public void WriteTo(Stream stream) + { + stream.Write(Bytes, 0, Size); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkMetadata.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkMetadata.cs index df47dc2..3a76749 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkMetadata.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkMetadata.cs @@ -1,29 +1,39 @@ -namespace PG.StarWarsGame.Files.ChunkFiles.Binary.Metadata; +using System; +using System.Diagnostics; +namespace PG.StarWarsGame.Files.ChunkFiles.Binary.Metadata; + +[DebuggerDisplay("Type: {Type}, Size: {BodySize}, Mini:{IsMiniChunk}")] public readonly struct ChunkMetadata { - public readonly int Type; - public readonly int Size; - - private ChunkMetadata(int type, int size, bool isContainer, bool isMiniChunk) - { - Type = type; - Size = size; - IsMiniChunk = isMiniChunk; - IsContainer = isContainer; - } + public readonly uint Type; + public readonly uint RawSize; + public readonly bool IsMiniChunk; - public bool IsContainer { get; } + /// + /// Indicates that bit 31 of RawSize is set. + /// This is a hint that the body contains child chunks, not a guarantee. + /// + public bool HasChildrenHint => !IsMiniChunk && (int)RawSize < 0; - public bool IsMiniChunk { get; } + /// + /// Gets the size of the chunk's data in bytes. + /// + /// + /// This value has bit 31 masked off compared to . + /// Per spec, bit 31 is set only for chunks containing regular child chunks. + /// Chunks containing mini-chunks (treated as data) do NOT set bit 31. + /// Since this library doesn't support sizes > , masking bit 31 + /// has no practical impact on the usable size range. + /// + public int BodySize => (int)(RawSize & 0x7FFF_FFFF); - public static ChunkMetadata FromContainer(int type, int size) + public ChunkMetadata(uint type, uint rawSize, bool isMiniChunk) { - return new ChunkMetadata(type, size, true, false); - } - - public static ChunkMetadata FromData(int type, int size, bool isMini = false) - { - return new ChunkMetadata(type, size, false, isMini); + if (isMiniChunk && rawSize > byte.MaxValue) + throw new ArgumentOutOfRangeException(nameof(rawSize), "Mini chunk size must fit in a byte (0-255)."); + Type = type; + RawSize = rawSize; + IsMiniChunk = isMiniChunk; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkFileReaderBase.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkFileReaderBase.cs index 01d085a..eb66444 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkFileReaderBase.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkFileReaderBase.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using AnakinRaW.CommonUtilities; using PG.StarWarsGame.Files.ChunkFiles.Data; @@ -20,4 +21,10 @@ protected override void DisposeResources() base.DisposeResources(); ChunkReader.Dispose(); } + + //[DoesNotReturn] + protected void ThrowChunkSizeTooLargeException() + { + throw new NotSupportedException("Chunk sizes larger than int.MaxValue are not supported."); + } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkReader.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkReader.cs index 742924d..024d6ee 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkReader.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkReader.cs @@ -20,13 +20,9 @@ public ChunkReader(Stream stream, bool leaveOpen = false) public ChunkMetadata ReadChunk() { - var type = _binaryReader.ReadInt32(); - var rawSize = _binaryReader.ReadInt32(); - - var isContainer = (rawSize & 0x80000000) != 0; - var size = rawSize & 0x7FFFFFFF; - - return isContainer ? ChunkMetadata.FromContainer(type, size) : ChunkMetadata.FromData(type, size); + var type = _binaryReader.ReadUInt32(); + var rawSize = _binaryReader.ReadUInt32(); + return new ChunkMetadata(type, rawSize, false); } public ChunkMetadata ReadChunk(ref int readBytes) @@ -43,9 +39,35 @@ public ChunkMetadata ReadMiniChunk(ref int readBytes) readBytes += 2; - return ChunkMetadata.FromData(type, size, true); + return new ChunkMetadata(type, size, true); + } + + public byte[] ReadData(ChunkMetadata chunk) + { + if (chunk.HasChildrenHint) + throw new InvalidOperationException("Unable to read data from container chunk."); + + return _binaryReader.ReadBytes(chunk.BodySize); + } + + public byte[] ReadData(int size) + { + return size < 0 ? + throw new ArgumentOutOfRangeException(nameof(size), "size cannot be negative") : + _binaryReader.ReadBytes(size); } + public byte[] ReadData(ChunkMetadata chunk, ref int readSize) + { + if (chunk.HasChildrenHint) + throw new InvalidOperationException("Unable to read data from container chunk."); + + var data = _binaryReader.ReadBytes(chunk.BodySize); + readSize += chunk.BodySize; + return data; + } + + public uint ReadDword(ref int readSize) { var value = _binaryReader.ReadUInt32(); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/CommonDatTestBase.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/CommonDatTestBase.cs new file mode 100644 index 0000000..85589e4 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/CommonDatTestBase.cs @@ -0,0 +1,13 @@ +using AnakinRaW.CommonUtilities.Testing; +using Microsoft.Extensions.DependencyInjection; + +namespace PG.StarWarsGame.Files.TED.Test; + +public class CommonDatTestBase : TestBaseWithFileSystem +{ + protected override void SetupServices(IServiceCollection serviceCollection) + { + base.SetupServices(serviceCollection); + serviceCollection.SupportTED(); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/PG.StarWarsGame.Files.TED.Test.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/PG.StarWarsGame.Files.TED.Test.csproj new file mode 100644 index 0000000..04f7ff7 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/PG.StarWarsGame.Files.TED.Test.csproj @@ -0,0 +1,37 @@ + + + + net8.0;net10.0 + $(TargetFrameworks);net481 + + + false + true + Exe + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/Services/TedFileServiceTest.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/Services/TedFileServiceTest.cs new file mode 100644 index 0000000..4987131 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED.Test/Services/TedFileServiceTest.cs @@ -0,0 +1,52 @@ +using System.IO; +using System.IO.Abstractions; +using AnakinRaW.CommonUtilities.Hashing; +using AnakinRaW.CommonUtilities.Testing; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons; +using PG.StarWarsGame.Files.TED.Services; +using Testably.Abstractions; +using Xunit; + +namespace PG.StarWarsGame.Files.TED.Test.Services; + +public class TedFileServiceTest : TestBaseWithFileSystem +{ + private readonly TedFileService _service; + + protected override IFileSystem CreateFileSystem() + { + return new RealFileSystem(); + } + + public TedFileServiceTest() + { + _service = new TedFileService(ServiceProvider); + } + + protected override void SetupServices(IServiceCollection serviceCollection) + { + base.SetupServices(serviceCollection); + base.SetupServices(serviceCollection); + serviceCollection.AddSingleton(sp => new HashingService(sp)); + PetroglyphCommons.ContributeServices(serviceCollection); + serviceCollection.SupportTED(); + } + + [Fact] + public void Foo() + { + const string path = @"C:\Program Files (x86)\Steam\steamapps\workshop\content\32470\1129810972\Data\Art\Maps\_land_planet_felucia_00.ted"; + using var tedFs = FileSystem.FileStream.New(path, FileMode.Open); + using var dest = FileSystem.FileStream.New("c:/test/map.ted", FileMode.Create); + _service.RemoveMapPreview(tedFs, dest, true, out var bytes); + + if (bytes is not null) + { + using var img = FileSystem.FileStream.New("c:/test/img.dds", FileMode.Create); + using var streamWriter = new BinaryWriter(img); + streamWriter.Write(bytes); + } + + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReader.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReader.cs index 92e0c5f..0556a80 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReader.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReader.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using PG.StarWarsGame.Files.ChunkFiles.Binary.Reader; using PG.StarWarsGame.Files.TED.Data; @@ -8,6 +9,6 @@ internal sealed class TedFileReader(Stream stream) : ChunkFileReaderBase Date: Sat, 28 Mar 2026 10:25:58 +0100 Subject: [PATCH 3/3] add testing project for chunk file project --- ModVerify.slnx | 1 + ....StarWarsGame.Files.ChunkFiles.Test.csproj | 37 +++++++++++++++++++ .../Properties/AssemblyAttributes.cs | 3 ++ 3 files changed, 41 insertions(+) create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles.Test/PG.StarWarsGame.Files.ChunkFiles.Test.csproj create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Properties/AssemblyAttributes.cs diff --git a/ModVerify.slnx b/ModVerify.slnx index dd53573..caae34b 100644 --- a/ModVerify.slnx +++ b/ModVerify.slnx @@ -19,6 +19,7 @@ + diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles.Test/PG.StarWarsGame.Files.ChunkFiles.Test.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles.Test/PG.StarWarsGame.Files.ChunkFiles.Test.csproj new file mode 100644 index 0000000..cb0168a --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles.Test/PG.StarWarsGame.Files.ChunkFiles.Test.csproj @@ -0,0 +1,37 @@ + + + + net8.0;net10.0 + $(TargetFrameworks);net481 + + + false + true + Exe + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Properties/AssemblyAttributes.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Properties/AssemblyAttributes.cs new file mode 100644 index 0000000..6e1a543 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Properties/AssemblyAttributes.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly:InternalsVisibleTo("PG.StarWarsGame.Files.ChunkFiles.Test")] \ No newline at end of file