/// <summary> /// Attempts to get a tag of a specific type from the current cache. /// </summary> /// <typeparam name="T">The type of the tag definition.</typeparam> /// <param name="name">The name of the tag.</param> /// <param name="result">The resulting tag.</param> /// <returns>True if the tag was found, false otherwise.</returns> public bool TryGetTag <T>(string name, out CachedTagInstance result) where T : TagStructure { if (name == "none" || name == "null") { result = null; return(true); } if (Tags.TagDefinition.Types.Values.Contains(typeof(T))) { var groupTag = Tags.TagDefinition.Types.First((KeyValuePair <Tag, Type> entry) => entry.Value == typeof(T)).Key; foreach (var instance in TagCache.Index) { if (instance is null) { continue; } if (instance.IsInGroup(groupTag) && instance.Name == name) { result = instance; return(true); } } } result = null; return(false); }
/// <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, CachedTagInstance ignore) { foreach (var adjustTag in _tags.Where(t => t != null && t != ignore && t.HeaderOffset >= startOffset)) { adjustTag.HeaderOffset += sizeDelta; } }
/// <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 CachedTagData ExtractTag(Stream stream, CachedTagInstance tag) { if (tag == null) { throw new ArgumentNullException(nameof(tag)); } else if (tag.HeaderOffset < 0) { throw new ArgumentException("The tag is not in the cache file"); } // Build the description info and get the data offset var data = BuildTagData(stream, tag, out uint 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> /// 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> /// <param name="name">The name of the tag instance.</param> /// <returns>The allocated tag.</returns> public CachedTagInstance AllocateTag(TagGroup type, string name = null) { var tagIndex = _tags.Count; var tag = new CachedTagInstance(tagIndex, type, name); _tags.Add(tag); return(tag); }
public void Serialize(Stream stream, CachedTagInstance instance, object definition) { if (!ModifiedTags.Contains(instance.Index)) { SignalModifiedTag(instance.Index); } Serializer.Serialize(new TagSerializationContext(stream, this, instance), definition); }
/// <summary> /// Attempts to get a tag from the current cache. /// </summary> /// <param name="index">The index of the tag.</param> /// <param name="instance">The tag at the specified index from the current cache.</param> /// <returns>true if the index is within the range of the tag cache, false otherwise.</returns> public bool TryGetTag(int index, out CachedTagInstance instance) { if (index < 0 || index >= TagCache.Index.Count) { instance = null; return(false); } instance = TagCache.Index[index]; return(true); }
/// <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 CachedTagInstance DuplicateTag(Stream stream, CachedTagInstance tag) { if (tag == null) { throw new ArgumentNullException(nameof(tag)); } // Just extract the tag and add it back var result = AllocateTag(tag.Group); SetTagDataRaw(stream, result, ExtractTagRaw(stream, tag)); return(result); }
/// <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> /// Reads the tags.dat file. /// </summary> /// <param name="reader">The stream to read from.</param> /// <param name="names">The dictionary of tag instance names.</param> private void Load(BinaryReader reader, Dictionary <int, string> names) { // 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; } string name = null; if (names.ContainsKey(i)) { name = names[i]; } var tag = new CachedTagInstance(i, name) { HeaderOffset = headerOffsets[i] }; _tags.Add(tag); reader.BaseStream.Position = tag.HeaderOffset; tag.ReadHeader(reader); } }
/// <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, CachedTagInstance tag) { if (tag == null) { throw new ArgumentNullException(nameof(tag)); } else if (tag.HeaderOffset < 0) { throw new ArgumentException("The tag is not in the cache file"); } var result = new byte[tag.TotalSize]; stream.Position = tag.HeaderOffset; stream.Read(result, 0, result.Length); return(result); }
/// <summary> /// Resizes a block of data in the file. /// </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, CachedTagInstance tag, long startOffset, long oldSize, long newSize) { if (newSize < 0) { throw new ArgumentException("Cannot resize a block to a negative size"); } else if (oldSize == newSize) { return; } var oldEndOffset = startOffset + oldSize; var sizeDelta = newSize - oldSize; StreamUtil.Copy(stream, oldEndOffset, oldEndOffset + sizeDelta, stream.Length - oldEndOffset); FixTagOffsets(oldEndOffset, sizeDelta, tag); }
public bool TryAllocateTag(out CachedTagInstance result, Type type, string name = null) { result = null; try { var structure = TagStructure.GetTagStructureInfo(type, Version).Structure; if (structure == null) { Console.WriteLine($"TagStructure attribute not found for type \"{type.Name}\"."); return(false); } var groupTag = new Tag(structure.Tag); if (!TagGroup.Instances.ContainsKey(groupTag)) { Console.WriteLine($"TagGroup not found for type \"{type.Name}\" ({structure.Tag})."); return(false); } result = TagCache.AllocateTag(TagGroup.Instances[groupTag], name); if (result == null) { return(false); } } catch (Exception e) { Console.WriteLine($"{e.GetType().Name}: {e.Message}"); return(false); } return(true); }
/// <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, CachedTagInstance 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> /// Attempts to get a tag by parsing its group name. /// </summary> /// <param name="name">The full name of the tag.</param> /// <param name="result">The resulting tag.</param> /// <returns>True if the tag was found, false otherwise.</returns> public bool TryGetTag(string name, out CachedTagInstance result) { if (name.Length == 0) { result = null; return(false); } if (name == "null") { result = null; return(true); } if (name == "*") { if (TagCache.Index.Count == 0) { result = null; return(false); } result = TagCache.Index.Last(); return(true); } if (name.StartsWith("*.")) { if (!name.TrySplit('.', out var startNamePieces) || !TryParseGroupTag(startNamePieces[1], out var starGroupTag)) { result = null; return(false); } result = TagCache.Index.Last(tag => tag.IsInGroup(starGroupTag)); return(true); } if (name.StartsWith("0x")) { name = name.Substring(2); if (name.TrySplit('.', out var hexNamePieces)) { name = hexNamePieces[0]; } if (!int.TryParse(name, NumberStyles.HexNumber, null, out int tagIndex) || !TagCache.Index.Contains(tagIndex)) { result = null; return(false); } result = TagCache.Index[tagIndex]; return(true); } if (!name.TrySplit('.', out var namePieces) || !TryParseGroupTag(namePieces[1], out var groupTag)) { throw new Exception($"Invalid tag name: {name}"); } var tagName = namePieces[0]; foreach (var instance in TagCache.Index) { if (instance is null) { continue; } if (instance.IsInGroup(groupTag) && instance.Name == tagName) { result = instance; return(true); } } result = null; return(false); }
public T Deserialize <T>(Stream stream, CachedTagInstance instance) => Deserialize <T>(new TagSerializationContext(stream, this, instance));
public object Deserialize(Stream stream, CachedTagInstance instance) => Deserialize(new TagSerializationContext(stream, this, instance), Tags.TagDefinition.Find(instance.Group.Tag));
public void Serialize(Stream stream, CachedTagInstance instance, object definition) => Serializer.Serialize(new TagSerializationContext(stream, this, instance), definition);
public void NullTag(Stream stream, CachedTagInstance tag) { Index[tag.Index] = null; SetTagDataRaw(stream, tag, new byte[] { }); }