/// <summary> /// Saves the serialized <see cref="GameBox{T}"/> on a disk. /// </summary> /// <param name="fileName">Relative or absolute file path.</param> /// <param name="remap">What to remap the newest node IDs to. Used for older games.</param> public void Save(string fileName, ClassIDRemap remap) { using (var fs = File.OpenWrite(fileName)) Save(fs, remap); Log.Write($"GBX file {fileName} saved."); }
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 static uint Remap(uint chunkID, ClassIDRemap remap = ClassIDRemap.Latest) { var classPart = chunkID & 0xFFFFF000; var chunkPart = chunkID & 0xFFF; switch (remap) { case ClassIDRemap.ManiaPlanet: if (Node.Mappings.TryGetValue(classPart, out uint newID)) { return(newID + chunkPart); } return(chunkID); case ClassIDRemap.TrackMania: if (classPart == 0x03078000) // Not ideal solution { return(0x24061000 + chunkPart); } return(Node.Mappings.LastOrDefault(x => x.Value == classPart).Key + chunkPart); default: return(chunkID); } }
/// <summary> /// Saves the serialized <see cref="GameBox{T}"/> to a stream. /// </summary> /// <param name="stream">Any kind of stream that supports writing.</param> /// <param name="remap">What to remap the newest node IDs to. Used for older games.</param> public void Save(Stream stream, ClassIDRemap remap) { if (IntPtr.Size == 8) { throw new NotSupportedException("Saving GBX is not supported with x64 platform target, due to LZO implementation. Please force your platform target to x86."); } using (var w = new GameBoxWriter(stream)) Write(w, remap); }
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, 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 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); } }