/// <summary>
 /// Allocates a new tag at the end of the tag list without updating the file.
 /// You can give the tag data by using one of the overwrite functions.
 /// </summary>
 /// <param name="type">The tag's type information.</param>
 /// <returns>The allocated tag.</returns>
 public TagInstance AllocateTag(TagGroup type)
 {
     var tagIndex = _tags.Count;
     var tag = new TagInstance(tagIndex, type);
     _tags.Add(tag);
     return tag;
 }
Exemple #2
0
 /// <summary>
 /// Fixes tag offsets after a resize operation.
 /// </summary>
 /// <param name="startOffset">The offset where the resize operation took place.</param>
 /// <param name="sizeDelta">The amount to add to each tag offset after the start offset.</param>
 /// <param name="ignore">A tag to ignore.</param>
 private void FixTagOffsets(long startOffset, long sizeDelta, TagInstance ignore)
 {
     foreach (var adjustTag in _tags.Where(t => t != null && t != ignore && t.HeaderOffset >= startOffset))
     {
         adjustTag.HeaderOffset += sizeDelta;
     }
 }
Exemple #3
0
        /// <summary>
        /// Overwrites a tag's data, not including its header.
        /// Any pointers in the data to write will be adjusted to be relative to the start of the tag's header.
        /// Make sure that fixups are set correctly before calling this.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag to overwrite.</param>
        /// <param name="data">The data to write.</param>
        public void OverwriteTagData(Stream stream, TagInstance tag, byte[] data)
        {
            if (tag == null)
            {
                throw new ArgumentNullException("tag");
            }

            // Ensure the data fits
            var newHeaderSize = CalculateNewHeaderSize(tag);

            if (tag.HeaderOffset < 0)
            {
                tag.HeaderOffset = GetNewTagOffset(tag.Index);
            }
            var alignedLength = (data.Length + 0xF) & ~0xF;

            ResizeBlock(stream, tag, tag.HeaderOffset, tag.TotalSize, newHeaderSize + alignedLength);
            tag.DataOffset = tag.HeaderOffset + newHeaderSize;
            tag.DataSize   = alignedLength;

            // Write it in and then update
            stream.Position = tag.DataOffset;
            stream.Write(data, 0, data.Length);
            StreamUtil.Fill(stream, 0, alignedLength - data.Length);
            UpdateTag(stream, tag);
        }
Exemple #4
0
        /// <summary>
        /// Reads a tag's data from the file.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="tag">The tag to read.</param>
        /// <returns>The data that was read.</returns>
        public TagData ExtractTag(Stream stream, TagInstance tag)
        {
            if (tag == null)
            {
                throw new ArgumentNullException(nameof(tag));
            }
            if (tag.HeaderOffset < 0)
            {
                throw new ArgumentException("The tag is not in the cache file");
            }

            // Build the description info and get the data offset
            uint dataOffset;
            var  data = BuildTagDescription(stream, tag, out dataOffset);

            // Read the tag data
            stream.Position = tag.HeaderOffset + dataOffset;
            data.Data       = new byte[tag.TotalSize - dataOffset];
            stream.Read(data.Data, 0, data.Data.Length);

            // Correct pointers
            using (var dataWriter = new BinaryWriter(new MemoryStream(data.Data)))
            {
                foreach (var fixup in data.PointerFixups)
                {
                    dataWriter.BaseStream.Position = fixup.WriteOffset;
                    dataWriter.Write(tag.OffsetToPointer(fixup.TargetOffset));
                }
            }
            return(data);
        }
Exemple #5
0
        /// <summary>
        /// Allocates a new tag at the end of the tag list without updating the file.
        /// You can give the tag data by using one of the overwrite functions.
        /// </summary>
        /// <param name="type">The tag's type information.</param>
        /// <returns>The allocated tag.</returns>
        public TagInstance AllocateTag(TagGroup type)
        {
            var tagIndex = _tags.Count;
            var tag      = new TagInstance(tagIndex, type);

            _tags.Add(tag);
            return(tag);
        }
Exemple #6
0
        /// <summary>
        /// Updates a tag's fixup information.
        /// </summary>
        /// <param name="writer">The stream to write to.</param>
        /// <param name="tag">The tag.</param>
        private static void UpdateTagFixups(BinaryWriter writer, TagInstance tag)
        {
            var totalHeaderSize = CalculateNewHeaderSize(tag);

            foreach (var fixup in tag.DataFixups)
            {
                writer.BaseStream.Position = tag.DataOffset + fixup.WriteOffset;
                writer.Write(fixup.TargetOffset + totalHeaderSize + FixupPointerBase);
            }
        }
Exemple #7
0
        /// <summary>
        /// Allocates a new tag at the end of the tag list without updating the file.
        /// You can give the tag data by using one of the overwrite functions.
        /// </summary>
        /// <returns>The allocated tag.</returns>
        public TagInstance AllocateTag()
        {
            var tagIndex = _tags.Count;
            var tag      = new TagInstance {
                Index = tagIndex
            };

            _tags.Add(tag);
            return(tag);
        }
