internal static void Parse <T>(T node, GameBoxReader r, IProgress <GameBoxReadProgress>?progress = null) where T : CMwNod { var stopwatch = Stopwatch.StartNew(); node.GBX = r.Body?.GBX; var type = node.GetType(); uint?previousChunkId = null; while (!r.BaseStream.CanSeek || r.BaseStream.Position < r.BaseStream.Length) { if (r.BaseStream.CanSeek && r.BaseStream.Position + 4 > r.BaseStream.Length) { Debug.WriteLine($"Unexpected end of the stream: {r.BaseStream.Position.ToString()}/{r.BaseStream.Length.ToString()}"); var bytes = r.ReadBytes((int)(r.BaseStream.Length - r.BaseStream.Position)); break; } var chunkId = r.ReadUInt32(); if (chunkId == 0xFACADE01) // no more chunks { break; } var logChunk = new StringBuilder("[") .Append(node.ClassName) .Append("] 0x") .Append(chunkId.ToString("X8")); if (r.BaseStream.CanSeek) // Decompressed body can always seek { logChunk.Append(" (") .Append(((float)r.BaseStream.Position / r.BaseStream.Length).ToString("0.00%")) .Append(')'); } if (node.GBX is null || !node.GBX.ID.HasValue || Remap(node.GBX.ID.Value) != node.ID) { logChunk.Insert(0, "~ "); } Log.Write(logChunk.ToString()); Chunk chunk; chunkId = Chunk.Remap(chunkId); var reflected = NodeCacheManager.AvailableChunkClasses[type].TryGetValue(chunkId, out Type? chunkClass); if (reflected && chunkClass is null) { throw new ThisShouldNotHappenException(); } var skippable = reflected && chunkClass !.BaseType !.GetGenericTypeDefinition() == typeof(SkippableChunk <>); // Unknown or skippable chunk if (!reflected || skippable) { var skip = r.ReadUInt32(); if (skip != 0x534B4950) { if (reflected) { break; } var logChunkError = $"[{node.ClassName}] 0x{chunkId.ToString("X8")} ERROR (wrong chunk format or unknown unskippable chunk)"; if (node.GBX is not null && node.GBX.ID.HasValue && Remap(node.GBX.ID.Value) == node.ID) { Log.Write(logChunkError, ConsoleColor.Red); } else { Log.Write("~ " + logChunkError, ConsoleColor.Red); } #if DEBUG // Read the rest of the body var streamPos = r.BaseStream.Position; var uncontrollableData = r.ReadToEnd(); r.BaseStream.Position = streamPos; #endif throw new ChunkParseException(chunkId, previousChunkId); /* Usually breaks in the current state and causes confusion * * var buffer = BitConverter.GetBytes(chunkID); * using (var restMs = new MemoryStream(ushort.MaxValue)) * { * restMs.Write(buffer, 0, buffer.Length); * * while (r.PeekUInt32() != 0xFACADE01) * restMs.WriteByte(r.ReadByte()); * * node.Rest = restMs.ToArray(); * } * Debug.WriteLine("FACADE found.");*/ } var chunkDataSize = r.ReadInt32(); var chunkData = new byte[chunkDataSize]; if (chunkDataSize > 0) { r.Read(chunkData, 0, chunkDataSize); } if (reflected) { var attributesAvailable = NodeCacheManager.AvailableChunkAttributes[type].TryGetValue( chunkId, out IEnumerable <Attribute>?attributes); if (!attributesAvailable) { throw new ThisShouldNotHappenException(); } if (attributes is null) { throw new ThisShouldNotHappenException(); } var ignoreChunkAttribute = default(IgnoreChunkAttribute); var chunkAttribute = default(ChunkAttribute); foreach (var att in attributes) { if (att is IgnoreChunkAttribute ignoreChunkAtt) { ignoreChunkAttribute = ignoreChunkAtt; } if (att is ChunkAttribute chunkAtt) { chunkAttribute = chunkAtt; } } if (chunkAttribute is null) { throw new ThisShouldNotHappenException(); } NodeCacheManager.AvailableChunkConstructors[type].TryGetValue(chunkId, out Func <Chunk>?constructor); if (constructor is null) { throw new ThisShouldNotHappenException(); } var c = constructor(); c.Node = node; ((ISkippableChunk)c).Data = chunkData; if (chunkData == null || chunkData.Length == 0) { ((ISkippableChunk)c).Discovered = true; } node.Chunks.Add(c); #if DEBUG c.Debugger.RawData = chunkData; #endif if (ignoreChunkAttribute == null) { c.OnLoad(); if (chunkAttribute.ProcessSync) { ((ISkippableChunk)c).Discover(); } } chunk = c; } else { Debug.WriteLine("Unknown skippable chunk: " + chunkId.ToString("X")); chunk = (Chunk)Activator.CreateInstance(typeof(SkippableChunk <>).MakeGenericType(type), node, chunkId, chunkData) !; #if DEBUG chunk.Debugger.RawData = chunkData; #endif node.Chunks.Add(chunk); } }