Ejemplo n.º 1
0
    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);
                }
            }