diff --git a/README.md b/README.md index dd96cdad7..8ce3e8236 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ Coming soon! - 0.0.1 - 0.9.0: System.Drawing.Common - 0.13.0+: TmEssentials - 0.15.0+: Microsoft.Extensions.Logging.Abstractions +- 1.2.4+: System.IO.Hashing ### GBX.NET.Imaging - System.Drawing.Common diff --git a/Src/GBX.NET/Engines/Game/CGameCtnChallenge.cs b/Src/GBX.NET/Engines/Game/CGameCtnChallenge.cs index 6cea6ff05..b76d9b8ad 100644 --- a/Src/GBX.NET/Engines/Game/CGameCtnChallenge.cs +++ b/Src/GBX.NET/Engines/Game/CGameCtnChallenge.cs @@ -1,10 +1,9 @@ -using System; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.IO.Compression; +using System.IO.Hashing; using System.Security; using System.Text; using GBX.NET.BlockInfo; -using GBX.NET.Engines.Plug; namespace GBX.NET.Engines.Game; @@ -3344,7 +3343,47 @@ public class Chunk03043029 : SkippableChunk public override void ReadWrite(CGameCtnChallenge n, GameBoxReaderWriter rw) { rw.Bytes(ref n.hashedPassword, 16); - rw.UInt32(ref n.crc32); + + if (rw.Reader is not null) + { + n.crc32 = rw.Reader.ReadUInt32(); + } + + if (rw.Writer is not null) + { + var toHash = n.hashedPassword is null + ? $"0x00000000000000000000000000000000???{n.MapUid}" + : $"0x{ToHex(n.hashedPassword).ToString()}???{n.MapUid}"; + var crc32 = Crc32.HashToUInt32(Encoding.ASCII.GetBytes(toHash)); + + rw.Writer.Write(crc32); + } + } + + private static Span ToHex(byte[] value) + { + var str = new char[value.Length * 2]; + + for (var i = 0; i < value.Length; i++) + { + var hex1 = HexIntToChar((byte)(value[value.Length - 1 - i] % 16)); + var hex2 = HexIntToChar((byte)(value[value.Length - 1 - i] / 16)); + + str[i * 2 + 1] = hex1; + str[i * 2] = hex2; + } + + return str; + } + + private static char HexIntToChar(byte v) + { + if (v < 10) + { + return (char)(v + 48); + } + + return (char)(v + 55); } } @@ -5118,27 +5157,21 @@ public override void Write(CGameCtnChallenge n, GameBoxWriter w) #endregion - #region 0x06B skippable chunk [TM2020] + #region 0x06B skippable chunk (light settings 2) [TM2020] /// - /// CGameCtnChallenge 0x06B skippable chunk [TM2020] + /// CGameCtnChallenge 0x06B skippable chunk (light settings 2) [TM2020] /// - [Chunk(0x0304306B)] + [Chunk(0x0304306B, "light settings 2")] public class Chunk0304306B : SkippableChunk { - public int U01; - public int U02; - public int U03; - public int U04; - public int U05; - public override void ReadWrite(CGameCtnChallenge n, GameBoxReaderWriter rw) { - rw.Int32(ref U01); - rw.Int32(ref U02); - rw.Int32(ref U03); - rw.Int32(ref U04); - rw.Int32(ref U05); + rw.Int32(0); + rw.TimeOfDay(ref n.dayTime); + rw.Int32(0); + rw.Boolean(ref n.dynamicDaylight); + rw.TimeInt32Nullable(ref n.dayDuration); } } diff --git a/Src/GBX.NET/Engines/Game/CGameCtnGhost.PlayerInputData.cs b/Src/GBX.NET/Engines/Game/CGameCtnGhost.PlayerInputData.cs index 37db3c46b..5b68e8931 100644 --- a/Src/GBX.NET/Engines/Game/CGameCtnGhost.PlayerInputData.cs +++ b/Src/GBX.NET/Engines/Game/CGameCtnGhost.PlayerInputData.cs @@ -28,6 +28,7 @@ private enum EStart NotStarted, Character, Vehicle, + VehicleMix } private EVersion version; // 8 in shootmania, 12 in tm2020 @@ -122,6 +123,7 @@ public void ReadWrite(GameBoxReaderWriter rw, int version = 0) rw.EnumInt32(ref this.version); // 8 in shootmania, 12 in tm2020 rw.Int32(ref u02); + // version from the method parameter (the chunk version), NOT this.version if (version >= 4) { rw.TimeInt32Nullable(ref startOffset); @@ -427,7 +429,12 @@ internal IEnumerable ProcessTrackmaniaInputs() if (started is EStart.NotStarted) { - started = (EStart)(states & 3); + started = (EStart)(states & 3); + + if (started is EStart.VehicleMix) + { + started = EStart.Vehicle; + } } if (started is EStart.Character) @@ -720,7 +727,13 @@ internal IEnumerable ProcessTrackmaniaInputChanges() if (started is EStart.NotStarted) { - started = (EStart)(states & 3); + started = (EStart)(states & 3); + + if (started is EStart.VehicleMix) + { + started = EStart.Vehicle; + } + horn = (states & 64) != 0; // a weird bit that can appear sometimes during the run too } else if (started is EStart.Vehicle) diff --git a/Src/GBX.NET/Engines/Game/CGameCtnGhost.cs b/Src/GBX.NET/Engines/Game/CGameCtnGhost.cs index bffe06283..5f2fd8228 100644 --- a/Src/GBX.NET/Engines/Game/CGameCtnGhost.cs +++ b/Src/GBX.NET/Engines/Game/CGameCtnGhost.cs @@ -489,7 +489,7 @@ public class Chunk03092000 : SkippableChunk, IVersionable public bool U03; public int[]? U04; - public int? U05; + public int U05; public int? U06; public int Version { get => version; set => version = value; } @@ -539,7 +539,7 @@ public override void ReadWrite(CGameCtnGhost n, GameBoxReaderWriter rw) if (Version >= 5) { - if (Version >= 9) + if (Version >= 9) // Not in code? { rw.Int32(ref U06); } diff --git a/Src/GBX.NET/Engines/GameData/CGameCommonItemEntityModel.cs b/Src/GBX.NET/Engines/GameData/CGameCommonItemEntityModel.cs index 6af818a7d..35bb73988 100644 --- a/Src/GBX.NET/Engines/GameData/CGameCommonItemEntityModel.cs +++ b/Src/GBX.NET/Engines/GameData/CGameCommonItemEntityModel.cs @@ -7,6 +7,7 @@ public class CGameCommonItemEntityModel : CMwNod private CMwNod? phyModel; private CMwNod? visModel; private CMwNod? staticObject; + private CPlugSurface? triggerShape; [NodeMember(ExactlyNamed = true)] [AppliedWithChunk] @@ -20,6 +21,10 @@ public class CGameCommonItemEntityModel : CMwNod [AppliedWithChunk(sinceVersion: 4)] public CMwNod? StaticObject { get => staticObject; set => staticObject = value; } + [NodeMember(ExactlyNamed = true)] + [AppliedWithChunk(sinceVersion: 4)] + public CPlugSurface? TriggerShape { get => triggerShape; set => triggerShape = value; } + internal CGameCommonItemEntityModel() { @@ -35,20 +40,67 @@ public class Chunk2E027000 : Chunk, IVersionable { private int version; + public string? U01; + public string? U02; + public Iso4? U03; + public CPlugParticleEmitterModel? U04; + public CGameActionModel?[]? U05; + public Node? U06; + public string? U07; + public string? U08; + public string? U09; + public string? U10; + public string? U11; + public Iso4? U12; + public int? U13; + public byte? U14; + public int Version { get => version; set => version = value; } public override void ReadWrite(CGameCommonItemEntityModel n, GameBoxReaderWriter rw) { rw.Int32(ref version); - if (version >= 4) + if (version == 0) + { + rw.NodeRef(ref n.phyModel); + rw.NodeRef(ref n.visModel); + } + else if (version == 3) + { + rw.String(ref U01); + rw.String(ref U02); + } + else { rw.NodeRef(ref n.staticObject); - return; } - rw.NodeRef(ref n.phyModel); - rw.NodeRef(ref n.visModel); + if (version >= 2) + { + rw.NodeRef(ref n.triggerShape); + rw.Iso4(ref U03); + rw.NodeRef(ref U04); + rw.ArrayNode(ref U05); + + if (version < 6) + { + rw.NodeRef(ref U06); + } + + rw.String(ref U07); + rw.String(ref U08); + rw.String(ref U09); + rw.String(ref U10); + rw.String(ref U11); + rw.Iso4(ref U12); + rw.Int32(ref U13); // ExprValidator + + if (version >= 5) + { + rw.Byte(ref U14); + } + } } } diff --git a/Src/GBX.NET/Engines/GameData/CGameItemPlacementParam.cs b/Src/GBX.NET/Engines/GameData/CGameItemPlacementParam.cs index a65c57388..9cc460b8f 100644 --- a/Src/GBX.NET/Engines/GameData/CGameItemPlacementParam.cs +++ b/Src/GBX.NET/Engines/GameData/CGameItemPlacementParam.cs @@ -361,5 +361,18 @@ public class Chunk2E020004 : SkippableChunk #endregion + #region 0x005 skippable chunk + + /// + /// CGameItemPlacementParam 0x005 skippable chunk + /// + [Chunk(0x2E020005), IgnoreChunk] + public class Chunk2E020005 : SkippableChunk + { + // NPlugItemPlacement_SClass noderef + } + + #endregion + #endregion } diff --git a/Src/GBX.NET/Engines/Plug/CPlugStaticObjectModel.cs b/Src/GBX.NET/Engines/Plug/CPlugStaticObjectModel.cs index 83f1039cd..76db5e966 100644 --- a/Src/GBX.NET/Engines/Plug/CPlugStaticObjectModel.cs +++ b/Src/GBX.NET/Engines/Plug/CPlugStaticObjectModel.cs @@ -4,24 +4,17 @@ [Node(0x09159000)] public class CPlugStaticObjectModel : CMwNod { + [NodeMember] public int U01 { get; set; } [NodeMember(ExactlyNamed = true)] public CPlugSolid2Model? Mesh { get; set; } - - public byte U02 { get; set; } - public int U03 { get; set; } - public Iso4 U04 { get; set; } - public int U05 { get; set; } - public int U06 { get; set; } - public int U07 { get; set; } - public int U08 { get; set; } - public int U09 { get; set; } - public int U10 { get; set; } - public int U11 { get; set; } - public int U12 { get; set; } - public Iso4 U13 { get; set; } - public int U14 { get; set; } + + [NodeMember] + public bool U02 { get; set; } + + [NodeMember(ExactlyNamed = true)] + public CPlugSurface? Shape { get; set; } internal CPlugStaticObjectModel() { @@ -32,19 +25,12 @@ protected override void ReadChunkData(GameBoxReader r, IProgress(); - U02 = r.ReadByte(); - U03 = r.ReadInt32(); - U04 = r.ReadIso4(); - U05 = r.ReadInt32(); - U06 = r.ReadInt32(); - U07 = r.ReadInt32(); - U08 = r.ReadInt32(); - U09 = r.ReadInt32(); - U10 = r.ReadInt32(); - U11 = r.ReadInt32(); - U12 = r.ReadInt32(); - U13 = r.ReadIso4(); - U14 = r.ReadInt32(); + U02 = r.ReadBoolean(asByte: true); + + if (!U02) + { + Shape = r.ReadNodeRef(); + } } protected override Task ReadChunkDataAsync(GameBoxReader r, CancellationToken cancellationToken) @@ -57,19 +43,12 @@ protected override void WriteChunkData(GameBoxWriter w) { w.Write(U01); w.Write(Mesh); - w.Write(U02); - w.Write(U03); - w.Write(U04); - w.Write(U05); - w.Write(U06); - w.Write(U07); - w.Write(U08); - w.Write(U09); - w.Write(U10); - w.Write(U11); - w.Write(U12); - w.Write(U13); - w.Write(U14); + w.Write(U02, asByte: true); + + if (!U02) + { + w.Write(Shape); + } } protected override Task WriteChunkDataAsync(GameBoxWriter w, CancellationToken cancellationToken) diff --git a/Src/GBX.NET/Engines/Plug/CPlugSurface.cs b/Src/GBX.NET/Engines/Plug/CPlugSurface.cs index d5d277c9d..ca2f439f2 100644 --- a/Src/GBX.NET/Engines/Plug/CPlugSurface.cs +++ b/Src/GBX.NET/Engines/Plug/CPlugSurface.cs @@ -153,7 +153,7 @@ public class Chunk0900C003 : Chunk, IVersionable public int U01; public byte[]? U02; - public int U06; + public byte U03; public int Version { get => version; set => version = value; } @@ -169,9 +169,14 @@ public override void ReadWrite(CPlugSurface n, GameBoxReaderWriter rw) ArchiveSurf(ref n.surf, rw, n.surfVersion, version); rw.ArrayArchiveWithGbx(ref n.materials); // ArchiveMaterials - + rw.Bytes(ref U02); + if (version >= 4) + { + rw.Byte(ref U03); + } + if (version >= 1) { rw.NodeRef(ref n.skel); diff --git a/Src/GBX.NET/Engines/Plug/CPlugTree.cs b/Src/GBX.NET/Engines/Plug/CPlugTree.cs index 4ee91365d..326e96d4b 100644 --- a/Src/GBX.NET/Engines/Plug/CPlugTree.cs +++ b/Src/GBX.NET/Engines/Plug/CPlugTree.cs @@ -204,6 +204,24 @@ public override async Task ReadWriteAsync(CPlugTree n, GameBoxReaderWriter rw, C #endregion + #region 0x017 chunk + + /// + /// CPlugTree 0x017 chunk + /// + [Chunk(0x0904F017)] + public class Chunk0904F017 : Chunk + { + public CPlugVisual? U01; + + public override void ReadWrite(CPlugTree n, GameBoxReaderWriter rw) + { + rw.NodeRef(ref U01); + } + } + + #endregion + #region 0x018 chunk (translation) /// diff --git a/Src/GBX.NET/GBX.NET.csproj b/Src/GBX.NET/GBX.NET.csproj index 504462ce7..f6503fdbc 100644 --- a/Src/GBX.NET/GBX.NET.csproj +++ b/Src/GBX.NET/GBX.NET.csproj @@ -12,7 +12,7 @@ enable true - 1.2.3 + 1.2.4 net8.0;net7.0;net6.0;netstandard2.1;netstandard2.0;net462 @@ -77,6 +77,7 @@ + diff --git a/Tests/GBX.NET.Tests/Integration/Engines/Game/CGameCtnChallengeTests.cs b/Tests/GBX.NET.Tests/Integration/Engines/Game/CGameCtnChallengeTests.cs index 5ddb96fe2..bf6e72f49 100644 --- a/Tests/GBX.NET.Tests/Integration/Engines/Game/CGameCtnChallengeTests.cs +++ b/Tests/GBX.NET.Tests/Integration/Engines/Game/CGameCtnChallengeTests.cs @@ -38,6 +38,12 @@ public partial class Chunk03043021Tests public partial void ReadAndWrite_TMUF_DataShouldEqual(); } + [IgnoreReadWriteEqualityTest] + public partial class Chunk03043029Tests + { + + } + public partial class Chunk0304303ETests { [IgnoreReadWriteEqualityTest]