Exemple #8
0
 /// <summary>
 /// Rebases fixup pointers in tag data.
 /// </summary>
 /// <param name="tag">The tag.</param>
 /// <param name="data">The data.</param>
 /// <param name="newBase">The new base offset to use.</param>
 private static void RebasePointers(TagInstance tag, byte[] data, uint newBase)
 {
     using (var writer = new BinaryWriter(new MemoryStream(data)))
     {
         foreach (var fixup in tag.DataFixups)
         {
             writer.BaseStream.Position = fixup.WriteOffset;
             writer.Write(newBase + fixup.TargetOffset + FixupPointerBase);
         }
     }
 }
 /// <summary>
 /// Reads a tag's raw data from the file, including its header.
 /// </summary>
 /// <param name="stream">The stream to read from.</param>
 /// <param name="tag">The tag to read.</param>
 /// <returns>The data that was read.</returns>
 public byte[] ExtractTagRaw(Stream stream, TagInstance tag)
 {
     if (tag == null)
         throw new ArgumentNullException(nameof(tag));
     if (tag.HeaderOffset < 0)
         throw new ArgumentException("The tag is not in the cache file");
     stream.Position = tag.HeaderOffset;
     var result = new byte[tag.TotalSize];
     stream.Read(result, 0, result.Length);
     return result;
 }
Exemple #10
0
        /// <summary>
        /// Resizes a block of data in the file and updates tag offsets.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="tag">The tag that the block belongs to, if any.</param>
        /// <param name="startOffset">The offset where the block to resize begins at.</param>
        /// <param name="oldSize">The current size of the block to resize.</param>
        /// <param name="newSize">The new size of the block.</param>
        /// <exception cref="System.ArgumentException">Cannot resize a block to a negative size</exception>
        private void ResizeBlock(Stream stream, TagInstance tag, long startOffset, long oldSize, long newSize)
        {
            if (newSize < 0)
            {
                throw new ArgumentException("Cannot resize a block to a negative size");
            }
            var oldEndOffset = startOffset + oldSize;
            var sizeDelta    = newSize - oldSize;

            StreamUtil.Copy(stream, oldEndOffset, oldEndOffset + sizeDelta, stream.Length - oldEndOffset);
            FixTagOffsets(oldEndOffset, sizeDelta, tag);
        }
Exemple #11
0
        /// <summary>
        /// Duplicates a tag.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag to duplicate.</param>
        /// <returns>The new tag.</returns>
        public TagInstance DuplicateTag(Stream stream, TagInstance tag)
        {
            if (tag == null)
            {
                throw new ArgumentNullException(nameof(tag));
            }

            // Just extract the tag and add it back
            var result = AllocateTag();

            SetTagDataRaw(stream, result, ExtractTagRaw(stream, tag));
            return(result);
        }
Exemple #12
0
        /// <summary>
        /// Duplicates a tag.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag to duplicate.</param>
        /// <returns>The new tag.</returns>
        public TagInstance DuplicateTag(Stream stream, TagInstance tag)
        {
            if (tag == null)
            {
                throw new ArgumentNullException("tag");
            }

            // Just extract the tag and add it back
            var result = AllocateTag();

            OverwriteFullTag(stream, result, ExtractFullTag(stream, tag));
            return(result);
        }
Exemple #13
0
        /// <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, TagInstance tag, TagData data)
        {
            if (tag == null)
            {
                throw new ArgumentNullException(nameof(tag));
            }
            if (data == null)
            {
                throw new ArgumentNullException(nameof(data));
            }
            if (data.Group == TagGroup.Null)
            {
                throw new ArgumentException("Cannot assign a tag to a null tag group");
            }
            if (data.Data == null)
            {
                throw new ArgumentException("The tag data buffer is null");
            }

            // Ensure the data fits
            var headerSize        = TagInstance.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);
        }
Exemple #14
0
        /// <summary>
        /// Saves any changes made to a tag's properties.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag to update.</param>
        public void UpdateTag(Stream stream, TagInstance tag)
        {
            if (tag == null)
            {
                throw new ArgumentNullException("tag");
            }
            if (tag.HeaderOffset < 0)
            {
                throw new ArgumentException("The tag is not in the cache file");
            }
            var writer = new BinaryWriter(stream);

            UpdateTagHeader(writer, tag);
            UpdateTagFixups(writer, tag);
            UpdateTagOffsets(writer);
        }
Exemple #15
0
        /// <summary>
        /// Reads a tag's raw data from the file, including its header.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="tag">The tag to read.</param>
        /// <returns>The data that was read.</returns>
        public byte[] ExtractTagRaw(Stream stream, TagInstance tag)
        {
            if (tag == null)
            {
                throw new ArgumentNullException(nameof(tag));
            }
            if (tag.HeaderOffset < 0)
            {
                throw new ArgumentException("The tag is not in the cache file");
            }
            stream.Position = tag.HeaderOffset;
            var result = new byte[tag.TotalSize];

            stream.Read(result, 0, result.Length);
            return(result);
        }
