Ejemplo n.º 1
0
        public static void Parse <T>(T node, GameBoxReader r, IProgress <GameBoxReadProgress> progress = null) where T : CMwNod
        {
            var stopwatch = Stopwatch.StartNew();

            node.GBX = r.GBX;

            var type = node.GetType();

            var chunks = new ChunkSet {
                Node = node
            };

            node.Chunks = chunks;

            uint?previousChunk = 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}/{r.BaseStream.Length}");
                    var bytes = r.ReadBytes((int)(r.BaseStream.Length - r.BaseStream.Position));
                    break;
                }

                var chunkID = r.ReadUInt32();

                if (chunkID == 0xFACADE01) // no more chunks
                {
                    break;
                }
                else
                {
                    var logChunk = $"[{node.ClassName}] 0x{chunkID:X8}";
                    if (r.BaseStream.CanSeek)
                    {
                        logChunk += $" ({(float)r.BaseStream.Position / r.BaseStream.Length:0.00%})";
                    }

                    if (node.GBX?.ID.HasValue == true && Remap(node.GBX.ID.Value) == node.ID)
                    {
                        Log.Write(logChunk);
                    }
                    else
                    {
                        Log.Write($"~ {logChunk}");
                    }
                }

                Chunk chunk;

                var chunkRemapped = Chunk.Remap(chunkID);

                Type chunkClass = null;

                var reflected = ((chunkRemapped & 0xFFFFF000) == node.ID || NodeCacheManager.AvailableInheritanceClasses[type].Contains(chunkRemapped & 0xFFFFF000)) &&
                                (NodeCacheManager.AvailableChunkClasses[type].TryGetValue(chunkRemapped, out chunkClass) || NodeCacheManager.AvailableChunkClasses[type].TryGetValue(chunkID & 0xFFF, out chunkClass));

                var skippable = reflected && chunkClass.BaseType.GetGenericTypeDefinition() == typeof(SkippableChunk <>);

                // Unknown or skippable chunk
                if (!reflected || skippable)
                {
                    var skip = r.ReadUInt32();

                    if (skip != 0x534B4950)
                    {
                        if (chunkID != 0 && !reflected)
                        {
                            var logChunkError = $"[{node.ClassName}] 0x{chunkID:X8} ERROR (wrong chunk format or unknown unskippable chunk)";
                            if (node.GBX?.ID.HasValue == true && Remap(node.GBX.ID.Value) == node.ID)
                            {
                                Log.Write(logChunkError, ConsoleColor.Red);
                            }
                            else
                            {
                                Log.Write($"~ {logChunkError}", ConsoleColor.Red);
                            }

                            throw new Exception($"Wrong chunk format or unskippable chunk: 0x{chunkID:X8} (" +
                                                $"{NodeCacheManager.Names.Where(x => x.Key == Chunk.Remap(chunkID & 0xFFFFF000)).Select(x => x.Value).FirstOrDefault() ?? "unknown class"})" +
                                                $"\nPrevious chunk: 0x{previousChunk ?? 0:X8} (" +
                                                $"{(previousChunk.HasValue ? (NodeCacheManager.Names.Where(x => x.Key == Chunk.Remap(previousChunk.Value & 0xFFFFF000)).Select(x => x.Value).FirstOrDefault() ?? "unknown class") : "not a class")})");

                            /* 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.");*/
                        }
                        break;
                    }

                    var chunkDataSize = r.ReadInt32();
                    var chunkData     = new byte[chunkDataSize];
                    if (chunkDataSize > 0)
                    {
                        r.Read(chunkData, 0, chunkDataSize);
                    }

                    if (reflected)
                    {
                        var ignoreChunkAttribute = chunkClass.GetCustomAttribute <IgnoreChunkAttribute>();

                        var constructor = Array.Find(chunkClass.GetConstructors(), x => x.GetParameters().Length == 0);
                        if (constructor == null)
                        {
                            throw new ArgumentException($"{type.FullName} doesn't have a parameterless constructor.");
                        }

                        var c = (Chunk)constructor.Invoke(new object[0]);
                        c.Node = node;
                        c.GBX  = node.GBX;
                        ((ISkippableChunk)c).Data = chunkData;
                        if (chunkData == null || chunkData.Length == 0)
                        {
                            ((ISkippableChunk)c).Discovered = true;
                        }
                        chunks.Add(c);

                        if (ignoreChunkAttribute == null)
                        {
                            c.OnLoad();

                            if (chunkClass.GetCustomAttribute <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, chunkRemapped, chunkData);
                        chunk.GBX = node.GBX;
                        chunks.Add(chunk);
                    }
                }
                else // Known or unskippable chunk
                {
                    var constructor = Array.Find(chunkClass.GetConstructors(), x => x.GetParameters().Length == 0);

                    if (constructor == null)
                    {
                        throw new ArgumentException($"{type.FullName} doesn't have a parameterless constructor.");
                    }

                    var c = (Chunk)constructor.Invoke(new object[0]);
                    c.Node = node;

                    c.OnLoad();

                    chunks.Add(c);

                    //r.Chunk = (Chunk)c; // Set chunk temporarily for reading

                    var posBefore = r.BaseStream.Position;

                    GameBoxReaderWriter gbxrw = new GameBoxReaderWriter(r);

                    var attributes                  = chunkClass.GetCustomAttributes();
                    var ignoreChunkAttribute        = default(IgnoreChunkAttribute);
                    var autoReadWriteChunkAttribute = default(AutoReadWriteChunkAttribute);

                    foreach (var att in attributes)
                    {
                        if (att is IgnoreChunkAttribute ignoreChunkAtt)
                        {
                            ignoreChunkAttribute = ignoreChunkAtt;
                        }
                        if (att is AutoReadWriteChunkAttribute autoReadWriteChunkAtt)
                        {
                            autoReadWriteChunkAttribute = autoReadWriteChunkAtt;
                        }
                    }

                    try
                    {
                        if (ignoreChunkAttribute == null)
                        {
                            if (autoReadWriteChunkAttribute == null)
                            {
                                c.ReadWrite(node, gbxrw);
                            }
                            else
                            {
                                var unknown     = new GameBoxWriter(c.Unknown, r.Lookbackable);
                                var unknownData = r.ReadUntilFacade();
                                unknown.Write(unknownData, 0, unknownData.Length);
                            }
                        }
                        else
                        {
                            throw new Exception($"Chunk 0x{chunkID & 0xFFF:x3} from class {node.ClassName} is known but its content is unknown to read.");
                        }
                    }
                    catch (EndOfStreamException)
                    {
                        Debug.WriteLine($"Unexpected end of the stream while reading the chunk.");
                    }

                    c.Progress = (int)(r.BaseStream.Position - posBefore);

                    chunk = c;
                }

                progress?.Report(new GameBoxReadProgress(GameBoxReadProgressStage.Body, (float)r.BaseStream.Position / r.BaseStream.Length, node.GBX, chunk));

                previousChunk = chunkID;
            }

            stopwatch.Stop();

            var logNodeCompletion = $"[{node.ClassName}] DONE! ({stopwatch.Elapsed.TotalMilliseconds}ms)";

            if (node.GBX.ID.HasValue == true && Remap(node.GBX.ID.Value) == node.ID)
            {
                Log.Write(logNodeCompletion, ConsoleColor.Green);
            }
            else
            {
                Log.Write($"~ {logNodeCompletion}", ConsoleColor.Green);
            }
        }