public void Write(GameBoxWriter w, ClassIDRemap remap) { using (MemoryStream ms = new MemoryStream()) using (GameBoxWriter bodyW = new GameBoxWriter(ms)) { (Body as ILookbackable).IdWritten = false; (Body as ILookbackable).IdStrings.Clear(); Body.AuxilaryNodes.Clear(); Log.Write("Writing the body..."); Body.Write(bodyW, remap); // Body is written first so that the aux node count is determined properly Log.Write("Writing the header..."); (Header as ILookbackable).IdWritten = false; (Header as ILookbackable).IdStrings.Clear(); Header.Write(w, Body.AuxilaryNodes.Count + 1, remap); Log.Write("Writing the reference table..."); if (RefTable == null) { w.Write(0); } else { RefTable.Write(w); } w.Write(ms.ToArray(), 0, (int)ms.Length); } }
public void Write(GameBoxWriter w) { w.Write(ExternalNodes.Length); w.Write(RootFolder.Folders.Count); // ... }
public void Write(GameBoxWriter w, ClassIDRemap remap) { if (GBX.BodyCompression == 'C') { using (var msBody = new MemoryStream()) using (var gbxwBody = new GameBoxWriter(msBody, this)) { GBX.MainNode.Write(gbxwBody, remap); MiniLZO.Compress(msBody.ToArray(), out byte[] output); w.Write((int)msBody.Length); // Uncompressed w.Write(output.Length); // Compressed w.Write(output, 0, output.Length); // Compressed body data } } else { GBX.MainNode.Write(w); } // ... }
public void Write(GameBoxWriter w) { w.Write(Stream.ToArray(), 0, (int)Stream.Length); }
public override void Write(T n, GameBoxWriter w, GameBoxReader unknownR) { w.Write(Stream.ToArray(), 0, (int)Stream.Length); }
public void Write(GameBoxWriter w, ClassIDRemap remap) { var stopwatch = Stopwatch.StartNew(); int counter = 0; foreach (Chunk chunk in Chunks) { counter++; var logChunk = $"[{ClassName}] 0x{chunk.ID:X8} ({(float)counter / Chunks.Count:0.00%})"; if (Body?.GBX.ClassID.HasValue == true && Remap(Body.GBX.ClassID.Value) == ID) { Log.Write(logChunk); } else { Log.Write($"~ {logChunk}"); } ((IChunk)chunk).Node = this; chunk.Unknown.Position = 0; ILookbackable lb = chunk.Lookbackable; if (chunk is ILookbackable l) { l.IdWritten = false; l.IdStrings.Clear(); lb = l; } if (lb == null) { if (ParentChunk is ILookbackable l2) { lb = l2; } else { lb = w.Lookbackable; } } using (var ms = new MemoryStream()) using (var msW = new GameBoxWriter(ms, lb)) { var rw = new GameBoxReaderWriter(msW); msW.Chunk = chunk; try { if (chunk is ISkippableChunk s && !s.Discovered) { s.Write(msW); } else if (chunk.GetType().GetCustomAttribute <AutoReadWriteChunkAttribute>() == null) { ((IChunk)chunk).ReadWrite(this, rw); } else { msW.Write(chunk.Unknown.ToArray(), 0, (int)chunk.Unknown.Length); } w.Write(Chunk.Remap(chunk.ID, remap)); if (chunk is ISkippableChunk) { w.Write(0x534B4950); w.Write((int)ms.Length); } w.Write(ms.ToArray(), 0, (int)ms.Length); }
public static T Parse <T>(GameBoxReader r, uint?classID = null, GameBox <T> gbx = null, IProgress <GameBoxReadProgress> progress = null) where T : Node { var stopwatch = Stopwatch.StartNew(); if (!classID.HasValue) { classID = r.ReadUInt32(); } if (classID == uint.MaxValue) { return(null); } classID = Remap(classID.Value); if (!AvailableClasses.TryGetValue(classID.Value, out Type type)) { throw new NotImplementedException($"Node ID 0x{classID.Value:X8} is not implemented. ({Names.Where(x => x.Key == Chunk.Remap(classID.Value)).Select(x => x.Value).FirstOrDefault() ?? "unknown class"})"); } T node = null; if (gbx == null) { node = (T)Activator.CreateInstance(type); } else { node = gbx.MainNode; } IGameBoxBody body; if (r.Lookbackable is Chunk ch) { body = (IGameBoxBody)ch.Part; } else { body = (IGameBoxBody)r.Lookbackable; } node.Body = body; var chunks = new ChunkSet { Node = node }; node.Chunks = chunks; uint?previousChunk = null; while (r.BaseStream.Position < r.BaseStream.Length) { if (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 if (chunkID == 0) { // weird case after ending node reference } else { var logChunk = $"[{node.ClassName}] 0x{chunkID:X8} ({(float)r.BaseStream.Position / r.BaseStream.Length:0.00%})"; if (node.Body?.GBX.ClassID.HasValue == true && Remap(node.Body.GBX.ClassID.Value) == node.ID) { Log.Write(logChunk); } else { Log.Write($"~ {logChunk}"); } } Type chunkClass = null; Chunk chunk = null; var chunkRemapped = Chunk.Remap(chunkID); var reflected = ((chunkRemapped & 0xFFFFF000) == node.ID || AvailableInheritanceClasses[type].Contains(chunkRemapped & 0xFFFFF000)) && (AvailableChunkClasses[type].TryGetValue(chunkRemapped, out chunkClass) || AvailableChunkClasses[type].TryGetValue(chunkID & 0xFFF, out chunkClass)); var skippable = reflected && chunkClass.BaseType.GetGenericTypeDefinition() == typeof(SkippableChunk <>); if (!reflected || skippable) { var skip = r.ReadUInt32(); if (skip != 0x534B4950) { if (chunkID != 0 && !reflected) { node.FaultyChunk = chunkID; var logChunkError = $"[{node.ClassName}] 0x{chunkID:X8} ERROR (wrong chunk format or unknown unskippable chunk)"; if (node.Body?.GBX.ClassID.HasValue == true && Remap(node.Body.GBX.ClassID.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} (" + $"{Names.Where(x => x.Key == Chunk.Remap(chunkID & 0xFFFFF000)).Select(x => x.Value).FirstOrDefault() ?? "unknown class"})" + $"\nPrevious chunk: 0x{previousChunk ?? 0:X8} (" + $"{(previousChunk.HasValue ? (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 && chunkClass.GetCustomAttribute <IgnoreChunkAttribute>() == null) { ISkippableChunk c; var constructor = chunkClass.GetConstructors().First(); var constructorParams = constructor.GetParameters(); if (constructorParams.Length == 0) { c = (ISkippableChunk)constructor.Invoke(new object[0]); c.Node = node; c.Part = (GameBoxPart)body; c.Stream = new MemoryStream(chunkData, 0, chunkData.Length, false); if (chunkData == null || chunkData.Length == 0) { c.Discovered = true; } c.OnLoad(); } else if (constructorParams.Length == 2) { c = (ISkippableChunk)constructor.Invoke(new object[] { node, chunkData }); } else { throw new ArgumentException($"{type.FullName} has an invalid amount of parameters."); } chunks.Add((Chunk)c); if (chunkClass.GetCustomAttribute <ChunkAttribute>().ProcessSync) { c.Discover(); } chunk = (Chunk)c; } else { Debug.WriteLine("Unknown skippable chunk: " + chunkID.ToString("X")); chunk = (Chunk)Activator.CreateInstance(typeof(SkippableChunk <>).MakeGenericType(type), node, chunkRemapped, chunkData); chunks.Add(chunk); } } if (reflected && !skippable) { if (skippable) // Does it ever happen? { var skip = r.ReadUInt32(); var chunkDataSize = r.ReadInt32(); } IChunk c; var constructor = chunkClass.GetConstructors().First(); var constructorParams = constructor.GetParameters(); if (constructorParams.Length == 0) { c = (IChunk)constructor.Invoke(new object[0]); c.Node = node; } else if (constructorParams.Length == 1) { c = (IChunk)constructor.Invoke(new object[] { node }); } else { throw new ArgumentException($"{type.FullName} has an invalid amount of parameters."); } c.Part = (GameBoxPart)body; c.OnLoad(); chunks.Add((Chunk)c); r.Chunk = (Chunk)c; // Set chunk temporarily for reading var posBefore = r.BaseStream.Position; GameBoxReaderWriter gbxrw = new GameBoxReaderWriter(r); try { if (chunkClass.GetCustomAttribute <IgnoreChunkAttribute>() == null) { if (chunkClass.GetCustomAttribute <AutoReadWriteChunkAttribute>() == null) { c.ReadWrite(node, gbxrw); } else { var unknown = new GameBoxWriter(((Chunk)c).Unknown, r.Lookbackable); var unknownData = r.ReadTillFacade(); 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); r.Chunk = null; chunk = (Chunk)c; } progress?.Report(new GameBoxReadProgress(GameBoxReadProgressStage.Body, (float)r.BaseStream.Position / r.BaseStream.Length, gbx, chunk)); previousChunk = chunkID; } stopwatch.Stop(); var logNodeCompletion = $"[{node.ClassName}] DONE! ({stopwatch.Elapsed.TotalMilliseconds}ms)"; if (node.Body?.GBX.ClassID.HasValue == true && Remap(node.Body.GBX.ClassID.Value) == node.ID) { Log.Write(logNodeCompletion, ConsoleColor.Green); } else { Log.Write($"~ {logNodeCompletion}", ConsoleColor.Green); } return(node); }
public void Write(GameBoxWriter w, int numNodes, ClassIDRemap remap) { w.Write(GameBox.Magic, StringLengthPrefix.None); w.Write(GBX.Version); if (GBX.Version >= 3) { w.Write((byte)GBX.ByteFormat.GetValueOrDefault()); w.Write((byte)GBX.RefTableCompression.GetValueOrDefault()); w.Write((byte)GBX.BodyCompression.GetValueOrDefault()); if (GBX.Version >= 4) { w.Write((byte)GBX.UnknownByte.GetValueOrDefault()); } w.Write(GBX.ClassID.GetValueOrDefault()); if (GBX.Version >= 6) { if (Chunks == null) { w.Write(0); } else { using (var userData = new MemoryStream()) using (var gbxw = new GameBoxWriter(userData, this)) { var gbxrw = new GameBoxReaderWriter(gbxw); Dictionary <uint, int> lengths = new Dictionary <uint, int>(); foreach (var chunk in Chunks) { chunk.Unknown.Position = 0; var pos = userData.Position; if (((ISkippableChunk)chunk).Discovered) { ((IChunk)chunk).ReadWrite(((GameBox <T>)GBX).MainNode, gbxrw); } else { ((ISkippableChunk)chunk).Write(gbxw); } lengths[chunk.ID] = (int)(userData.Position - pos); } // Actual data size plus the class id (4 bytes) and each length (4 bytes) plus the number of chunks integer w.Write((int)userData.Length + Chunks.Count * 8 + 4); // Write number of header chunks integer w.Write(Chunks.Count); foreach (Chunk chunk in Chunks) { w.Write(Chunk.Remap(chunk.ID, remap)); var length = lengths[chunk.ID]; if (((IHeaderChunk)chunk).IsHeavy) { length |= 1 << 31; } w.Write(length); } w.Write(userData.ToArray(), 0, (int)userData.Length); } } } w.Write(numNodes); } }