/// <summary> /// Updates the tag instance's state from a block of tag data. /// </summary> /// <param name="data">The tag data.</param> /// <param name="dataOffset">The offset of the tag data relative to the tag instance's header.</param> internal void Update(CachedTagData data, uint dataOffset) { Group = data.Group; DefinitionOffset = data.MainStructOffset + dataOffset; Dependencies = new ReadOnlySet <int>(new HashSet <int>(data.Dependencies)); _pointerOffsets = data.PointerFixups.Select(fixup => fixup.WriteOffset + dataOffset).ToList(); _resourceOffsets = data.ResourcePointerOffsets.Select(offset => offset + dataOffset).ToList(); }
/// <summary> /// Builds a description for a tag's data without extracting anything. /// </summary> /// <param name="stream">The stream to read from.</param> /// <param name="tag">The tag to read.</param> /// <param name="dataOffset">On return, this will contain the offset of the tag's data relative to its header.</param> /// <returns>The description that was built. </returns> private static CachedTagData BuildTagData(Stream stream, CachedTagInstance tag, out uint dataOffset) { var data = new CachedTagData { Group = tag.Group, MainStructOffset = tag.DefinitionOffset, }; foreach (var dependency in tag.Dependencies) { data.Dependencies.Add(dependency); } // Read pointer fixups var reader = new BinaryReader(stream); foreach (var pointerOffset in tag.PointerOffsets) { reader.BaseStream.Position = tag.HeaderOffset + pointerOffset; data.PointerFixups.Add(new CachedTagData.PointerFixup { WriteOffset = pointerOffset, TargetOffset = tag.PointerToOffset(reader.ReadUInt32()), }); } // Find the start of the tag's data by finding the offset of the first block which is pointed to by something // We CAN'T just calculate a header size here because we don't know for sure if there's padding and how big it is var startOffset = tag.DefinitionOffset; foreach (var fixup in data.PointerFixups) { startOffset = Math.Min(startOffset, Math.Min(fixup.WriteOffset, fixup.TargetOffset)); } // Now convert all offsets into relative ones foreach (var fixup in data.PointerFixups) { fixup.WriteOffset -= startOffset; fixup.TargetOffset -= startOffset; } data.ResourcePointerOffsets.AddRange(tag.ResourcePointerOffsets.Select(offset => offset - startOffset)); data.TagReferenceOffsets.AddRange(tag.TagReferenceOffsets.Select(offset => offset - startOffset)); data.MainStructOffset -= startOffset; dataOffset = startOffset; return(data); }
/// <summary> /// Overwrites a tag's data. /// </summary> /// <param name="stream">The stream to write to.</param> /// <param name="tag">The tag to overwrite.</param> /// <param name="data">The data to store.</param> public void SetTagData(Stream stream, CachedTagInstance tag, CachedTagData data) { if (tag == null) { throw new ArgumentNullException(nameof(tag)); } else if (data == null) { throw new ArgumentNullException(nameof(data)); } else if (data.Group == TagGroup.None) { throw new ArgumentException("Cannot assign a tag to a null tag group"); } else if (data.Data == null) { throw new ArgumentException("The tag data buffer is null"); } // Ensure the data fits var headerSize = CachedTagInstance.CalculateHeaderSize(data); var alignedHeaderSize = (uint)((headerSize + 0xF) & ~0xF); if (tag.HeaderOffset < 0) { tag.HeaderOffset = GetNewTagOffset(tag.Index); } var alignedLength = (data.Data.Length + 0xF) & ~0xF; ResizeBlock(stream, tag, tag.HeaderOffset, tag.TotalSize, alignedHeaderSize + alignedLength); tag.TotalSize = alignedHeaderSize + alignedLength; tag.Update(data, alignedHeaderSize); // Write in the new header and data stream.Position = tag.HeaderOffset; var writer = new BinaryWriter(stream); tag.WriteHeader(writer); StreamUtil.Fill(stream, 0, (int)(alignedHeaderSize - headerSize)); stream.Write(data.Data, 0, data.Data.Length); StreamUtil.Fill(stream, 0, alignedLength - data.Data.Length); // Correct pointers foreach (var fixup in data.PointerFixups) { writer.BaseStream.Position = tag.HeaderOffset + alignedHeaderSize + fixup.WriteOffset; writer.Write(tag.OffsetToPointer(alignedHeaderSize + fixup.TargetOffset)); } UpdateTagOffsets(writer); }
/// <summary> /// Calculates the header size that would be needed for a given block of tag data. /// </summary> /// <param name="data">The descriptor to use.</param> /// <returns>The size of the tag's header.</returns> internal static uint CalculateHeaderSize(CachedTagData data) => (uint)(TagHeaderSize + data.Dependencies.Count * 4 + data.PointerFixups.Count * 4 + data.ResourcePointerOffsets.Count * 4 + data.TagReferenceOffsets.Count * 4);