From 64854c21f31beaacc2da6adc6e51b727e4ba1a53 Mon Sep 17 00:00:00 2001 From: ReimousTH Date: Fri, 5 Dec 2025 14:20:34 +0300 Subject: [PATCH] Acroarts Format (Read) --- .../Formats/Acroarts/AckResourceTests.cs | 20 ++ Marathon/Formats/Acroarts/ACM/IACM.cs | 12 ++ Marathon/Formats/Acroarts/ACM/_acmBlurBelt.cs | 34 ++++ .../Formats/Acroarts/ACM/_acmParticlePlay.cs | 49 +++++ .../Acroarts/ACM/_acmPlaceFanShaped.cs | 38 ++++ .../Formats/Acroarts/ACM/_acmRotateNormal.cs | 39 ++++ Marathon/Formats/Acroarts/AckResource.cs | 62 ++++++ .../Acroarts/Chunks/AcroartsBinaryArchive.cs | 69 +++++++ .../Acroarts/Chunks/AcroartsBinaryBranch.cs | 41 ++++ .../Acroarts/Chunks/AcroartsBinaryData.cs | 46 +++++ .../Acroarts/Chunks/AcroartsBinaryLeaf.cs | 173 +++++++++++++++++ .../Chunks/AcroartsBinaryLeafMomNode.cs | 181 ++++++++++++++++++ .../Chunks/AcroartsBinaryLeafMomRoot.cs | 51 +++++ .../Acroarts/Chunks/AcroartsBinaryTrunk.cs | 49 +++++ .../Formats/Acroarts/Chunks/ChunkFactory.cs | 18 ++ .../Formats/Acroarts/Chunks/EndOfChunk.cs | 38 ++++ Marathon/Formats/Acroarts/Chunks/IChunk.cs | 16 ++ Marathon/Formats/Acroarts/Chunks/INode.cs | 11 ++ .../Formats/Acroarts/Chunks/UndefinedChunk.cs | 46 +++++ .../Formats/Acroarts/Spangles/ISpangle.cs | 16 ++ .../Acroarts/Spangles/SpangleCamera.cs | 31 +++ .../Acroarts/Spangles/SpangleCellSprite.cs | 36 ++++ .../Formats/Acroarts/Spangles/SpangleLight.cs | 31 +++ .../Acroarts/Spangles/SpangleObject.cs | 31 +++ .../Acroarts/Spangles/SpangleParticle.cs | 31 +++ .../Acroarts/Spangles/SpanglePrimitive.cs | 31 +++ .../Acroarts/Spangles/SpangleScreen.cs | 31 +++ .../Formats/Acroarts/Types/ChunkHeader.cs | 90 +++++++++ 28 files changed, 1321 insertions(+) create mode 100644 Marathon.Tests/Formats/Acroarts/AckResourceTests.cs create mode 100644 Marathon/Formats/Acroarts/ACM/IACM.cs create mode 100644 Marathon/Formats/Acroarts/ACM/_acmBlurBelt.cs create mode 100644 Marathon/Formats/Acroarts/ACM/_acmParticlePlay.cs create mode 100644 Marathon/Formats/Acroarts/ACM/_acmPlaceFanShaped.cs create mode 100644 Marathon/Formats/Acroarts/ACM/_acmRotateNormal.cs create mode 100644 Marathon/Formats/Acroarts/AckResource.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/AcroartsBinaryArchive.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/AcroartsBinaryBranch.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/AcroartsBinaryData.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeaf.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeafMomNode.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeafMomRoot.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/AcroartsBinaryTrunk.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/ChunkFactory.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/EndOfChunk.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/IChunk.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/INode.cs create mode 100644 Marathon/Formats/Acroarts/Chunks/UndefinedChunk.cs create mode 100644 Marathon/Formats/Acroarts/Spangles/ISpangle.cs create mode 100644 Marathon/Formats/Acroarts/Spangles/SpangleCamera.cs create mode 100644 Marathon/Formats/Acroarts/Spangles/SpangleCellSprite.cs create mode 100644 Marathon/Formats/Acroarts/Spangles/SpangleLight.cs create mode 100644 Marathon/Formats/Acroarts/Spangles/SpangleObject.cs create mode 100644 Marathon/Formats/Acroarts/Spangles/SpangleParticle.cs create mode 100644 Marathon/Formats/Acroarts/Spangles/SpanglePrimitive.cs create mode 100644 Marathon/Formats/Acroarts/Spangles/SpangleScreen.cs create mode 100644 Marathon/Formats/Acroarts/Types/ChunkHeader.cs diff --git a/Marathon.Tests/Formats/Acroarts/AckResourceTests.cs b/Marathon.Tests/Formats/Acroarts/AckResourceTests.cs new file mode 100644 index 00000000..88011f3e --- /dev/null +++ b/Marathon.Tests/Formats/Acroarts/AckResourceTests.cs @@ -0,0 +1,20 @@ +using Marathon.Formats.Acroarts; +using Marathon.Tests.Helpers; + +namespace Marathon.Tests.Formats.Particle +{ + internal class AckResourceTests : ITest + { + private Func[] _tests = [BinaryIdenticalTest]; + + private static bool BinaryIdenticalTest() + { + return TestHelper.CheckAllBinaries("*mab"); + } + + public bool Run() + { + return TestHelper.RunSubTests(_tests); + } + } +} diff --git a/Marathon/Formats/Acroarts/ACM/IACM.cs b/Marathon/Formats/Acroarts/ACM/IACM.cs new file mode 100644 index 00000000..a64d1b76 --- /dev/null +++ b/Marathon/Formats/Acroarts/ACM/IACM.cs @@ -0,0 +1,12 @@ +using Marathon.IO.Types.BINA; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public interface IACM + { + void Read(BINAReader in_reader,uint count); + + void Write(BINAWriter in_writer); + + } +} diff --git a/Marathon/Formats/Acroarts/ACM/_acmBlurBelt.cs b/Marathon/Formats/Acroarts/ACM/_acmBlurBelt.cs new file mode 100644 index 00000000..ab3e3aae --- /dev/null +++ b/Marathon/Formats/Acroarts/ACM/_acmBlurBelt.cs @@ -0,0 +1,34 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using System.Collections.Generic; +using System.Linq; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class _acmBlurBelt : IACM + { + public List Data { get; set; } + public _acmBlurBelt() { } + + public _acmBlurBelt(BINAReader in_reader, uint count) + { + Read(in_reader, count); + } + + public void Read(BINAReader in_reader, uint count) + { + var DefaultPosition = 0x50; + var Position = in_reader.Position; + Data = in_reader.ReadArray((int)(count * 4)).ToList(); + Logger.Warning($"_acmBlurBelt at {Position:x} Count:{count}"); + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + } +} diff --git a/Marathon/Formats/Acroarts/ACM/_acmParticlePlay.cs b/Marathon/Formats/Acroarts/ACM/_acmParticlePlay.cs new file mode 100644 index 00000000..31a0e28e --- /dev/null +++ b/Marathon/Formats/Acroarts/ACM/_acmParticlePlay.cs @@ -0,0 +1,49 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using System.Collections.Generic; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class _acmParticlePlay : IACM + { + public string Bank { get; set; } + public string BankParticle { get; set; } + public float Speed { get; set; } // > 0 (Speed * 1.0/60.0) + public uint Mode { get; set; } // 0 - 1 + + public _acmParticlePlay() { } + + public _acmParticlePlay(BINAReader in_reader, uint count) + { + Read(in_reader, count); + } + + public void Read(BINAReader in_reader, uint count) + { + var DefaultPosition = 0x50; + var Position = in_reader.Position; + in_reader.ReadAtOffset(in_reader.Read() + DefaultPosition, () => + { + Bank = in_reader.ReadString(StringBinaryFormat.NullTerminated); + }); + + in_reader.ReadAtOffset(in_reader.Read() + DefaultPosition, () => + { + BankParticle = in_reader.ReadString(StringBinaryFormat.NullTerminated); + }); + + Speed = in_reader.Read(); + Mode = in_reader.Read(); + + Logger.Warning($"_acmParticlePlay at {Position:x} Count:{count} Bank:{Bank} Particle:{BankParticle} Speed {Speed} Mode {Mode}"); + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + } +} diff --git a/Marathon/Formats/Acroarts/ACM/_acmPlaceFanShaped.cs b/Marathon/Formats/Acroarts/ACM/_acmPlaceFanShaped.cs new file mode 100644 index 00000000..1e091587 --- /dev/null +++ b/Marathon/Formats/Acroarts/ACM/_acmPlaceFanShaped.cs @@ -0,0 +1,38 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using System.Collections.Generic; +using System.Linq; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class _acmPlaceFanShaped : IACM + { + public uint Start { get; set; } + public uint End { get; set; } + + //End - Start + public _acmPlaceFanShaped() { } + + public _acmPlaceFanShaped(BINAReader in_reader, uint count) + { + Read(in_reader, count); + } + + public void Read(BINAReader in_reader, uint count) + { + var DefaultPosition = 0x50; + var Position = in_reader.Position; + Start = in_reader.Read(); + End = in_reader.Read(); + Logger.Warning($"_acmPlaceFanShaped at {Position:x} Count:{count} Start {Start} End {End}"); + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + } +} diff --git a/Marathon/Formats/Acroarts/ACM/_acmRotateNormal.cs b/Marathon/Formats/Acroarts/ACM/_acmRotateNormal.cs new file mode 100644 index 00000000..6b5d832c --- /dev/null +++ b/Marathon/Formats/Acroarts/ACM/_acmRotateNormal.cs @@ -0,0 +1,39 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using System.Collections.Generic; +using System.Linq; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class _acmRotateNormal : IACM + { + //Degrees + public float AngleX; + public float AngleY; + public float AngleZ; + public _acmRotateNormal() { } + + public _acmRotateNormal(BINAReader in_reader, uint count) + { + Read(in_reader, count); + } + + public void Read(BINAReader in_reader, uint count) + { + var DefaultPosition = 0x50; + var Position = in_reader.Position; + AngleX = in_reader.ReadSingle(); + AngleY = in_reader.ReadSingle(); + AngleZ = in_reader.ReadSingle(); + Logger.Warning($"_acmRotateNormal at {Position:x} Count:{count} AngleX {AngleX} AngleY {AngleY} AngleZ {AngleZ}"); + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + } +} diff --git a/Marathon/Formats/Acroarts/AckResource.cs b/Marathon/Formats/Acroarts/AckResource.cs new file mode 100644 index 00000000..2e7d803b --- /dev/null +++ b/Marathon/Formats/Acroarts/AckResource.cs @@ -0,0 +1,62 @@ +using Amicitia.IO.Streams; +using Marathon.Formats.Acroarts.Chunks; +using Marathon.IO; +using Marathon.IO.Extensions; +using Marathon.IO.Types.BINA; +using Marathon.IO.Types.FileSystem; +using System.Collections.Generic; +using System.IO; + +// Format names: Acroarts Resource +// Format references: Sonicteam::Spanverse::AckResource +// Format designers: Sonic Team +// Format researchers: Hyper, Rei-san + +namespace Marathon.Formats.Acroarts +{ + /// + /// Support for *.mab files; used for Acroarts event data. + /// + public class AckResource : FileBase + { + private const string _extension = ".mab"; // "My Acroarts Binary" (speculatory) + private const string _signature = "MRAB"; // "My Resource Acroarts Binary" (speculatory) + + public const uint Version = 2006020901; // 2006 February 9th, Revision 1 + + public AcroartsBinaryArchive Archive { get; set; } + + public override string Extension => _extension; + + public AckResource() { } + + public AckResource(string in_path) : base(in_path) { } + + public AckResource(Stream in_stream) : base(in_stream) { } + + public AckResource(IFile in_file) : base(in_file) { } + + public override void Read(Stream in_stream) + { + var mrabReader = new BinaryObjectReaderEx(in_stream, StreamOwnership.Retain, Endianness); + + mrabReader.CheckSignature(_signature); + + var binaStart = mrabReader.Read(); + var binaLength = mrabReader.Read(); + + var binaReader = new BINAReader(in_stream, binaStart); + + Endianness = binaReader.Endianness; + + var abdaStart = binaReader.Read(); + var abrsStart = binaReader.Read(); + + binaReader.JumpTo(binaReader.Offset + BINAHeader.Size + abdaStart); + + Archive = new AcroartsBinaryArchive(binaReader); + + binaReader.JumpTo(binaReader.Offset + BINAHeader.Size + abrsStart); + } + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryArchive.cs b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryArchive.cs new file mode 100644 index 00000000..86086ef0 --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryArchive.cs @@ -0,0 +1,69 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.IO.Types.BINA; +using System.Collections.Generic; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class AcroartsBinaryArchive : IChunk + { + public const string ID = "ABDA"; // "Acroarts Binary Archive" (speculatory) + + public List Data { get; set; } = new(); + + public AcroartsBinaryArchive() { } + + public AcroartsBinaryArchive(BINAReader in_reader) + { + Read(in_reader); + } + + public void Read(BINAReader in_reader) + { + var DefaultPosition = 0x50; + + var header = in_reader.ReadObject(); + /* + if (!header.ID.Equals(GetChunkID())) + throw new InvalidSignatureException(GetChunkID(), header.ID); + */ + + var version = in_reader.Read(); + + if (version != AckResource.Version) + throw new InvalidSignatureException(AckResource.Version, version); + + var abdtChunkCount = in_reader.Read(); + var abdaChunkLength = in_reader.Read(); + var unkField1 = in_reader.Read(); + + List abdtChunkOffsets = new(); + + for (int i = 0; i < abdtChunkCount; i++) + { + //64 bit pointer, 32 bit part only + var abdtChunkOffset = in_reader.Read(); + in_reader.Skip(4); + abdtChunkOffsets.Add(abdtChunkOffset); + } + + for (int i = 0; i < abdtChunkCount; i++) + { + var offsetBinaryData = abdtChunkOffsets[i] + DefaultPosition; + in_reader.JumpTo(offsetBinaryData); + Data.Add(new AcroartsBinaryData(in_reader)); + } + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + + public virtual string GetChunkID() + { + return ID; + } + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryBranch.cs b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryBranch.cs new file mode 100644 index 00000000..5e889098 --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryBranch.cs @@ -0,0 +1,41 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using System.Collections.Generic; +using System.Linq; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class AcroartsBinaryBranch : INode + { + public List Data { get; set; } + public List Leafs { get; set; } = new(); + public AcroartsBinaryBranch() { } + + public AcroartsBinaryBranch(BINAReader in_reader) + { + Read(in_reader); + } + + public void Read(BINAReader in_reader) + { + var DefaultOffset = 0x50; // Should Use Offset Of Binary Archive + Data = in_reader.ReadArray(0x44).ToList(); + var leafCount = in_reader.Read(); + var leafOffsets = in_reader.ReadArrayAtOffset(in_reader.Read() + DefaultOffset, (int)leafCount); + for (int i = 0; i < leafCount; i++) + { + var leafOffset = leafOffsets[i] + DefaultOffset; + in_reader.JumpTo(leafOffset); + Leafs.Add(new AcroartsBinaryLeaf(in_reader)); + } + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryData.cs b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryData.cs new file mode 100644 index 00000000..ab91b2c9 --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryData.cs @@ -0,0 +1,46 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using System; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class AcroartsBinaryData : IChunk + { + public const string ID = "ABDT"; // "Acroarts Binary Data" (speculatory) + + public AcroartsBinaryTrunk Trunk { get; set; } + + public AcroartsBinaryData() { } + + public AcroartsBinaryData(BINAReader in_reader) + { + Read(in_reader); + } + + public void Read(BINAReader in_reader) + { + var offset = in_reader.Position; + var header = in_reader.ReadObject(); + + //if (!header.ID.Equals(GetChunkID())) + //throw new InvalidSignatureException(GetChunkID(), header.ID); + + var rootOffset = (offset + header.HeaderSize); + in_reader.JumpTo(rootOffset); + Trunk = new AcroartsBinaryTrunk(in_reader); + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + + public virtual string GetChunkID() + { + return ID; + } + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeaf.cs b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeaf.cs new file mode 100644 index 00000000..731ef8d8 --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeaf.cs @@ -0,0 +1,173 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public enum SpkSpangleBase : uint + { + SpangleObject = 0, + SpangleCellSprite = 4, + SpangleCamera = 5, + SpangleScreen = 6, + SpangleLight = 7, + SpanglePrimitive = 8, + SpangleParticle = 9, + } + + public struct AcroartsBinaryLeafData : INode + { + public List ResourceIndices1; + public List ResourceIndices2; + public List ResourceIndices3; + public List ResourceIndices4; + + public AcroartsBinaryLeafData(BINAReader in_reader) + { + Read(in_reader); + } + + public void Read(BINAReader in_reader) + { + var Offset = in_reader.Position; + var DefaultOffset = 0x50; // Should Use Offset Of Binary Archive + + var ResourceCount = in_reader.Read(); + var ResourceIndicesOffset = in_reader.Read(); + var ResourceCount2 = in_reader.Read(); + var ResourceIndicesOffset2 = in_reader.Read(); + var ResourceCount3 = in_reader.Read(); + var ResourceIndicesOffset3 = in_reader.Read(); + var ResourceCount4 = in_reader.Read(); + var ResourceIndicesOffset4 = in_reader.Read(); + + ResourceIndices1 = in_reader.ReadArrayAtOffset(ResourceIndicesOffset + DefaultOffset, (int)ResourceCount).Select(offset => + { + uint index = 0xFFFFFFFF; + in_reader.ReadAtOffset(offset + DefaultOffset, () => + { + index = in_reader.Read(); + }); + return index; + }).ToList(); + + ResourceIndices2 = in_reader.ReadArrayAtOffset(ResourceIndicesOffset2 + DefaultOffset, (int)ResourceCount2).Select(offset => + { + uint index = 0xFFFFFFFF; + in_reader.ReadAtOffset(offset + DefaultOffset, () => + { + index = in_reader.Read(); + }); + return index; + }).ToList(); + + ResourceIndices3 = in_reader.ReadArrayAtOffset(ResourceIndicesOffset3 + DefaultOffset, (int)ResourceCount3).Select(offset => + { + uint index = 0xFFFFFFFF; + in_reader.ReadAtOffset(offset + DefaultOffset, () => + { + index = in_reader.Read(); + }); + return index; + }).ToList(); + + ResourceIndices4 = in_reader.ReadArrayAtOffset(ResourceIndicesOffset4 + DefaultOffset, (int)ResourceCount4).Select(offset => + { + uint index = 0xFFFFFFFF; + in_reader.ReadAtOffset(offset + DefaultOffset, () => + { + index = in_reader.Read(); + }); + return index; + }).ToList(); + } + + public void Write(BINAWriter in_writer) + { + throw new NotImplementedException(); + } + } + + public class AcroartsBinaryLeaf : INode + { + public List Data { get; set; } + public List Data2 { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public SpkSpangleBase Type { get; set; } + + [JsonIgnore] + public ISpangle Spangle { get; set; } + + public List MomRoots { get; set; } = new(); + + public AcroartsBinaryLeaf() { } + + public AcroartsBinaryLeaf(BINAReader in_reader) + { + Read(in_reader); + } + + public void Read(BINAReader in_reader) + { + var Offset = in_reader.Position; + var DefaultOffset = 0x50; // Should Use Offset Of Binary Archive + + var offset = in_reader.Length; + + Data = in_reader.ReadArray(0x84).ToList(); + Type = in_reader.Read(); + var leafData = new AcroartsBinaryLeafData(in_reader); + + switch (Type) + { + case SpkSpangleBase.SpangleObject: + Spangle = new SpangleObject(in_reader,leafData); + break; + case SpkSpangleBase.SpangleCellSprite: + Spangle = new SpangleCellSprite(in_reader, leafData); + break; + case SpkSpangleBase.SpangleCamera: + Spangle = new SpangleCamera(in_reader, leafData); + break; + case SpkSpangleBase.SpangleScreen: + Spangle = new SpangleScreen(in_reader, leafData); + break; + case SpkSpangleBase.SpangleLight: + Spangle = new SpangleLight(in_reader, leafData); + break; + case SpkSpangleBase.SpanglePrimitive: + Spangle = new SpanglePrimitive(in_reader, leafData); + break; + case SpkSpangleBase.SpangleParticle: + Spangle = new SpangleParticle(in_reader, leafData); + break; + default: + Logger.Warning($"UNKNOWN SpkSpangleType {Type} at {Offset:x}"); + break; + } + + Data2 = in_reader.ReadArray(0x10).ToList(); + var momCount = in_reader.Read(); + var momOffsets = in_reader.ReadArrayAtOffset(in_reader.Read() + DefaultOffset, (int)momCount); + for (int i = 0; i < momCount; i++) + { + var momOffset = momOffsets[i] + DefaultOffset; + in_reader.JumpTo(momOffset); + MomRoots.Add(new AcroartsBinaryLeafMomRoot(in_reader)); + } + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeafMomNode.cs b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeafMomNode.cs new file mode 100644 index 00000000..fddff95f --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeafMomNode.cs @@ -0,0 +1,181 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public enum MomBase : uint + { + _acmTranslateNormal = 0x0, + _acmTranslateAdd = 0x1, + _acmTranslateAccel = 0x2, + _acmTranslateRandomNormal = 0x3, + _acmTranslateRandomAdd = 0x4, + _acmTranslateRandomAccel = 0x5, + _acmTranslateSin = 0x6, + _acmTranslateRandomSin = 0x7, + _acmTranslateGoal = 0x8, + _acmTranslateSpline = 0x9, + _acmTranslateAddGoal = 0xA, + _acmTranslateRandomGoal = 0xB, + _acmTranslateWrap = 0x5C11, + _acmRotateNormal = 0x14, + _acmRotateAdd = 0x15, + _acmRotateAccel = 0x16, + _acmRotateRandomNormal = 0x17, + _acmRotateRandomAdd = 0x18, + _acmRotateRandomAccel = 0x19, + _acmRotateSin = 0x1A, + _acmRotateRandomSin = 0x1B, + _acmRotateGoal = 0x1C, + _acmRotateRandomGoal = 0x1D, + _acmRotateAddGoal = 0x1E, + _acmRotateErase = 0x1F, + _acmScaleAdd = 0x29, + _acmScaleAccel = 0x2A, + _acmScaleAddGoal = 0x32, + _acmScaleGoal = 0x30, + _acmScaleNormal = 0x28, + _acmScaleRandomAccel = 0x2D, + _acmScaleRandomAdd = 0x2C, + _acmScaleRandomGoal = 0x31, + _acmScaleRandomNormal = 0x2B, + _acmScaleRandomSin = 0x2F, + _acmScaleSin = 0x2E, + _acmPlaceFanShaped = 0x3C, + _acmPlaceLineShaped = 0x3D, + _acmPlaceSelectLocation = 0x3E, + _acmPlacePolygonShaped = 0x3F, + _acmCameraPosition = 0x1C2, + _acmCameraTarget = 0x1C3, + _acmCameraRoll = 0x1C4, + _acmTurnCamera = 0x96, + _acmCameraContactLens = 0x264, + _acmUvScrollByCameraDirection = 0x12C, + _acmPointLight = 0x17C, + _acmDirectionalLight = 0x17D, + _acmAmbientLight = 0x17E, + _acmDetachCoordinate = 0x25A, + _acmUvScrollSin = 0x12E, + _acmUvScrollNormal = 0x12D, + _acmUvSet = 0x12F, + _acmMaterialColorNormal = 0xC8, + _acmMaterialColorSin = 0xC9, + _acmMaterialColorRandomNormal = 0xCA, + _acmMaterialColorRandomSin = 0xCB, + _acmMaterialColorGoal = 0xCC, + _acmMaterialColorRandomGoal = 0xCD, + _acmMaterialColorSetByCamDist = 0xCE, + _acmMaterialColorSetByCamDire = 0xCF, + _acmMotionFrameSet = 0x1F6, + _acmMotionSet = 0x1F4, + _acmModelMotionSet = 0x1F5, + _acmParticleBillboardEx = 0x65, + _acmParticleBillboardPV = 0x67, + _acmParticleBillboardND = 0x6A, + _acmBlurBelt = 0x78, + _acmSparklingTail = 0x69, + _acmBillboardTail = 0x7A, + _acmTexScreen = 0x263, + _acmLineEx = 0x66, + _acmTextureSurfaceAnimation = 0xFA, + _acmTextureSurfaceAnimationEasy = 0xFD, + _acmTextureSet = 0x100, + _acmTexturePatternAnimation = 0xFB, + _acmTexturePatternAnimationEasy = 0xFE, + _acmTextureListAnimation = 0xFF, + _acmScreenPerspective = 0x226, + _acmTimeRate = 0x28A, + _acmThunder = 0x258, + _acmLensFlare = 0x265, + _acmSendParamAccel = 0x2BE, + _acmSendParamAdd = 0x2BD, + _acmSendParamAddGoal = 0x2C5, + _acmSendParamEqualSpace = 0x2C8, + _acmSendParamGoal = 0x2C4, + _acmSendParamNormal = 0x2BC, + _acmSendParamRandomAccel = 0x2C2, + _acmSendParamRandomAdd = 0x2C1, + _acmSendParamRandomGoal = 0x2C7, + _acmSendParamRandomNormal = 0x2C0, + _acmSendParamRandomSin = 0x2C3, + _acmSendParamSin = 0x2BF, + _acmSendParamSpline = 0x2C6, + _acmModelDrawMultitude = 0x269, + _acmSoundPlay = 0x320, //+ + _acmSound3DPlay = 0x321, + _acmParticlePlay = 0x32A, + _acmParticleSet = 0x32B, + _acmCellSpriteSceneSet = 0x334, + _acmSubtitle = 0x33E, + _acmClipPlane = 0x348, + _acmModelJoin = 0xE7, + _acmShadowOn = 0xE8, + _acmFilterClassicBlur = 0x352, + _acmFilterColorCorrection = 0x353, + _acmFilterDepthOfField = 0x354, + _acmSceneBloom = 0x366 + }; + + public class AcroartsBinaryLeafMomNode : INode + { + public List Data { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public MomBase Type { get; set; } + public List Data2 { get; set; } + public IACM ACM { get; set; } + + public AcroartsBinaryLeafMomNode() { } + + public AcroartsBinaryLeafMomNode(BINAReader in_reader) + { + Read(in_reader); + } + + public void Read(BINAReader in_reader) + { + var Position = in_reader.Position; + var DefaultOffset = 0x50; // Should Use Offset Of Binary Archive + + Data = in_reader.ReadArray(0x4).ToList(); + Type = in_reader.Read(); + Data2 = in_reader.ReadArray(0xC).ToList(); + var count = in_reader.Read(); // Count not always match, it usually depends on Type + var offset = in_reader.Read(); + in_reader.JumpTo(offset + DefaultOffset); + switch (Type) + { + case MomBase._acmParticlePlay: + ACM = new _acmParticlePlay(in_reader, count); + break; + case MomBase._acmBlurBelt: + ACM = new _acmBlurBelt(in_reader, count); + break; + case MomBase._acmRotateNormal: + ACM = new _acmRotateNormal(in_reader, count); + break; + case MomBase._acmPlaceFanShaped: + ACM = new _acmPlaceFanShaped(in_reader, count); + break; + default: + Logger.Warning($"{Type.ToString()} [Count {count}] not implemented yet"); + // Try (count * 4) + var ACM_DATA = in_reader.ReadArray((int)(count * 4)); + break; + } + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeafMomRoot.cs b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeafMomRoot.cs new file mode 100644 index 00000000..77c93bb3 --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryLeafMomRoot.cs @@ -0,0 +1,51 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class AcroartsBinaryLeafMomRoot : INode + { + public uint Index { get; set; } + public List Data { get; set; } + public List Data2 { get; set; } + + public List MomNodes { get; set; } = new(); + + public AcroartsBinaryLeafMomRoot() { } + + public AcroartsBinaryLeafMomRoot(BINAReader in_reader) + { + Read(in_reader); + } + + public void Read(BINAReader in_reader) + { + var DefaultOffset = 0x50; // Should Use Offset Of Binary Archive + + Data = in_reader.ReadArray(0x4).ToList(); + Index = in_reader.Read(); + Data2 = in_reader.ReadArray(0x10).ToList(); + + var momNodeCount = in_reader.Read(); + var momNodeOffsets = in_reader.ReadArrayAtOffset(in_reader.Read() + DefaultOffset, (int)momNodeCount); + for (int i = 0; i < momNodeCount; i++) + { + var momNodeOffset = momNodeOffsets[i] + DefaultOffset; + in_reader.JumpTo(momNodeOffset); + MomNodes.Add(new AcroartsBinaryLeafMomNode(in_reader)); + } + + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryTrunk.cs b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryTrunk.cs new file mode 100644 index 00000000..c1a0ca83 --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/AcroartsBinaryTrunk.cs @@ -0,0 +1,49 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class AcroartsBinaryTrunk : INode + { + public uint Version { get; set; } + public List Data { get; set; } + public List Branches { get; set; } = new(); + public AcroartsBinaryTrunk() { } + + public AcroartsBinaryTrunk(BINAReader in_reader) + { + Read(in_reader); + } + + public void Read(BINAReader in_reader) + { + var DefaultOffset = 0x50; // Should Use Offset Of Binary Archive + Version = in_reader.Read(); + + if (Version != AckResource.Version) + throw new InvalidSignatureException(AckResource.Version, Version); + + Data = in_reader.ReadArray(0x20).ToList(); + + var branchCount = in_reader.Read(); + var branchOffsets = in_reader.ReadArrayAtOffset(in_reader.Read() + DefaultOffset, (int)branchCount); + for (int i = 0; i < branchCount; i++) + { + var branchOffset = branchOffsets[i] + DefaultOffset; + in_reader.JumpTo(branchOffset); + Branches.Add(new AcroartsBinaryBranch(in_reader)); + } + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/ChunkFactory.cs b/Marathon/Formats/Acroarts/Chunks/ChunkFactory.cs new file mode 100644 index 00000000..84aa76e5 --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/ChunkFactory.cs @@ -0,0 +1,18 @@ +using Marathon.IO.Types; +using Marathon.IO.Types.BINA; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class ChunkFactory + { + public static IChunk GetChunkByFourCC(BINAReader in_reader, FourCC in_signature) + { + return in_signature.ToString() switch + { + AcroartsBinaryArchive.ID => new AcroartsBinaryArchive(in_reader), + EndOfChunk.ID => new EndOfChunk(in_reader), + _ => new UndefinedChunk(in_reader) + }; + } + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/EndOfChunk.cs b/Marathon/Formats/Acroarts/Chunks/EndOfChunk.cs new file mode 100644 index 00000000..c9feb583 --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/EndOfChunk.cs @@ -0,0 +1,38 @@ +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.IO.Types.BINA; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class EndOfChunk : IChunk + { + public const string ID = "EOFC"; // "End OF Chunk" + + public EndOfChunk() { } + + public EndOfChunk(BINAReader in_reader) + { + Read(in_reader); + } + + public void Read(BINAReader in_reader) + { + var header = in_reader.ReadObject(); + + if (header.ID.Equals(GetChunkID())) + return; + + throw new InvalidSignatureException(GetChunkID(), header.ID); + } + + public void Write(BINAWriter in_writer) + { + new ChunkHeader(in_writer, GetChunkID()).FinishWrite(in_writer); + } + + public virtual string GetChunkID() + { + return ID; + } + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/IChunk.cs b/Marathon/Formats/Acroarts/Chunks/IChunk.cs new file mode 100644 index 00000000..620a5f0d --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/IChunk.cs @@ -0,0 +1,16 @@ +using Marathon.IO.Types.BINA; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public interface IChunk + { + void Read(BINAReader in_reader); + + void Write(BINAWriter in_writer); + + virtual string GetChunkID() + { + return string.Empty; + } + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/INode.cs b/Marathon/Formats/Acroarts/Chunks/INode.cs new file mode 100644 index 00000000..d03852f1 --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/INode.cs @@ -0,0 +1,11 @@ +using Marathon.IO.Types.BINA; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public interface INode + { + void Read(BINAReader in_reader); + + void Write(BINAWriter in_writer); + } +} diff --git a/Marathon/Formats/Acroarts/Chunks/UndefinedChunk.cs b/Marathon/Formats/Acroarts/Chunks/UndefinedChunk.cs new file mode 100644 index 00000000..e67dd103 --- /dev/null +++ b/Marathon/Formats/Acroarts/Chunks/UndefinedChunk.cs @@ -0,0 +1,46 @@ +using Marathon.Helpers; +using Marathon.IO.Extensions; +using Marathon.IO.Types; +using Marathon.IO.Types.BINA; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class UndefinedChunk : IChunk + { + public string ChunkID { get; set; } + + public FourCC Signature { get; set; } + + public byte[] Data { get; set; } + + public UndefinedChunk() { } + + public UndefinedChunk(BINAReader in_reader) + { + Read(in_reader); + + ChunkID = Signature.ToString(); + } + + public void Read(BINAReader in_reader) + { + var pos = in_reader.Position; + + Signature = in_reader.ReadObject(); + + var chunkLength = in_reader.Read(); + + Data = in_reader.ReadBytes((int)chunkLength); + + Logger.Warning($"Encountered undefined chunk at 0x{pos:X8}: {Signature}"); + } + + public void Write(BINAWriter in_writer) + { + in_writer.WriteObject(Signature); + var length = in_writer.Reserve(); + in_writer.WriteBytes(Data); + in_writer.WriteReserved(length, (int)(in_writer.Position - (length + 4))); + } + } +} diff --git a/Marathon/Formats/Acroarts/Spangles/ISpangle.cs b/Marathon/Formats/Acroarts/Spangles/ISpangle.cs new file mode 100644 index 00000000..7ef10150 --- /dev/null +++ b/Marathon/Formats/Acroarts/Spangles/ISpangle.cs @@ -0,0 +1,16 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using System.Collections.Generic; +using System.Linq; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public interface ISpangle + { + public void Read(BINAReader in_reader, AcroartsBinaryLeafData leafData); + public void Write(BINAWriter in_writer); + } +} diff --git a/Marathon/Formats/Acroarts/Spangles/SpangleCamera.cs b/Marathon/Formats/Acroarts/Spangles/SpangleCamera.cs new file mode 100644 index 00000000..236d6255 --- /dev/null +++ b/Marathon/Formats/Acroarts/Spangles/SpangleCamera.cs @@ -0,0 +1,31 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using System.Collections.Generic; +using System.Linq; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class SpangleCamera : ISpangle + { + public SpangleCamera() { } + + public SpangleCamera(BINAReader in_reader, AcroartsBinaryLeafData leafData) + { + Read(in_reader, leafData); + } + + public void Read(BINAReader in_reader, AcroartsBinaryLeafData leafData) + { + var DefaultPosition = 0x50; + var Position = in_reader.Position; + } + + public void Write(BINAWriter in_writer) + { + // TODO + } + } +} diff --git a/Marathon/Formats/Acroarts/Spangles/SpangleCellSprite.cs b/Marathon/Formats/Acroarts/Spangles/SpangleCellSprite.cs new file mode 100644 index 00000000..9e9a6e58 --- /dev/null +++ b/Marathon/Formats/Acroarts/Spangles/SpangleCellSprite.cs @@ -0,0 +1,36 @@ +using Amicitia.IO.Binary; +using Marathon.Exceptions; +using Marathon.Formats.Acroarts.Types; +using Marathon.Helpers; +using Marathon.IO.Types.BINA; +using System.Collections.Generic; +using System.Linq; + +namespace Marathon.Formats.Acroarts.Chunks +{ + public class SpangleCellSprite : ISpangle + { + public SpangleCellSprite() { } + + public SpangleCellSprite(BINAReader in_reader, AcroartsBinaryLeafData leafData) + { + Read(in_reader, leafData); + } + + public void Read(BINAReader in_reader, AcroartsBinaryLeafData leafData) + { + var DefaultPosition = 0x50; + var Position = in_reader.Position; + + if (leafData.ResourceIndices2.Count > 0 && leafData.ResourceIndices2.Count < 2) + { + Logger.Warning($"SpangleCellSprite leafData.ResourceIndices2.Count < 2, should be in range >1(); + Length = in_reader.Read(); + HeaderSize = in_reader.Read(); + _chunkflags = in_reader.Read(); + } + + public void Write(BinaryObjectWriter in_writer) + { + in_writer.WriteObject(ID); + in_writer.Write(Length); + in_writer.Write(HeaderSize); + in_writer.Write(_chunkflags); + } + + public void Reserve(BinaryObjectWriterEx in_writer) + { + in_writer.Write(ID); + _lengthOffset = in_writer.Reserve(true); + _headerSize = in_writer.Reserve(true); + _chunkflags = in_writer.Reserve(); + _chunkStart = (uint)in_writer.Position; + } + + public void FinishWrite(BinaryObjectWriterEx in_writer) + { + in_writer.WriteReserved(_lengthOffset, Length); + in_writer.WriteReserved(_headerSize, HeaderSize); + } + + public void FinishWrite(BinaryObjectWriterEx in_writer, uint in_headerSize = _defaultHeaderSize) + { + Length = (uint)(in_writer.Position - _chunkStart); + HeaderSize = in_headerSize; + + FinishWrite(in_writer); + } + + public uint GetChunkStart() + { + return _chunkStart; + } + } +}