diff --git a/InfectedRose.Core/LengthToken.cs b/InfectedRose.Core/LengthToken.cs new file mode 100644 index 0000000..c174951 --- /dev/null +++ b/InfectedRose.Core/LengthToken.cs @@ -0,0 +1,44 @@ +using System; +using RakDotNet.IO; + +namespace InfectedRose.Core +{ + public class LengthToken : Token + { + public long Point { get; set; } + + public LengthToken(BitWriter writer) : base(writer.BaseStream) + { + } + + public void Allocate() + { + Point = Stream.Position; + + Stream.Position += 4; + } + + protected override void Construct() + { + if (Point <= 0) + { + throw new InvalidOperationException($"{nameof(Point)} cannot be negative or 0"); + } + + var position = Stream.Position; + + var length = (int) Math.Abs(Reference - position); + + var bytes = BitConverter.GetBytes(length); + + Stream.Position = Point; + + for (var i = 0; i < 4; i++) + { + Stream.WriteByte(bytes[i]); + } + + Stream.Position = position; + } + } +} \ No newline at end of file diff --git a/InfectedRose.Core/PointerToken.cs b/InfectedRose.Core/PointerToken.cs new file mode 100644 index 0000000..c72b298 --- /dev/null +++ b/InfectedRose.Core/PointerToken.cs @@ -0,0 +1,31 @@ +using System; +using RakDotNet.IO; + +namespace InfectedRose.Core +{ + public class PointerToken : Token + { + public bool Zero { get; set; } + + public PointerToken(BitWriter writer) : base(writer.BaseStream) + { + Stream.Position += 4; + } + + protected override void Construct() + { + var position = Stream.Position; + + var bytes = BitConverter.GetBytes(Zero ? 0 : (int) Stream.Position); + + Stream.Position = Reference; + + for (var i = 0; i < 4; i++) + { + Stream.WriteByte(bytes[i]); + } + + Stream.Position = position; + } + } +} \ No newline at end of file diff --git a/InfectedRose.Core/Token.cs b/InfectedRose.Core/Token.cs new file mode 100644 index 0000000..096e7f9 --- /dev/null +++ b/InfectedRose.Core/Token.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; + +namespace InfectedRose.Core +{ + public abstract class Token : IDisposable + { + private bool Disposed { get; set; } + + public Stream Stream { get; } + + public long Reference { get; } + + public Token(Stream stream) + { + Stream = stream; + + Reference = stream.Position; + } + + protected abstract void Construct(); + + public void Dispose() + { + if (Disposed) return; + + Disposed = true; + + Construct(); + } + } +} \ No newline at end of file diff --git a/InfectedRose.Luz/LuzCameraPath.cs b/InfectedRose.Luz/LuzCameraPath.cs index e1ff470..db8b9d7 100644 --- a/InfectedRose.Luz/LuzCameraPath.cs +++ b/InfectedRose.Luz/LuzCameraPath.cs @@ -1,4 +1,3 @@ -using System; using InfectedRose.Core; using RakDotNet.IO; diff --git a/InfectedRose.Lvl/LevelInfo.cs b/InfectedRose.Lvl/LevelInfo.cs index ffadbdc..0fe915d 100644 --- a/InfectedRose.Lvl/LevelInfo.cs +++ b/InfectedRose.Lvl/LevelInfo.cs @@ -1,3 +1,4 @@ +using InfectedRose.Core; using RakDotNet.IO; namespace InfectedRose.Lvl @@ -13,6 +14,12 @@ namespace InfectedRose.Lvl public uint AddressChunk2001 { get; set; } public uint AddressChunk2002 { get; set; } + + public PointerToken SkyBoxPointer { get; set; } + + public PointerToken EnvironmentPointer { get; set; } + + public PointerToken ObjectsPointer { get; set; } public override uint ChunkType => 1000; @@ -22,11 +29,11 @@ namespace InfectedRose.Lvl writer.Write(RevisionNumber); - writer.Write(AddressChunk2000); + SkyBoxPointer = new PointerToken(writer); - writer.Write(AddressChunk2001); + ObjectsPointer = new PointerToken(writer); - writer.Write(AddressChunk2002); + EnvironmentPointer = new PointerToken(writer); } public override void Deserialize(BitReader reader) diff --git a/InfectedRose.Lvl/LevelObjectTemplate.cs b/InfectedRose.Lvl/LevelObjectTemplate.cs index 5bf962e..e3c7976 100644 --- a/InfectedRose.Lvl/LevelObjectTemplate.cs +++ b/InfectedRose.Lvl/LevelObjectTemplate.cs @@ -22,9 +22,15 @@ namespace InfectedRose.Lvl public float Scale { get; set; } - public LegoDataDictionary LegoInfo { get; set; } + public string ExtraInfo { get; set; } public uint LvlVersion { get; set; } + + public LegoDataDictionary LegoData + { + get => LegoDataDictionary.FromString(ExtraInfo); + set => ExtraInfo = value.ToString("\n"); + } public LevelObjectTemplate(uint lvlVersion = 0x26) { @@ -49,10 +55,10 @@ namespace InfectedRose.Lvl writer.Write(Scale); - writer.WriteNiString(LegoInfo.ToString("\n"), true); + writer.WriteNiString(ExtraInfo, true); if (LvlVersion >= 0x7) - writer.Write(0u); + writer.Write(UnknownInt1); } public void Deserialize(BitReader reader) @@ -73,13 +79,8 @@ namespace InfectedRose.Lvl Scale = reader.Read(); - var legoInfo = reader.ReadNiString(true); - - if (legoInfo.Length > 0) - { - LegoInfo = LegoDataDictionary.FromString(legoInfo); - } - + ExtraInfo = reader.ReadNiString(true); + if (LvlVersion >= 0x7) UnknownInt1 = reader.Read(); } diff --git a/InfectedRose.Lvl/LevelSkyConfig.cs b/InfectedRose.Lvl/LevelSkyConfig.cs index bcde1ff..be794f6 100644 --- a/InfectedRose.Lvl/LevelSkyConfig.cs +++ b/InfectedRose.Lvl/LevelSkyConfig.cs @@ -22,13 +22,16 @@ namespace InfectedRose.Lvl public override void Serialize(BitWriter writer) { - writer.Write(8); + writer.Write((uint) (8 + UnknownFloatArray0.Length * 4)); - // TODO: Add UnknownFloatArray0? - - var addressPosition = writer.BaseStream.Position; + var skySectionPointer = new PointerToken(writer); - writer.BaseStream.Position += 8; + var otherSectionPointer = new PointerToken(writer); + + foreach (var f in UnknownFloatArray0) + { + writer.Write(f); + } writer.Write((uint) Identifiers.Length); @@ -47,21 +50,13 @@ namespace InfectedRose.Lvl writer.Write(UnknownFloatArray2[i]); } - var skySectionAddress = writer.BaseStream.Position; - + skySectionPointer.Dispose(); + writer.Write(WriteSkySection()); - var otherSectionAddress = writer.BaseStream.Position; + otherSectionPointer.Dispose(); writer.Write(WriteOtherSection()); - - writer.BaseStream.Position = addressPosition; - - writer.Write((uint) skySectionAddress); - - writer.Write((uint) otherSectionAddress); - - writer.BaseStream.Position = otherSectionAddress + 4; } private byte[] WriteSkySection() @@ -80,9 +75,11 @@ namespace InfectedRose.Lvl private byte[] WriteOtherSection() { using var stream = new MemoryStream(); - using var writer = new BinaryWriter(stream); + using var writer = new BitWriter(stream); - writer.Write(0u); + writer.Write((uint) UnknownSectionData.Length); + + writer.Write(UnknownSectionData); return stream.ToArray(); } diff --git a/InfectedRose.Lvl/LvlFile.cs b/InfectedRose.Lvl/LvlFile.cs index 676db7b..d57c655 100644 --- a/InfectedRose.Lvl/LvlFile.cs +++ b/InfectedRose.Lvl/LvlFile.cs @@ -26,7 +26,11 @@ namespace InfectedRose.Lvl public void Serialize(BitWriter writer) { if (OldLevelHeader == default) - throw new NotSupportedException("Writing new level files is not yet supported."); + { + SerializeNew(writer); + + return; + } OldLevelHeader.Serialize(writer); @@ -38,6 +42,64 @@ namespace InfectedRose.Lvl writer.Write(0); } + private void SerializeNew(BitWriter writer) + { + SerializeChunk(writer, LevelInfo); + SerializeChunk(writer, LevelSkyConfig); + SerializeChunk(writer, LevelObjects); + SerializeChunk(writer, LevelEnvironmentConfig); + + LevelInfo.EnvironmentPointer.Dispose(); + LevelInfo.ObjectsPointer.Dispose(); + LevelInfo.SkyBoxPointer.Dispose(); + } + + private void SerializeChunk(BitWriter writer, ChunkBase chunkBase) + { + if (chunkBase == default) return; + + using (var token = new LengthToken(writer)) + { + switch (chunkBase) + { + case LevelEnvironmentConfig _: + LevelInfo.EnvironmentPointer.Dispose(); + break; + case LevelObjects _: + LevelInfo.ObjectsPointer.Dispose(); + break; + case LevelSkyConfig _: + LevelInfo.SkyBoxPointer.Dispose(); + break; + } + + writer.Write(ChunkHeader); + + writer.Write(chunkBase.ChunkType); + + writer.Write(1); + + writer.Write(chunkBase.Index); + + token.Allocate(); + + using (new PointerToken(writer)) + { + while (writer.BaseStream.Position % 16 != 0) + { + writer.Write(0); + } + } + + chunkBase.Serialize(writer); + + while (writer.BaseStream.Position % 16 != 0) + { + writer.Write(0); + } + } + } + public void Deserialize(BitReader reader) { var magic = new string(reader.ReadBuffer(4).Select(s => (char) s).ToArray()); @@ -134,11 +196,6 @@ namespace InfectedRose.Lvl throw new ArgumentOutOfRangeException($"{chunkType} is not a valid chunk type."); } - while (reader.BaseStream.Position != startPosition + chunkLength) - { - reader.Read(); - } - reader.BaseStream.Position = startPosition + chunkLength; } }