Exemple #16
0
        /// <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 TagData BuildTagDescription(Stream stream, TagInstance tag, out uint dataOffset)
        {
            var data = new TagData
            {
                Group            = tag.Group,
                MainStructOffset = tag.MainStructOffset,
            };

            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 TagPointerFixup
                {
                    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.MainStructOffset;

            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.MainStructOffset -= startOffset;
            dataOffset             = startOffset;
            return(data);
        }
Exemple #17
0
        /// <summary>
        /// Reads a tag's data from the file, not including its header.
        /// Any pointers in the tag will be adjusted to be relative to the start of the tag's data.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="tag">The tag to read.</param>
        /// <returns>The data that was read.</returns>
        public byte[] ExtractTagData(Stream stream, TagInstance tag)
        {
            if (tag == null)
            {
                throw new ArgumentNullException("tag");
            }
            if (tag.HeaderOffset < 0)
            {
                throw new ArgumentException("The tag is not in the cache file");
            }
            stream.Position = tag.DataOffset;
            var result = new byte[tag.DataSize];

            stream.Read(result, 0, result.Length);
            RebasePointers(tag, result, 0);
            return(result);
        }
Exemple #18
0
        /// <summary>
        /// Writes a tag's header back to the file, resizing it if necessary.
        /// </summary>
        /// <param name="writer">The stream to write to.</param>
        /// <param name="tag">The tag.</param>
        private void UpdateTagHeader(BinaryWriter writer, TagInstance tag)
        {
            // Resize the header if necessary
            var newHeaderSize = CalculateNewHeaderSize(tag);

            if (newHeaderSize != tag.HeaderSize)
            {
                ResizeBlock(writer.BaseStream, tag, tag.HeaderOffset, tag.HeaderSize, newHeaderSize);
            }
            tag.DataOffset = tag.HeaderOffset + newHeaderSize;

            // Write the tag header
            writer.BaseStream.Position = tag.HeaderOffset;
            writer.Write(tag.Checksum);
            writer.Write((uint)(tag.DataSize + newHeaderSize));
            writer.Write((short)tag.Dependencies.Count);
            writer.Write((short)tag.DataFixups.Count);
            writer.Write((short)tag.ResourceFixups.Count);
            writer.Write((short)0);
            writer.Write(tag.MainStructOffset + newHeaderSize);
            writer.Write(tag.GroupTag.Value);
            writer.Write(tag.ParentGroupTag.Value);
            writer.Write(tag.GrandparentGroupTag.Value);
            writer.Write(tag.GroupName.Value);

            // Write dependencies
            foreach (var dependency in tag.Dependencies)
            {
                writer.Write(dependency);
            }

            // Write fixup pointers
            foreach (var fixup in tag.DataFixups.Concat(tag.ResourceFixups))
            {
                writer.Write(fixup.WriteOffset + newHeaderSize + FixupPointerBase);
            }

            // Padding
            var paddingSize = (tag.HeaderSize - (writer.BaseStream.Position - tag.HeaderOffset)) & 0xF;

            for (var i = 0; i < paddingSize; i += 4)
            {
                writer.Write(0);
            }
        }
Exemple #19
0
        /// <summary>
        /// Overwrites a tag's data, including its header.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag to overwrite.</param>
        /// <param name="data">The data to overwrite the tag with.</param>
        /// <exception cref="System.ArgumentNullException">tag</exception>
        public void OverwriteFullTag(Stream stream, TagInstance tag, byte[] data)
        {
            if (tag == null)
            {
                throw new ArgumentNullException("tag");
            }

            // Ensure the data fits
            if (tag.HeaderOffset < 0)
            {
                tag.HeaderOffset = GetNewTagOffset(tag.Index);
            }
            ResizeBlock(stream, tag, tag.HeaderOffset, tag.TotalSize, data.Length);

            // Write the data
            stream.Position = tag.HeaderOffset;
            stream.Write(data, 0, data.Length);

            // Re-parse it and update tag offsets
            stream.Position = tag.HeaderOffset;
            ReadTagHeader(new BinaryReader(stream), tag);
            UpdateTagOffsets(new BinaryWriter(stream));
        }
Exemple #20
0
        /// <summary>
        /// Reads the tags.dat file.
        /// </summary>
        /// <param name="reader">The stream to read from.</param>
        private void Load(BinaryReader reader)
        {
            // Read file header
            reader.BaseStream.Position = 0x4;
            var tagListOffset = reader.ReadInt32(); // 0x4 uint32 offset table offset
            var tagCount      = reader.ReadInt32(); // 0x8 uint32 number of tags

            reader.BaseStream.Position = 0x10;
            Timestamp = reader.ReadInt64();         // 0x10 FILETIME timestamp

            // Read tag offset list
            var headerOffsets = new uint[tagCount];

            reader.BaseStream.Position = tagListOffset;
            for (var i = 0; i < tagCount; i++)
            {
                headerOffsets[i] = reader.ReadUInt32();
            }

            // Read each tag
            for (var i = 0; i < tagCount; i++)
            {
                if (headerOffsets[i] == 0)
                {
                    // Offset of 0 = null tag
                    _tags.Add(null);
                    continue;
                }
                var tag = new TagInstance(i)
                {
                    HeaderOffset = headerOffsets[i]
                };
                _tags.Add(tag);
                reader.BaseStream.Position = tag.HeaderOffset;
                tag.ReadHeader(reader);
            }
        }
Exemple #21
0
        /// <summary>
        /// Overwrites a tag's raw data, including its header.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag to overwrite.</param>
        /// <param name="data">The data to overwrite the tag with.</param>
        /// <exception cref="System.ArgumentNullException">tag</exception>
        public void SetTagDataRaw(Stream stream, TagInstance tag, byte[] data)
        {
            if (tag == null)
            {
                throw new ArgumentNullException(nameof(tag));
            }

            // Ensure the data fits
            if (tag.HeaderOffset < 0)
            {
                tag.HeaderOffset = GetNewTagOffset(tag.Index);
            }
            ResizeBlock(stream, tag, tag.HeaderOffset, tag.TotalSize, data.Length);
            tag.TotalSize = data.Length;

            // Write the data
            stream.Position = tag.HeaderOffset;
            stream.Write(data, 0, data.Length);

            // Re-parse it
            stream.Position = tag.HeaderOffset;
            tag.ReadHeader(new BinaryReader(stream));
            UpdateTagOffsets(new BinaryWriter(stream));
        }
 /// <summary>
 /// Saves any changes made to a tag's properties.
 /// </summary>
 /// <param name="stream">The stream to write to.</param>
 /// <param name="tag">The tag to update.</param>
 public void UpdateTag(Stream stream, TagInstance tag)
 {
     if (tag == null)
         throw new ArgumentNullException("tag");
     if (tag.HeaderOffset < 0)
         throw new ArgumentException("The tag is not in the cache file");
     var writer = new BinaryWriter(stream);
     UpdateTagHeader(writer, tag);
     UpdateTagFixups(writer, tag);
     UpdateTagOffsets(writer);
 }
 /// <summary>
 /// Calculates the size of a tag's header after it will be updated in the file.
 /// </summary>
 /// <param name="tag">The tag.</param>
 /// <returns>The new size of the tag's header.</returns>
 private static uint CalculateNewHeaderSize(TagInstance tag)
 {
     return CalculateHeaderSize(tag.Dependencies.Count, tag.DataFixups.Count, tag.ResourceFixups.Count);
 }
        /// <summary>
        /// Overwrites a tag's data, not including its header.
        /// Any pointers in the data to write will be adjusted to be relative to the start of the tag's header.
        /// Make sure that fixups are set correctly before calling this.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag to overwrite.</param>
        /// <param name="data">The data to write.</param>
        public void OverwriteTagData(Stream stream, TagInstance tag, byte[] data)
        {
            if (tag == null)
                throw new ArgumentNullException("tag");

            // Ensure the data fits
            var newHeaderSize = CalculateNewHeaderSize(tag);
            if (tag.HeaderOffset < 0)
                tag.HeaderOffset = GetNewTagOffset(tag.Index);
            var alignedLength = (data.Length + 0xF) & ~0xF;
            ResizeBlock(stream, tag, tag.HeaderOffset, tag.TotalSize, newHeaderSize + alignedLength);
            tag.DataOffset = tag.HeaderOffset + newHeaderSize;
            tag.DataSize = alignedLength;

            // Write it in and then update
            stream.Position = tag.DataOffset;
            stream.Write(data, 0, data.Length);
            StreamUtil.Fill(stream, 0, alignedLength - data.Length);
            UpdateTag(stream, tag);
        }
        /// <summary>
        /// Resizes a block of tag data, updating relative pointers which do not point into the block.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag.</param>
        /// <param name="startOffset">The offset, from the start of the tag's data, where the block to resize begins at.</param>
        /// <param name="oldSize">The current size of the block to resize.</param>
        /// <param name="newSize">The new size of the block.</param>
        /// <param name="origin">The origin where data should be inserted or removed.</param>
        public void ResizeTagData(Stream stream, TagInstance tag, long startOffset, long oldSize, long newSize, ResizeOrigin origin)
        {
            if (tag == null)
                throw new ArgumentNullException("tag");
            if (tag.HeaderOffset < 0)
                throw new ArgumentException("The tag is not in the cache file");
            if (oldSize < 0)
                throw new ArgumentException("The old block size cannot be negative");
            if (newSize < 0)
                throw new ArgumentException("Cannot resize a block to a negative size");
            if (newSize == tag.DataSize)
                return;

            // Correct offsets pointing after the block
            var sizeDelta = newSize - oldSize;
            var blockEndOffset = startOffset + oldSize;
            tag.DataSize += sizeDelta;
            foreach (var fixup in tag.DataFixups.Concat(tag.ResourceFixups))
            {
                if (fixup.WriteOffset >= blockEndOffset)
                    fixup.WriteOffset = (uint)(fixup.WriteOffset + sizeDelta);
                if (fixup.TargetOffset >= blockEndOffset)
                    fixup.TargetOffset = (uint)(fixup.TargetOffset + sizeDelta);
            }
            if (tag.MainStructOffset >= blockEndOffset)
                tag.MainStructOffset = (uint)(tag.MainStructOffset + sizeDelta);
            FixTagOffsets(tag.DataOffset + blockEndOffset, sizeDelta, tag);

            // Insert/remove the data
            long editOffset;
            if (origin == ResizeOrigin.Beginning)
                editOffset = startOffset;
            else if (sizeDelta > 0)
                editOffset = blockEndOffset;
            else
                editOffset = blockEndOffset + sizeDelta;
            stream.Position = tag.DataOffset + editOffset;
            if (sizeDelta > 0)
                StreamUtil.Insert(stream, (int)sizeDelta, 0);
            else
                StreamUtil.Remove(stream, (int)-sizeDelta);
            UpdateTag(stream, tag);
        }
Exemple #26
0
        /// <summary>
        /// Resizes a block of tag data, updating relative pointers which do not point into the block.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag.</param>
        /// <param name="startOffset">The offset, from the start of the tag's data, where the block to resize begins at.</param>
        /// <param name="oldSize">The current size of the block to resize.</param>
        /// <param name="newSize">The new size of the block.</param>
        /// <param name="origin">The origin where data should be inserted or removed.</param>
        public void ResizeTagData(Stream stream, TagInstance tag, long startOffset, long oldSize, long newSize, ResizeOrigin origin)
        {
            if (tag == null)
            {
                throw new ArgumentNullException("tag");
            }
            if (tag.HeaderOffset < 0)
            {
                throw new ArgumentException("The tag is not in the cache file");
            }
            if (oldSize < 0)
            {
                throw new ArgumentException("The old block size cannot be negative");
            }
            if (newSize < 0)
            {
                throw new ArgumentException("Cannot resize a block to a negative size");
            }
            if (newSize == tag.DataSize)
            {
                return;
            }

            // Correct offsets pointing after the block
            var sizeDelta      = newSize - oldSize;
            var blockEndOffset = startOffset + oldSize;

            tag.DataSize += sizeDelta;
            foreach (var fixup in tag.DataFixups.Concat(tag.ResourceFixups))
            {
                if (fixup.WriteOffset >= blockEndOffset)
                {
                    fixup.WriteOffset = (uint)(fixup.WriteOffset + sizeDelta);
                }
                if (fixup.TargetOffset >= blockEndOffset)
                {
                    fixup.TargetOffset = (uint)(fixup.TargetOffset + sizeDelta);
                }
            }
            if (tag.MainStructOffset >= blockEndOffset)
            {
                tag.MainStructOffset = (uint)(tag.MainStructOffset + sizeDelta);
            }
            FixTagOffsets(tag.DataOffset + blockEndOffset, sizeDelta, tag);

            // Insert/remove the data
            long editOffset;

            if (origin == ResizeOrigin.Beginning)
            {
                editOffset = startOffset;
            }
            else if (sizeDelta > 0)
            {
                editOffset = blockEndOffset;
            }
            else
            {
                editOffset = blockEndOffset + sizeDelta;
            }
            stream.Position = tag.DataOffset + editOffset;
            if (sizeDelta > 0)
            {
                StreamUtil.Insert(stream, (int)sizeDelta, 0);
            }
            else
            {
                StreamUtil.Remove(stream, (int)-sizeDelta);
            }
            UpdateTag(stream, tag);
        }
        /// <summary>
        /// Overwrites a tag's data, including its header.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag to overwrite.</param>
        /// <param name="data">The data to overwrite the tag with.</param>
        /// <exception cref="System.ArgumentNullException">tag</exception>
        public void OverwriteFullTag(Stream stream, TagInstance tag, byte[] data)
        {
            if (tag == null)
                throw new ArgumentNullException("tag");

            // Ensure the data fits
            if (tag.HeaderOffset < 0)
                tag.HeaderOffset = GetNewTagOffset(tag.Index);
            ResizeBlock(stream, tag, tag.HeaderOffset, tag.TotalSize, data.Length);

            // Write the data
            stream.Position = tag.HeaderOffset;
            stream.Write(data, 0, data.Length);

            // Re-parse it and update tag offsets
            stream.Position = tag.HeaderOffset;
            ReadTagHeader(new BinaryReader(stream), tag);
            UpdateTagOffsets(new BinaryWriter(stream));
        }
        /// <summary>
        /// Writes a tag's header back to the file, resizing it if necessary.
        /// </summary>
        /// <param name="writer">The stream to write to.</param>
        /// <param name="tag">The tag.</param>
        private void UpdateTagHeader(BinaryWriter writer, TagInstance tag)
        {
            // Resize the header if necessary
            var newHeaderSize = CalculateNewHeaderSize(tag);
            if (newHeaderSize != tag.HeaderSize)
                ResizeBlock(writer.BaseStream, tag, tag.HeaderOffset, tag.HeaderSize, newHeaderSize);
            tag.DataOffset = tag.HeaderOffset + newHeaderSize;

            // Write the tag header
            writer.BaseStream.Position = tag.HeaderOffset;
            writer.Write(tag.Checksum);
            writer.Write((uint)(tag.DataSize + newHeaderSize));
            writer.Write((short)tag.Dependencies.Count);
            writer.Write((short)tag.DataFixups.Count);
            writer.Write((short)tag.ResourceFixups.Count);
            writer.Write((short)0);
            writer.Write(tag.MainStructOffset + newHeaderSize);
            writer.Write(tag.GroupTag.Value);
            writer.Write(tag.ParentGroupTag.Value);
            writer.Write(tag.GrandparentGroupTag.Value);
            writer.Write(tag.GroupName.Value);

            // Write dependencies
            foreach (var dependency in tag.Dependencies)
                writer.Write(dependency);

            // Write fixup pointers
            foreach (var fixup in tag.DataFixups.Concat(tag.ResourceFixups))
                writer.Write(fixup.WriteOffset + newHeaderSize + FixupPointerBase);

            // Padding
            var paddingSize = (tag.HeaderSize - (writer.BaseStream.Position - tag.HeaderOffset)) & 0xF;
            for (var i = 0; i < paddingSize; i += 4)
                writer.Write(0);
        }
 /// <summary>
 /// Updates a tag's fixup information.
 /// </summary>
 /// <param name="writer">The stream to write to.</param>
 /// <param name="tag">The tag.</param>
 private static void UpdateTagFixups(BinaryWriter writer, TagInstance tag)
 {
     var totalHeaderSize = CalculateNewHeaderSize(tag);
     foreach (var fixup in tag.DataFixups)
     {
         writer.BaseStream.Position = tag.DataOffset + fixup.WriteOffset;
         writer.Write(fixup.TargetOffset + totalHeaderSize + FixupPointerBase);
     }
 }
        /// <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 TagData BuildTagDescription(Stream stream, TagInstance tag, out uint dataOffset)
        {
            var data = new TagData
            {
                Group = tag.Group,
                MainStructOffset = tag.MainStructOffset,
            };
            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 TagPointerFixup
                {
                    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.MainStructOffset;
            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.MainStructOffset -= startOffset;
            dataOffset = startOffset;
            return data;
        }
        /// <summary>
        /// Reads the tags.dat file.
        /// </summary>
        /// <param name="reader">The stream to read from.</param>
        private void Load(BinaryReader reader)
        {
            // Read file header
            reader.BaseStream.Position = 0x4;
            var tagListOffset = reader.ReadInt32(); // 0x4 uint32 offset table offset
            var tagCount = reader.ReadInt32();      // 0x8 uint32 number of tags
            reader.BaseStream.Position = 0x10;
            Timestamp = reader.ReadInt64();         // 0x10 FILETIME timestamp

            // Read tag offset list
            var headerOffsets = new uint[tagCount];
            reader.BaseStream.Position = tagListOffset;
            for (var i = 0; i < tagCount; i++)
                headerOffsets[i] = reader.ReadUInt32();

            // Read each tag
            for (var i = 0; i < tagCount; i++)
            {
                if (headerOffsets[i] == 0)
                {
                    // Offset of 0 = null tag
                    _tags.Add(null);
                    continue;
                }
                var tag = new TagInstance { Index = i, HeaderOffset = headerOffsets[i] };
                _tags.Add(tag);
                reader.BaseStream.Position = tag.HeaderOffset;
                ReadTagHeader(reader, tag);
            }
        }
        /// <summary>
        /// Overwrites a tag's raw data, including its header.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag to overwrite.</param>
        /// <param name="data">The data to overwrite the tag with.</param>
        /// <exception cref="System.ArgumentNullException">tag</exception>
        public void SetTagDataRaw(Stream stream, TagInstance tag, byte[] data)
        {
            if (tag == null)
                throw new ArgumentNullException(nameof(tag));

            // Ensure the data fits
            if (tag.HeaderOffset < 0)
                tag.HeaderOffset = GetNewTagOffset(tag.Index);
            ResizeBlock(stream, tag, tag.HeaderOffset, tag.TotalSize, data.Length);
            tag.TotalSize = data.Length;

            // Write the data
            stream.Position = tag.HeaderOffset;
            stream.Write(data, 0, data.Length);

            // Re-parse it
            stream.Position = tag.HeaderOffset;
            tag.ReadHeader(new BinaryReader(stream));
            UpdateTagOffsets(new BinaryWriter(stream));
        }
        /// <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, TagInstance tag, TagData data)
        {
            if (tag == null)
                throw new ArgumentNullException(nameof(tag));
            if (data == null)
                throw new ArgumentNullException(nameof(data));
            if (data.Group == TagGroup.Null)
                throw new ArgumentException("Cannot assign a tag to a null tag group");
            if (data.Data == null)
                throw new ArgumentException("The tag data buffer is null");

            // Ensure the data fits
            var headerSize = TagInstance.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>
        /// Reads a tag's data from the file.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="tag">The tag to read.</param>
        /// <returns>The data that was read.</returns>
        public TagData ExtractTag(Stream stream, TagInstance tag)
        {
            if (tag == null)
                throw new ArgumentNullException(nameof(tag));
            if (tag.HeaderOffset < 0)
                throw new ArgumentException("The tag is not in the cache file");

            // Build the description info and get the data offset
            uint dataOffset;
            var data = BuildTagDescription(stream, tag, out dataOffset);

            // Read the tag data
            stream.Position = tag.HeaderOffset + dataOffset;
            data.Data = new byte[tag.TotalSize - dataOffset];
            stream.Read(data.Data, 0, data.Data.Length);

            // Correct pointers
            using (var dataWriter = new BinaryWriter(new MemoryStream(data.Data)))
            {
                foreach (var fixup in data.PointerFixups)
                {
                    dataWriter.BaseStream.Position = fixup.WriteOffset;
                    dataWriter.Write(tag.OffsetToPointer(fixup.TargetOffset));
                }
            }
            return data;
        }
        /// <summary>
        /// Reads a tag's header.
        /// </summary>
        /// <param name="reader">The stream to read from.</param>
        /// <param name="resultTag">The tag to update.</param>
        private static void ReadTagHeader(BinaryReader reader, TagInstance resultTag)
        {
            resultTag.Checksum = reader.ReadUInt32();                            // 0x00 uint32 checksum
            var totalSize = reader.ReadUInt32();                                 // 0x04 uint32 total size
            var numDependencies = reader.ReadInt16();                            // 0x08 int16  dependencies count
            var numDataFixups = reader.ReadInt16();                              // 0x0A int16  data fixup count
            var numResourceFixups = reader.ReadInt16();                          // 0x0C int16  resource fixup count
            reader.BaseStream.Position += 2;                                     // 0x0E int16  (padding)
            var mainStructOffset = reader.ReadUInt32();                          // 0x10 uint32 main struct offset
            resultTag.GroupTag = new Tag(reader.ReadInt32());            // 0x14 int32  group tag
            resultTag.ParentGroupTag = new Tag(reader.ReadInt32());      // 0x18 int32  parent group tag
            resultTag.GrandparentGroupTag = new Tag(reader.ReadInt32()); // 0x1C int32  grandparent group tag
            resultTag.GroupName = new StringId(reader.ReadUInt32());             // 0x20 uint32 group name stringid

            // Read dependencies
            resultTag.Dependencies.Clear();
            for (var j = 0; j < numDependencies; j++)
                resultTag.Dependencies.Add(reader.ReadInt32());

            // Read fixup pointers
            resultTag.DataFixups.Clear();
            for (var j = 0; j < numDataFixups; j++)
                resultTag.DataFixups.Add(new TagFixup { WriteOffset = reader.ReadUInt32() - FixupPointerBase });
            resultTag.ResourceFixups.Clear();
            for (var j = 0; j < numResourceFixups; j++)
                resultTag.ResourceFixups.Add(new TagFixup { WriteOffset = reader.ReadUInt32() - FixupPointerBase });

            // Read fixup destinations
            foreach (var fixup in resultTag.DataFixups)
                fixup.TargetOffset = ReadFixupTarget(reader, fixup.WriteOffset, resultTag.HeaderOffset);
            foreach (var fixup in resultTag.ResourceFixups)
                fixup.TargetOffset = ReadFixupTarget(reader, fixup.WriteOffset, resultTag.HeaderOffset);

            // Compute the data offset based on the smallest offset into the tag
            var smallestOffset = mainStructOffset;
            foreach (var fixup in resultTag.DataFixups.Concat(resultTag.ResourceFixups))
            {
                if (fixup.WriteOffset < smallestOffset)
                    smallestOffset = fixup.WriteOffset;
                if (fixup.TargetOffset < smallestOffset)
                    smallestOffset = fixup.TargetOffset;
            }
            resultTag.MainStructOffset = mainStructOffset - smallestOffset;
            resultTag.DataOffset = resultTag.HeaderOffset + smallestOffset;
            resultTag.DataSize = totalSize - smallestOffset;

            // Make fixups relative to the data offset
            foreach (var fixup in resultTag.DataFixups.Concat(resultTag.ResourceFixups))
            {
                fixup.TargetOffset -= smallestOffset;
                fixup.WriteOffset -= smallestOffset;
            }
        }
Exemple #36
0
 /// <summary>
 /// Calculates the size of a tag's header after it will be updated in the file.
 /// </summary>
 /// <param name="tag">The tag.</param>
 /// <returns>The new size of the tag's header.</returns>
 private static uint CalculateNewHeaderSize(TagInstance tag)
 {
     return(CalculateHeaderSize(tag.Dependencies.Count, tag.DataFixups.Count, tag.ResourceFixups.Count));
 }
 /// <summary>
 /// Rebases fixup pointers in tag data.
 /// </summary>
 /// <param name="tag">The tag.</param>
 /// <param name="data">The data.</param>
 /// <param name="newBase">The new base offset to use.</param>
 private static void RebasePointers(TagInstance tag, byte[] data, uint newBase)
 {
     using (var writer = new BinaryWriter(new MemoryStream(data)))
     {
         foreach (var fixup in tag.DataFixups)
         {
             writer.BaseStream.Position = fixup.WriteOffset;
             writer.Write(newBase + fixup.TargetOffset + FixupPointerBase);
         }
     }
 }
 /// <summary>
 /// Reads a tag's data from the file, not including its header.
 /// Any pointers in the tag will be adjusted to be relative to the start of the tag's data.
 /// </summary>
 /// <param name="stream">The stream to read from.</param>
 /// <param name="tag">The tag to read.</param>
 /// <returns>The data that was read.</returns>
 public byte[] ExtractTagData(Stream stream, TagInstance tag)
 {
     if (tag == null)
         throw new ArgumentNullException("tag");
     if (tag.HeaderOffset < 0)
         throw new ArgumentException("The tag is not in the cache file");
     stream.Position = tag.DataOffset;
     var result = new byte[tag.DataSize];
     stream.Read(result, 0, result.Length);
     RebasePointers(tag, result, 0);
     return result;
 }
 /// <summary>
 /// Fixes tag offsets after a resize operation.
 /// </summary>
 /// <param name="startOffset">The offset where the resize operation took place.</param>
 /// <param name="sizeDelta">The amount to add to each tag offset after the start offset.</param>
 /// <param name="ignore">A tag to ignore.</param>
 private void FixTagOffsets(long startOffset, long sizeDelta, TagInstance ignore)
 {
     foreach (var adjustTag in _tags.Where(t => t != null && t != ignore && t.HeaderOffset >= startOffset))
     {
         adjustTag.HeaderOffset += sizeDelta;
         adjustTag.DataOffset += sizeDelta;
     }
 }
Exemple #40
0
        /// <summary>
        /// Reads a tag's header.
        /// </summary>
        /// <param name="reader">The stream to read from.</param>
        /// <param name="resultTag">The tag to update.</param>
        private static void ReadTagHeader(BinaryReader reader, TagInstance resultTag)
        {
            resultTag.Checksum = reader.ReadUInt32();                          // 0x00 uint32 checksum
            var totalSize         = reader.ReadUInt32();                       // 0x04 uint32 total size
            var numDependencies   = reader.ReadInt16();                        // 0x08 int16  dependencies count
            var numDataFixups     = reader.ReadInt16();                        // 0x0A int16  data fixup count
            var numResourceFixups = reader.ReadInt16();                        // 0x0C int16  resource fixup count

            reader.BaseStream.Position += 2;                                   // 0x0E int16  (padding)
            var mainStructOffset = reader.ReadUInt32();                        // 0x10 uint32 main struct offset

            resultTag.GroupTag            = new Tag(reader.ReadInt32());       // 0x14 int32  group tag
            resultTag.ParentGroupTag      = new Tag(reader.ReadInt32());       // 0x18 int32  parent group tag
            resultTag.GrandparentGroupTag = new Tag(reader.ReadInt32());       // 0x1C int32  grandparent group tag
            resultTag.GroupName           = new StringId(reader.ReadUInt32()); // 0x20 uint32 group name stringid

            // Read dependencies
            resultTag.Dependencies.Clear();
            for (var j = 0; j < numDependencies; j++)
            {
                resultTag.Dependencies.Add(reader.ReadInt32());
            }

            // Read fixup pointers
            resultTag.DataFixups.Clear();
            for (var j = 0; j < numDataFixups; j++)
            {
                resultTag.DataFixups.Add(new TagFixup {
                    WriteOffset = reader.ReadUInt32() - FixupPointerBase
                });
            }
            resultTag.ResourceFixups.Clear();
            for (var j = 0; j < numResourceFixups; j++)
            {
                resultTag.ResourceFixups.Add(new TagFixup {
                    WriteOffset = reader.ReadUInt32() - FixupPointerBase
                });
            }

            // Read fixup destinations
            foreach (var fixup in resultTag.DataFixups)
            {
                fixup.TargetOffset = ReadFixupTarget(reader, fixup.WriteOffset, resultTag.HeaderOffset);
            }
            foreach (var fixup in resultTag.ResourceFixups)
            {
                fixup.TargetOffset = ReadFixupTarget(reader, fixup.WriteOffset, resultTag.HeaderOffset);
            }

            // Compute the data offset based on the smallest offset into the tag
            var smallestOffset = mainStructOffset;

            foreach (var fixup in resultTag.DataFixups.Concat(resultTag.ResourceFixups))
            {
                if (fixup.WriteOffset < smallestOffset)
                {
                    smallestOffset = fixup.WriteOffset;
                }
                if (fixup.TargetOffset < smallestOffset)
                {
                    smallestOffset = fixup.TargetOffset;
                }
            }
            resultTag.MainStructOffset = mainStructOffset - smallestOffset;
            resultTag.DataOffset       = resultTag.HeaderOffset + smallestOffset;
            resultTag.DataSize         = totalSize - smallestOffset;

            // Make fixups relative to the data offset
            foreach (var fixup in resultTag.DataFixups.Concat(resultTag.ResourceFixups))
            {
                fixup.TargetOffset -= smallestOffset;
                fixup.WriteOffset  -= smallestOffset;
            }
        }
 /// <summary>
 /// Resizes a block of data in the file and updates tag offsets.
 /// </summary>
 /// <param name="stream">The stream.</param>
 /// <param name="tag">The tag that the block belongs to, if any.</param>
 /// <param name="startOffset">The offset where the block to resize begins at.</param>
 /// <param name="oldSize">The current size of the block to resize.</param>
 /// <param name="newSize">The new size of the block.</param>
 /// <exception cref="System.ArgumentException">Cannot resize a block to a negative size</exception>
 private void ResizeBlock(Stream stream, TagInstance tag, long startOffset, long oldSize, long newSize)
 {
     if (newSize < 0)
         throw new ArgumentException("Cannot resize a block to a negative size");
     var oldEndOffset = startOffset + oldSize;
     var sizeDelta = newSize - oldSize;
     StreamUtil.Copy(stream, oldEndOffset, oldEndOffset + sizeDelta, stream.Length - oldEndOffset);
     FixTagOffsets(oldEndOffset, sizeDelta, tag);
 }
        /// <summary>
        /// Duplicates a tag.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag to duplicate.</param>
        /// <returns>The new tag.</returns>
        public TagInstance DuplicateTag(Stream stream, TagInstance tag)
        {
            if (tag == null)
                throw new ArgumentNullException(nameof(tag));

            // Just extract the tag and add it back
            var result = AllocateTag();
            SetTagDataRaw(stream, result, ExtractTagRaw(stream, tag));
            return result;
        }
 /// <summary>
 /// Allocates a new tag at the end of the tag list without updating the file.
 /// You can give the tag data by using one of the overwrite functions.
 /// </summary>
 /// <returns>The allocated tag.</returns>
 public TagInstance AllocateTag()
 {
     var tagIndex = _tags.Count;
     var tag = new TagInstance { Index = tagIndex };
     _tags.Add(tag);
     return tag;
 }
        /// <summary>
        /// Duplicates a tag.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="tag">The tag to duplicate.</param>
        /// <returns>The new tag.</returns>
        public TagInstance DuplicateTag(Stream stream, TagInstance tag)
        {
            if (tag == null)
                throw new ArgumentNullException("tag");

            // Just extract the tag and add it back
            var result = AllocateTag();
            OverwriteFullTag(stream, result, ExtractFullTag(stream, tag));
            return result;
        }