diff --git a/ModVerify.slnx b/ModVerify.slnx
index 3527ff4..caae34b 100644
--- a/ModVerify.slnx
+++ b/ModVerify.slnx
@@ -19,7 +19,10 @@
+
+
+
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.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/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.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
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/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..0556a80
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Reader/TedFileReader.cs
@@ -0,0 +1,14 @@
+using System;
+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 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/Binary/Writer/MapPreviewExtractor.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Writer/MapPreviewExtractor.cs
new file mode 100644
index 0000000..bd6a4f6
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Binary/Writer/MapPreviewExtractor.cs
@@ -0,0 +1,57 @@
+using System;
+using System.IO;
+using PG.StarWarsGame.Files.ChunkFiles.Binary.Metadata;
+using PG.StarWarsGame.Files.ChunkFiles.Binary.Reader;
+
+namespace PG.StarWarsGame.Files.TED.Binary.Writer;
+
+internal sealed class MapPreviewExtractor
+{
+ public bool ContainsPreviewPicture(Stream tedStream)
+ {
+ if (tedStream == null)
+ throw new ArgumentNullException(nameof(tedStream));
+ using var reader = new ChunkReader(tedStream, true);
+ ChunkMetadata? chunkInfo;
+ while ((chunkInfo = reader.TryReadChunk()) != null)
+ {
+ if (chunkInfo.Value.Type == 0x13)
+ return true;
+ reader.Skip(chunkInfo.Value.BodySize);
+ }
+ return false;
+ }
+
+ public bool ExtractPreview(Stream tedStream, Stream destination, bool extract, out byte[]? previewImageBytes)
+ {
+ if (tedStream == null)
+ throw new ArgumentNullException(nameof(tedStream));
+ if (destination == null)
+ throw new ArgumentNullException(nameof(destination));
+
+ previewImageBytes = null;
+ var extracted = false;
+
+ using var reader = new ChunkReader(tedStream, true);
+
+ ChunkMetadata? chunkInfo;
+ while ((chunkInfo = reader.TryReadChunk()) != null)
+ {
+ if (chunkInfo.Value.Type == 0x13)
+ {
+ extracted = true;
+ if (extract)
+ previewImageBytes = reader.ReadData(chunkInfo.Value.BodySize);
+ else
+ reader.Skip(chunkInfo.Value.BodySize);
+ }
+ else
+ {
+ var chunk = new Chunk(chunkInfo.Value, reader.ReadData(chunkInfo.Value.BodySize));
+ destination.Write(chunk.Bytes, 0, chunk.Bytes.Length);
+ }
+ }
+
+ return extracted;
+ }
+}
\ 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/Properties/AssemblyAttributes.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Properties/AssemblyAttributes.cs
new file mode 100644
index 0000000..d4ae6b1
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Properties/AssemblyAttributes.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly:InternalsVisibleTo("PG.StarWarsGame.Files.TED.Test")]
\ 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..fa5ef8f
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Services/ITedFileService.cs
@@ -0,0 +1,16 @@
+using System.IO;
+using System.IO.Abstractions;
+using PG.StarWarsGame.Files.TED.Files;
+
+namespace PG.StarWarsGame.Files.TED.Services;
+
+public interface ITedFileService
+{
+ bool RemoveMapPreview(Stream tedStream, FileSystemStream destination, bool extract, out byte[]? previewImageBytes);
+
+ bool ContainsPreviewImage(Stream tedStream);
+
+ 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..361fcd1
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Files.TED/Services/TedFileService.cs
@@ -0,0 +1,53 @@
+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;
+using PG.StarWarsGame.Files.TED.Binary.Writer;
+
+namespace PG.StarWarsGame.Files.TED.Services;
+
+internal class TedFileService(IServiceProvider serviceProvider) : ServiceBase(serviceProvider), ITedFileService
+{
+ public bool 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));
+
+ var previewExtractor = new MapPreviewExtractor();
+ return previewExtractor.ExtractPreview(tedStream, destination, extract, out previewImageBytes);
+ }
+
+ public bool ContainsPreviewImage(Stream tedStream)
+ {
+ if (tedStream == null)
+ throw new ArgumentNullException(nameof(tedStream));
+ var previewExtractor = new MapPreviewExtractor();
+ return previewExtractor.ContainsPreviewPicture(tedStream);
+ }
+
+ 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