private static CacheVersion DetectCacheVersion(EndianReader reader) { var version = GetMapFileVersion(reader); var buildDate = GetBuildDate(reader, version); return(CacheVersionDetection.GetFromBuildName(buildDate)); }
public StringTableHaloOnline(CacheVersion version, Stream stream) { Version = version; Resolver = null; if (CacheVersionDetection.Compare(Version, CacheVersion.HaloOnline700123) >= 0) { Resolver = new StringIdResolverMS30(); } else if (CacheVersionDetection.Compare(Version, CacheVersion.HaloOnline498295) >= 0) { Resolver = new StringIdResolverMS28(); } else { Resolver = new StringIdResolverMS23(); } if (stream != null && stream.Length != 0) { Load(stream); } else { CreateFromStrings(); } }
public GameCacheHaloOnline(DirectoryInfo directory) { Directory = directory; TagsFile = new FileInfo(Path.Combine(directory.FullName, "tags.dat")); TagNamesFile = new FileInfo(Path.Combine(directory.FullName, "tag_list.csv")); StringIdCacheFile = new FileInfo(Path.Combine(directory.FullName, "string_ids.dat")); Endianness = EndianFormat.LittleEndian; var names = TagCacheHaloOnline.LoadTagNames(TagNamesFile.FullName); using (var stream = TagsFile.OpenRead()) TagCacheGenHO = new TagCacheHaloOnline(stream, names); if (CacheVersion.Unknown == (Version = CacheVersionDetection.DetectFromTimestamp(TagCacheGenHO.Header.CreationTime, out var closestVersion))) { Version = closestVersion; } using (var stream = StringIdCacheFile.OpenRead()) StringTableHaloOnline = new StringTableHaloOnline(Version, stream); DisplayName = Version.ToString(); Deserializer = new TagDeserializer(Version); Serializer = new TagSerializer(Version); ResourceCaches = new ResourceCachesHaloOnline(this); }
public TagStructureAttribute GetTagStructureAttribute(Type type, CacheVersion version = CacheVersion.Unknown) { if (!TagStructureAttributes.TryGetValue(type, out TagStructureAttribute attribute)) { lock (TagStructureAttributes) { if (!TagStructureAttributes.TryGetValue(type, out attribute)) { TagStructureAttributes[type] = attribute = GetStructureAttribute(); } } } return(attribute); TagStructureAttribute GetStructureAttribute() { // First match against any TagStructureAttributes that have version restrictions var attrib = type.GetCustomAttributes(typeof(TagStructureAttribute), false) .Cast <TagStructureAttribute>() .Where(a => a.MinVersion != CacheVersion.Unknown || a.MaxVersion != CacheVersion.Unknown) .FirstOrDefault(a => CacheVersionDetection.IsBetween(version, a.MinVersion, a.MaxVersion)); // If nothing was found, find the first attribute without any version restrictions return(attrib ?? type.GetCustomAttributes(typeof(TagStructureAttribute), false) .Cast <TagStructureAttribute>() .FirstOrDefault(a => a.MinVersion == CacheVersion.Unknown && a.MaxVersion == CacheVersion.Unknown)); } }
private MapFile GenerateMapFile(CachedTagInstance scenarioTag, Blf mapInfo = null) { MapFile map = new MapFile(); var header = new MapFileHeader(); Scenario scnr; using (var stream = CacheContext.OpenTagCacheRead()) { var deserializer = new TagDeserializer(CacheContext.Version); scnr = (Scenario)CacheContext.Deserialize(stream, scenarioTag); } map.Version = CacheContext.Version; map.EndianFormat = EndianFormat.LittleEndian; map.MapVersion = MapFileVersion.HaloOnline; header.HeadTag = new Tag("head"); header.FootTag = new Tag("foot"); header.Version = (int)map.MapVersion; header.Build = CacheVersionDetection.GetBuildName(CacheContext.Version); switch (scnr.MapType) { case ScenarioMapType.MainMenu: header.CacheType = CacheFileType.MainMenu; break; case ScenarioMapType.SinglePlayer: header.CacheType = CacheFileType.Campaign; break; case ScenarioMapType.Multiplayer: header.CacheType = CacheFileType.Multiplayer; break; } header.SharedType = CacheFileSharedType.None; header.MapId = scnr.MapId; header.ScenarioTagIndex = scenarioTag.Index; header.Name = scenarioTag.Name.Split('\\').Last(); header.ScenarioPath = scenarioTag.Name; map.Header = header; header.FileLength = 0x3390; if (mapInfo != null) { if (mapInfo.ContentFlags.HasFlag(BlfFileContentFlags.StartOfFile) && mapInfo.ContentFlags.HasFlag(BlfFileContentFlags.EndOfFile) && mapInfo.ContentFlags.HasFlag(BlfFileContentFlags.Scenario)) { map.MapFileBlf = mapInfo; } } return(map); }
/// <summary> /// Creates a vertex stream for a given engine version. /// </summary> /// <param name="version">The engine version.</param> /// <param name="stream">The base stream.</param> /// <returns>The created vertex stream.</returns> public static IVertexStream Create(CacheVersion version, Stream stream) { if (CacheVersionDetection.Compare(version, CacheVersion.Halo3ODST) <= 0) { return(new VertexStreamXbox(stream)); } if (CacheVersionDetection.Compare(version, CacheVersion.HaloOnline235640) >= 0) { return(new VertexStreamMS25(stream)); } return(new VertexStreamMS23(stream)); }
private static bool IsModifiedReachFormat(EndianReader reader) { reader.SeekTo(0x120); var version = CacheVersionDetection.GetFromBuildName(reader.ReadString(0x20)); if (version == CacheVersion.Unknown) { return(false); } else { return(true); } }
/// <summary> /// Deserializes a property of a structure. /// </summary> /// <param name="reader">The reader.</param> /// <param name="context">The serialization context to use.</param> /// <param name="instance">The instance to store the property to.</param> /// <param name="tagFieldInfo">The active element enumerator.</param> /// <param name="baseOffset">The offset of the start of the structure.</param> /// <exception cref="System.InvalidOperationException">Offset for property is outside of its structure</exception> public void DeserializeProperty(EndianReader reader, ISerializationContext context, object instance, TagFieldInfo tagFieldInfo, long baseOffset) { var attr = tagFieldInfo.Attribute; if (attr.Flags.HasFlag(Runtime) || !CacheVersionDetection.AttributeInCacheVersion(attr, Version)) { return; } if (tagFieldInfo.FieldType.IsArray && attr.Flags.HasFlag(Relative)) { var type = instance.GetType(); var field = type.GetField( attr.Field, BindingFlags.Instance | BindingFlags.Public); var attr2 = TagStructure.GetTagFieldAttribute(type, field); if (CacheVersionDetection.AttributeInCacheVersion(attr2, Version)) { attr.Length = (int)Convert.ChangeType(field.GetValue(instance), typeof(int)); } else { throw new InvalidOperationException(attr2.Field); } } if (attr.Flags.HasFlag(Padding)) { #if DEBUG var unused = reader.ReadBytes(attr.Length); foreach (var b in unused) { if (b != 0) { Console.WriteLine($"WARNING: non-zero padding found in {tagFieldInfo.FieldInfo.DeclaringType.FullName}.{tagFieldInfo.FieldInfo.Name} = {b}"); break; } } #else reader.BaseStream.Position += attr.Length; #endif } else { var value = DeserializeValue(reader, context, attr, tagFieldInfo.FieldType); tagFieldInfo.SetValue(instance, value); } }
/// <summary> /// Writes the map out to a CSV. /// </summary> /// <param name="writer">The writer to write to.</param> public void WriteCsv(TextWriter writer) { // Write a list of versions being represented writer.WriteLine(string.Join(",", _versionMaps.Keys.Select(CacheVersionDetection.GetBuildName))); // Write a list of timestamps for the versions writer.WriteLine(string.Join(",", _versionMaps.Keys.Select(v => CacheVersionDetection.GetTimestamp(v).ToString("X16")))); // Now write out each tag for (var i = 0; i < _nextGlobalTagIndex; i++) { var globalIndex = i; writer.WriteLine(string.Join(",", _versionMaps.Keys.Select(v => _versionMaps[v].GetVersionedTagIndex(globalIndex).ToString("X4")))); } }
private ResourceLocation FixResourceLocation(ResourceLocation location, CacheVersion srcVersion, CacheVersion destVersion) { if (CacheVersionDetection.Compare(destVersion, CacheVersion.HaloOnline235640) >= 0) { return(location); } switch (location) { case ResourceLocation.RenderModels: return(ResourceLocation.Resources); case ResourceLocation.Lightmaps: return(ResourceLocation.Textures); } return(location); }
public static void ConvertIndexBuffer(CacheVersion inVersion, CacheVersion outVersion, IndexBufferDefinition indexBuffer) { using (var outputStream = new MemoryStream()) using (var inputStream = new MemoryStream(indexBuffer.Data.Data)) { var inIndexStream = new IndexBufferStream(inputStream, CacheVersionDetection.IsLittleEndian(inVersion) ? EndianFormat.LittleEndian : EndianFormat.BigEndian); var outIndexStream = new IndexBufferStream(outputStream, CacheVersionDetection.IsLittleEndian(outVersion) ? EndianFormat.LittleEndian : EndianFormat.BigEndian); var indexCount = indexBuffer.Data.Data.Length / 2; for (var j = 0; j < indexCount; j++) { outIndexStream.WriteIndex(inIndexStream.ReadIndex()); } indexBuffer.Data.Data = outputStream.ToArray(); } }
public static uint GetSize(this Type type, CacheVersion version = CacheVersion.Unknown) { if (TypeSizes.ContainsKey(type.FullName)) { return(TypeSizes[type.FullName]); } var attr = type.GetCustomAttributes(typeof(TagStructureAttribute), false) .Cast <TagStructureAttribute>() .FirstOrDefault(a => version == CacheVersion.Unknown || CacheVersionDetection.IsBetween(version, a.MinVersion, a.MaxVersion)); if (attr == null) { throw new NotSupportedException(type.FullName); } return(TypeSizes[type.FullName] = attr.Size); }
public static byte[] ConvertHkpMoppData(CacheVersion sourceVersion, CacheVersion destVersion, byte[] data) { if (data == null || data.Length == 0) { return(data); } byte[] result; using (var inputReader = new EndianReader(new MemoryStream(data), CacheVersionDetection.IsLittleEndian(sourceVersion) ? EndianFormat.LittleEndian : EndianFormat.BigEndian)) using (var outputStream = new MemoryStream()) using (var outputWriter = new EndianWriter(outputStream, CacheVersionDetection.IsLittleEndian(destVersion) ? EndianFormat.LittleEndian : EndianFormat.BigEndian)) { var dataContext = new DataSerializationContext(inputReader, outputWriter); var deserializer = new TagDeserializer(sourceVersion); var serializer = new TagSerializer(destVersion); while (!inputReader.EOF) { var header = deserializer.Deserialize <HkpMoppCode>(dataContext); var dataSize = header.ArrayBase.GetCapacity(); var alignedSize = dataSize + 0xf & ~0xf; var nextOffset = inputReader.Position + alignedSize; List <byte> moppCodes = new List <byte>(); for (int j = 0; j < header.ArrayBase.GetCapacity(); j++) { moppCodes.Add(inputReader.ReadByte()); } inputReader.SeekTo(nextOffset); moppCodes = ConvertMoppCodes(sourceVersion, destVersion, moppCodes); serializer.Serialize(dataContext, header); for (int j = 0; j < moppCodes.Count; j++) { outputWriter.Write(moppCodes[j]); } StreamUtil.Align(outputStream, 0x10); } result = outputStream.ToArray(); } return(result); }
/// <summary> /// Builds the <see cref="Tags.TagFieldInfo"/> <see cref="List{T}"/> to be enumerated. /// </summary> private void Build() { uint offset = 0; // Build the field list. Scan through the type's inheritance // hierarchy and add any fields belonging to tag structures. foreach (var type in Info.Types.Reverse <Type>()) { // Ensure that fields are in declaration order - GetFields does NOT guarantee foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly).OrderBy(i => i.MetadataToken)) { var attr = TagStructure.GetTagFieldAttribute(type, field); if (CacheVersionDetection.AttributeInCacheVersion(attr, Info.Version)) { CreateTagFieldInfo(field, attr, Info.Version, ref offset); } } } }
/// <summary> /// Deserializes a property of a structure. /// </summary> /// <param name="reader">The reader.</param> /// <param name="context">The serialization context to use.</param> /// <param name="instance">The instance to store the property to.</param> /// <param name="tagFieldInfo">The active element enumerator.</param> /// <param name="baseOffset">The offset of the start of the structure.</param> /// <exception cref="System.InvalidOperationException">Offset for property is outside of its structure</exception> public void DeserializeProperty(EndianReader reader, ISerializationContext context, object instance, TagFieldInfo tagFieldInfo, long baseOffset) { var attr = tagFieldInfo.Attribute; if (attr.Flags.HasFlag(TagFieldFlags.Runtime)) { return; } if (tagFieldInfo.Attribute.Flags.HasFlag(TagFieldFlags.Padding)) { reader.BaseStream.Position += tagFieldInfo.Attribute.Length; } else { if ((attr.Version != CacheVersion.Unknown && attr.Version == Version) || (attr.Version == CacheVersion.Unknown && CacheVersionDetection.IsBetween(Version, attr.MinVersion, attr.MaxVersion))) { var value = DeserializeValue(reader, context, attr, tagFieldInfo.FieldType); tagFieldInfo.SetValue(instance, value); } } }
/// <summary> /// Gets the size of a tag-field. /// </summary> /// <param name="type">The <see cref="Type"/> of the field.</param> /// <param name="attr">The <see cref="TagFieldAttribute"/> of the field.</param> /// <param name="targetVersion">The <see cref="CacheVersion"/> to target.</param> /// <returns></returns> public static uint GetFieldSize(Type type, TagFieldAttribute attr, CacheVersion targetVersion) { switch (Type.GetTypeCode(type)) { case TypeCode.Boolean: case TypeCode.SByte: case TypeCode.Byte: return(0x01); case TypeCode.Char: case TypeCode.Int16: case TypeCode.UInt16: return(0x02); case TypeCode.Single: case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Object when attr != null && attr.Flags.HasFlag(TagFieldFlags.Pointer): case TypeCode.Object when type == typeof(Tag): case TypeCode.Object when type == typeof(CacheAddress): case TypeCode.Object when type == typeof(CachedTagInstance) && attr.Flags.HasFlag(TagFieldFlags.Short): //case TypeCode.Object when type == typeof(RgbColor): case TypeCode.Object when type == typeof(ArgbColor): case TypeCode.Object when type == typeof(Point2d): case TypeCode.Object when type == typeof(StringId): case TypeCode.Object when type == typeof(Angle): case TypeCode.Object when type == typeof(VertexShaderReference): case TypeCode.Object when type == typeof(PixelShaderReference): return(0x04); case TypeCode.Double: case TypeCode.Int64: case TypeCode.UInt64: case TypeCode.Object when type == typeof(CachedTagInstance) && targetVersion != CacheVersion.Unknown && CacheVersionDetection.IsBetween(targetVersion, CacheVersion.Halo2Xbox, CacheVersion.Halo2Vista): case TypeCode.Object when type == typeof(byte[]) && targetVersion != CacheVersion.Unknown && CacheVersionDetection.IsBetween(targetVersion, CacheVersion.Halo2Xbox, CacheVersion.Halo2Vista): case TypeCode.Object when type == typeof(Rectangle2d): case TypeCode.Object when type == typeof(RealEulerAngles2d): case TypeCode.Object when type == typeof(RealPoint2d): case TypeCode.Object when type == typeof(RealVector2d): case TypeCode.Object when type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List <>) && targetVersion != CacheVersion.Unknown && CacheVersionDetection.IsBetween(targetVersion, CacheVersion.Halo2Xbox, CacheVersion.Halo2Vista): case TypeCode.Object when type.IsGenericType && type.GetGenericTypeDefinition() == typeof(TagBlock <>) && targetVersion != CacheVersion.Unknown && CacheVersionDetection.IsBetween(targetVersion, CacheVersion.Halo2Xbox, CacheVersion.Halo2Vista): return(0x08); case TypeCode.Object when type == typeof(RealRgbColor): case TypeCode.Object when type == typeof(RealEulerAngles3d): case TypeCode.Object when type == typeof(RealPoint3d): case TypeCode.Object when type == typeof(RealVector3d): case TypeCode.Object when type == typeof(RealPlane2d): case TypeCode.Object when type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List <>) && CacheVersionDetection.IsBetween(targetVersion, CacheVersion.Halo3Retail, CacheVersion.Unknown): case TypeCode.Object when type.IsGenericType && type.GetGenericTypeDefinition() == typeof(TagBlock <>) && CacheVersionDetection.IsBetween(targetVersion, CacheVersion.Halo3Retail, CacheVersion.Unknown): return(0x0C); case TypeCode.Decimal: case TypeCode.Object when type == typeof(CachedTagInstance) && CacheVersionDetection.IsBetween(targetVersion, CacheVersion.Halo3Retail, CacheVersion.Unknown): case TypeCode.Object when type == typeof(RealArgbColor): case TypeCode.Object when type == typeof(RealQuaternion): case TypeCode.Object when type == typeof(RealPlane3d): return(0x10); case TypeCode.Object when type == typeof(byte[]) && CacheVersionDetection.IsBetween(targetVersion, CacheVersion.Halo3Retail, CacheVersion.Unknown): return(0x14); case TypeCode.Object when type == typeof(RealMatrix4x3): return(0x30); case TypeCode.Object when type == typeof(DatumIndex): return(sizeof(uint)); case TypeCode.String: case TypeCode.Object when type.IsArray: return((uint)attr.Length); case TypeCode.Object when type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Bounds <>): return(TagFieldInfo.GetFieldSize(type.GenericTypeArguments[0], attr, targetVersion) * 2); case TypeCode.Object when type.IsEnum: return(TagFieldInfo.GetFieldSize(type.GetEnumUnderlyingType(), attr, targetVersion)); // Assume the field is a structure default: return(TagStructure.GetTagStructureInfo(type, targetVersion).TotalSize); } }
private ExportedType ExportType(Type type, FileInfo file) { if (file.Name == ".hpp") { return(null); } if (ExportedTypes.ContainsKey(type.FullName)) { return(ExportedTypes[type.FullName]); } using (var writer = file.CreateText()) { writer.WriteLine("#pragma once"); writer.WriteLine("#include \"../cseries/cseries.hpp\""); writer.WriteLine("#include \"../math/real_math.hpp\""); writer.WriteLine("#include \"../tag_files/tag_groups.hpp\""); foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly).OrderBy(i => i.MetadataToken)) { if (field.FieldType.IsPrimitive) { continue; } var info = type.GetCustomAttributes <TagStructureAttribute>(false).Where(attr => CacheVersionDetection.IsBetween(CacheContext.Version, attr.MinVersion, attr.MaxVersion)).First(); var typeBaseName = field.FieldType.FullName.ToSnakeCase().Replace("._", "."); typeBaseName = typeBaseName.Substring(typeBaseName.LastIndexOf('.') + 1).Replace("+", ""); while (typeBaseName.StartsWith("_")) { typeBaseName = typeBaseName.Substring(1); } ExportedType typeInfo = null; if (ExportedTypes.ContainsKey(field.FieldType.FullName)) { typeInfo = ExportedTypes[field.FieldType.FullName]; continue; } if (field.FieldType.IsArray) { } else if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(List <>)) { typeBaseName = field.FieldType.GetGenericArguments()[0].FullName .ToSnakeCase().Replace("._", ".").Replace("+", ""); typeBaseName = typeBaseName.Substring(typeBaseName.LastIndexOf('.') + 1); while (typeBaseName.StartsWith("_")) { typeBaseName = typeBaseName.Substring(1); } ExportedTypes[field.FieldType.FullName] = new ExportedType { Name = $"c_tag_block<s_{typeBaseName}>", File = file.FullName }; typeInfo = ExportedTypes[field.FieldType.GetGenericArguments()[0].FullName] = new ExportedType { Name = $"s_{typeBaseName}", File = file.FullName }; } else if (field.FieldType.IsEnum) { if (typeBaseName.EndsWith("_value")) { typeBaseName = typeBaseName.Substring(0, typeBaseName.Length - 6); } typeInfo = ExportedTypes[field.FieldType.FullName] = new ExportedType { Name = $"e_{typeBaseName}", File = file.FullName }; } else if (field.FieldType.IsSubclassOf(typeof(TagStructure))) { typeInfo = ExportedTypes[field.FieldType.FullName] = new ExportedType { Name = $"s_{typeBaseName}", File = file.FullName }; } if (field.FieldType.IsArray) { } else if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(List <>)) { writer.WriteLine(); if (typeInfo.Name.Contains('.')) { break; } writer.WriteLine($"struct {typeInfo.Name}"); writer.WriteLine("{"); writer.WriteLine("};"); } else if (field.FieldType.IsEnum) { // // Export the enum // writer.WriteLine(); writer.WriteLine($"enum {typeInfo.Name}"); writer.WriteLine("{"); var isFlags = field.FieldType.GetCustomAttribute <FlagsAttribute>(false) != null; foreach (var option in Enum.GetValues(field.FieldType)) { var optionValue = (int)Convert.ChangeType(option, typeof(int)); if (isFlags) { if (optionValue == 0) { continue; } var bitSet = false; for (var i = 0; i < 32; i++) { if ((optionValue & (1 << i)) != 0) { if (bitSet) { throw new NotSupportedException("multiple bits set in flags enum"); } optionValue = i; bitSet = true; } } } writer.WriteLine($"\t_{typeBaseName}_{option.ToString().ToSnakeCase()} = {optionValue},"); } writer.WriteLine("};"); } else if (field.FieldType.IsSubclassOf(typeof(TagStructure))) { writer.WriteLine(); writer.WriteLine($"struct {typeInfo.Name}"); writer.WriteLine("{"); writer.WriteLine("};"); } } } return(ExportedTypes.ContainsKey(type.FullName) ? ExportedTypes[type.FullName] : null); }
private static void CompareBlocks(object dataA, CacheVersion versionA, object dataB, CacheVersion versionB, TagVersionMap result, Queue <QueuedTag> tagQueue) { if (dataA == null || dataB == null) { return; } if (dataA is CachedTagInstance) { // If the objects are tags, then we've found a match var tagA = dataA as CachedTagInstance; var tagB = dataB as CachedTagInstance; if (tagA.Group.Tag != tagB.Group.Tag) { return; } if (tagA.IsInGroup("rmt2") || tagA.IsInGroup("rmdf") || tagA.IsInGroup("vtsh") || tagA.IsInGroup("pixl") || tagA.IsInGroup("rm ") || tagA.IsInGroup("bitm")) { return; } var translated = result.Translate(versionA, tagA.Index, versionB); if (translated >= 0) { return; } result.Add(versionA, tagA.Index, versionB, tagB.Index); tagQueue.Enqueue(new QueuedTag { Tag = tagB }); } else if (dataA is IList) // Compare lists and arrays { var collectionA = dataA as IList; var collectionB = dataB as IList; if (collectionA.Count == 0 || collectionA[0].GetType().IsPrimitive) { return; } // If the sizes are different, we probably can't compare them if (collectionA.Count != collectionB.Count) { return; } // Compare each element for (var i = 0; i < collectionA.Count; i++) { var itemA = collectionA[i]; var itemB = collectionB[i]; CompareBlocks(itemA, versionA, itemB, versionB, result, tagQueue); } } else if (dataA is TagStructure) { var tagFieldInfosA = TagStructure.GetTagFieldEnumerable(dataA.GetType(), versionA); var tagFieldInfosB = TagStructure.GetTagFieldEnumerable(dataB.GetType(), versionB); // The objects are structures for (int a = 0, b = 0; a < tagFieldInfosA.Count && b < tagFieldInfosB.Count; a++, b++) { var tagFieldInfoA = tagFieldInfosA[a]; var tagFieldInfoB = tagFieldInfosB[b]; // Keep going on the left until the field is on the right while (!CacheVersionDetection.IsBetween(versionB, tagFieldInfoA.Attribute.MinVersion, tagFieldInfoA.Attribute.MaxVersion)) { if (++a < tagFieldInfosA.Count) { tagFieldInfoA = tagFieldInfosA[a]; } else { return; } } // Keep going on the right until the field is on the left while (!CacheVersionDetection.IsBetween(versionA, tagFieldInfoB.Attribute.MinVersion, tagFieldInfoB.Attribute.MaxVersion)) { if (++b < tagFieldInfosB.Count) { tagFieldInfoB = tagFieldInfosB[b]; } else { return; } } if (tagFieldInfoA.MetadataToken != tagFieldInfoB.MetadataToken) { throw new InvalidOperationException("WTF, left and right fields don't match!"); } // Process the fields var fieldDataA = tagFieldInfoA.GetValue(dataA); var fieldDataB = tagFieldInfoB.GetValue(dataB); CompareBlocks(fieldDataA, versionA, fieldDataB, versionB, result, tagQueue); } } }
public static CommandContext Create(CommandContextStack contextStack, GameCache cache, CachedTag tag, object definition) { var documentationPath = $"{new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName}\\TagTool.xml"; if (Documentation.ChildNodes.Count == 0 && File.Exists(documentationPath)) { Documentation.Load(documentationPath); } var groupName = cache.StringTable.GetString(tag.Group.Name); var tagName = tag?.Name ?? $"0x{tag.Index:X4}"; var commandContext = new CommandContext(contextStack.Context, string.Format("{0}.{1}", tagName, groupName)); switch (tag.Group.Tag.ToString()) { case "bitm": BitmapContextFactory.Populate(commandContext, cache, tag, (Bitmap)definition); break; case "bipd": BipedContextFactory.Populate(commandContext, cache, tag, (Biped)definition); break; case "bink": VideoContextFactory.Populate(commandContext, cache, tag, (Bink)definition); break; case "coll": CollisionModelContextFactory.Populate(commandContext, cache, tag, (CollisionModel)definition); break; case "forg": ForgeContextFactory.Populate(commandContext, cache, tag, (ForgeGlobalsDefinition)definition); break; case "hlmt": ModelContextFactory.Populate(commandContext, cache, tag, (Model)definition); break; case "jmad": AnimationContextFactory.Populate(commandContext, cache, tag, (ModelAnimationGraph)definition); break; case "pmdf": ParticleModelContextFactory.Populate(commandContext, cache, tag, (ParticleModel)definition); break; case "unic": UnicodeContextFactory.Populate(commandContext, cache, tag, (MultilingualUnicodeStringList)definition); break; case "snd!": SoundContextFactory.Populate(commandContext, cache, tag, (Sound)definition); break; case "rmt2": RenderMethodTemplateContextFactory.Populate(commandContext, cache, tag, (RenderMethodTemplate)definition); break; case "rm ": // render_method case "rmsh": // shader case "rmd ": // shader_decal case "rmfl": // shader_foliage case "rmhg": // shader_halogram case "rmss": // shader_screen case "rmtr": // shader_terrain case "rmw ": // shader_water case "rmzo": // shader_zonly case "rmcs": // shader_custom RenderMethodContextFactory.Populate(commandContext, cache, tag, (RenderMethod)definition); break; case "pixl": ShaderContextFactory <PixelShader> .Populate(commandContext, cache, tag, (PixelShader)definition); break; case "vtsh": ShaderContextFactory <VertexShader> .Populate(commandContext, cache, tag, (VertexShader)definition); break; case "glps": ShaderContextFactory <GlobalPixelShader> .Populate(commandContext, cache, tag, (GlobalPixelShader)definition); break; case "glvs": ShaderContextFactory <GlobalVertexShader> .Populate(commandContext, cache, tag, (GlobalVertexShader)definition); break; case "Lbsp": LightmapContextFactory.Populate(commandContext, cache, tag, (ScenarioLightmapBspData)definition); break; case "vfsl": VFilesContextFactory.Populate(commandContext, cache, tag, (VFilesList)definition); break; case "mode": RenderModelContextFactory.Populate(commandContext, cache, tag, (RenderModel)definition); break; case "sbsp": BSPContextFactory.Populate(commandContext, cache, tag, (ScenarioStructureBsp)definition); break; case "scnr": ScnrContextFactory.Populate(commandContext, cache, tag, (Scenario)definition); break; } var structure = TagStructure.GetTagStructureInfo(TagDefinition.Find(tag.Group.Tag), cache.Version); commandContext.AddCommand(new ListFieldsCommand(cache, structure, definition)); commandContext.AddCommand(new SetFieldCommand(contextStack, cache, tag, structure, definition)); commandContext.AddCommand(new EditBlockCommand(contextStack, cache, tag, definition)); commandContext.AddCommand(new AddBlockElementsCommand(contextStack, cache, tag, structure, definition)); commandContext.AddCommand(new RemoveBlockElementsCommand(contextStack, cache, tag, structure, definition)); commandContext.AddCommand(new CopyBlockElementsCommand(contextStack, cache, tag, structure, definition)); commandContext.AddCommand(new PasteBlockElementsCommand(contextStack, cache, tag, structure, definition)); commandContext.AddCommand(new ForEachCommand(contextStack, cache, tag, structure, definition)); commandContext.AddCommand(new SaveTagChangesCommand(cache, tag, definition)); commandContext.AddCommand(new ExitToCommand(contextStack)); if (CacheVersionDetection.IsInGen(CacheGeneration.HaloOnline, cache.Version)) { commandContext.AddCommand(new PokeTagChangesCommand(cache as GameCacheHaloOnlineBase, tag as CachedTagHaloOnline, definition)); } return(commandContext); }
public override object Execute(List <string> args) { if (args.Count != 4) { return(false); } if (!CacheContext.TryGetTag(args[0], out var srcTag)) { return(false); } var csvPath = args[1]; var csvOutPath = args[2]; var targetDir = args[3]; // Load the CSV Console.WriteLine("Reading {0}...", csvPath); TagVersionMap tagMap; using (var reader = new StreamReader(File.Exists(csvPath) ? File.OpenRead(csvPath) : File.Create(csvPath))) tagMap = TagVersionMap.ParseTagVersionMap(reader); // Load destination cache files var destCacheContext = new GameCacheContextHaloOnline(new DirectoryInfo(targetDir)); using (var stream = destCacheContext.OpenTagCacheRead()) destCacheContext.TagCache = new TagCache(stream, destCacheContext.LoadTagNames()); Console.WriteLine(); Console.WriteLine("CONVERTING FROM VERSION {0} TO {1}", CacheVersionDetection.GetBuildName(CacheContext.Version), CacheVersionDetection.GetBuildName(destCacheContext.Version)); Console.WriteLine(); CachedTagHaloOnline resultTag; using (Stream srcStream = CacheContext.TagCache.OpenTagCacheRead(), destStream = destCacheContext.OpenTagCacheReadWrite()) resultTag = ConvertTag(srcTag, CacheContext, srcStream, destCacheContext, destStream, tagMap); Console.WriteLine(); Console.WriteLine("Repairing decal systems..."); if (CacheContext.Version != destCacheContext.Version) { FixDecalSystems(destCacheContext, resultTag.Index); } Console.WriteLine(); Console.WriteLine("Saving stringIDs..."); using (var stream = destCacheContext.OpenStringIdCacheReadWrite()) destCacheContext.StringIdCache.Save(stream); Console.WriteLine("Writing {0}...", csvOutPath); using (var stream = new StreamWriter(File.Open(csvOutPath, FileMode.Create, FileAccess.ReadWrite))) tagMap.WriteCsv(stream); // Uncomment this to add the new tag as a dependency to cfgt to make testing easier /*using (var stream = destCacheContext.OpenCacheReadWrite()) * { * destCacheContext.Cache.Tags[0].Dependencies.Add(resultTag.Index); * destCacheContext.Cache.UpdateTag(stream, destCacheContext.Cache.Tags[0]); * }*/ Console.WriteLine(); Console.WriteLine("All done! The converted tag is:"); TagPrinter.PrintTagShort(resultTag); destCacheContext.SaveTagNames(); return(true); }
private RenderGeometry ConvertGeometry(RenderGeometry geometry, GameCacheContextHaloOnline srcCacheContext, GameCacheContextHaloOnline destCacheContext) { if (geometry == null || geometry.Resource.HaloOnlinePageableResource == null || geometry.Resource.HaloOnlinePageableResource.Page.Index < 0 || !geometry.Resource.HaloOnlinePageableResource.GetLocation(out var location)) { return(geometry); } // The format changed starting with version 1.235640, so if both versions are on the same side then they can be converted normally var srcCompare = CacheVersionDetection.Compare(srcCacheContext.Version, CacheVersion.HaloOnline235640); var destCompare = CacheVersionDetection.Compare(destCacheContext.Version, CacheVersion.HaloOnline235640); if ((srcCompare < 0 && destCompare < 0) || (srcCompare >= 0 && destCompare >= 0)) { geometry.Resource.HaloOnlinePageableResource = ConvertResource(geometry.Resource.HaloOnlinePageableResource, srcCacheContext, destCacheContext); return(geometry); } Console.WriteLine("- Rebuilding geometry resource {0} in {1}...", geometry.Resource.HaloOnlinePageableResource.Page.Index, location); using (MemoryStream inStream = new MemoryStream(), outStream = new MemoryStream()) { // First extract the model data srcCacheContext.ExtractResource(geometry.Resource.HaloOnlinePageableResource, inStream); // Now open source and destination vertex streams inStream.Position = 0; var inVertexStream = VertexStreamFactory.Create(srcCacheContext.Version, inStream); var outVertexStream = VertexStreamFactory.Create(destCacheContext.Version, outStream); // Deserialize the definition data var resourceContext = new ResourceSerializationContext(CacheContext, geometry.Resource.HaloOnlinePageableResource); var definition = srcCacheContext.Deserializer.Deserialize <RenderGeometryApiResourceDefinition>(resourceContext); // Convert each vertex buffer foreach (var buffer in definition.VertexBuffers) { ConvertVertexBuffer(srcCacheContext, destCacheContext, buffer.Definition, inStream, inVertexStream, outStream, outVertexStream); } // Copy each index buffer over foreach (var buffer in definition.IndexBuffers) { if (buffer.Definition.Data.Size == 0) { continue; } inStream.Position = buffer.Definition.Data.Address.Offset; buffer.Definition.Data.Address = new CacheAddress(CacheAddressType.Data, (int)outStream.Position); var bufferData = new byte[buffer.Definition.Data.Size]; inStream.Read(bufferData, 0, bufferData.Length); outStream.Write(bufferData, 0, bufferData.Length); StreamUtil.Align(outStream, 4); } // Update the definition data destCacheContext.Serializer.Serialize(resourceContext, definition); // Now inject the new resource data var newLocation = FixResourceLocation(location, srcCacheContext.Version, destCacheContext.Version); outStream.Position = 0; geometry.Resource.HaloOnlinePageableResource.ChangeLocation(newLocation); destCacheContext.AddResource(geometry.Resource.HaloOnlinePageableResource, outStream); } return(geometry); }
public StringTableGen3(EndianReader reader, MapFile baseMapFile) : base() { Version = baseMapFile.Version; switch (Version) { case CacheVersion.Halo3Retail: case CacheVersion.Halo3Beta: // double check Resolver = new StringIdResolverHalo3(); break; case CacheVersion.Halo3ODST: Resolver = new StringIdResolverHalo3ODST(); break; case CacheVersion.HaloReach: Resolver = new StringIdResolverHaloReach(); StringKey = "ILikeSafeStrings"; break; case CacheVersion.HaloReachMCC0824: case CacheVersion.HaloReachMCC0887: case CacheVersion.HaloReachMCC1035: case CacheVersion.HaloReachMCC1211: Resolver = new StringIdResolverHaloReachMCC(); break; default: throw new NotSupportedException(CacheVersionDetection.GetBuildName(Version)); } var sectionTable = baseMapFile.Header.SectionTable; // means no strings if (sectionTable.Sections[(int)CacheFileSectionType.StringSection].Size == 0) { return; } var stringIdIndexTableOffset = sectionTable.GetOffset(CacheFileSectionType.StringSection, baseMapFile.Header.StringIDsIndicesOffset); var stringIdBufferOffset = sectionTable.GetOffset(CacheFileSectionType.StringSection, baseMapFile.Header.StringIDsBufferOffset); // // Read offsets // reader.SeekTo(stringIdIndexTableOffset); int[] stringOffset = new int[baseMapFile.Header.StringIDsCount]; for (var i = 0; i < baseMapFile.Header.StringIDsCount; i++) { stringOffset[i] = reader.ReadInt32(); Add(""); } reader.SeekTo(stringIdBufferOffset); EndianReader newReader; if (StringKey == "") { newReader = new EndianReader(new MemoryStream(reader.ReadBytes(baseMapFile.Header.StringIDsBufferSize)), reader.Format); } else { newReader = new EndianReader(reader.DecryptAesSegment(baseMapFile.Header.StringIDsBufferSize, StringKey), reader.Format); } // // Read strings // for (var i = 0; i < stringOffset.Length; i++) { if (stringOffset[i] == -1) { this[i] = "<null>"; continue; } newReader.SeekTo(stringOffset[i]); this[i] = newReader.ReadNullTerminatedString(); } newReader.Close(); newReader.Dispose(); }
/// <summary> /// Parses a map from a CSV. /// </summary> /// <param name="reader">The reader to read from.</param> /// <returns>The map that was read.</returns> public static TagVersionMap ParseTagVersionMap(TextReader reader) { var result = new TagVersionMap(); // Skip the first line, we don't need it if (reader.ReadLine() == null) { return(result); } // Read the timestamp list and resolve each one var timestampLine = reader.ReadLine(); if (timestampLine == null) { return(result); } var timestamps = timestampLine.Split(',').Select(t => { if (long.TryParse(t, NumberStyles.HexNumber, null, out long r)) { return(r); } return(-1); }); var versions = timestamps.Select(t => { return(CacheVersionDetection.DetectFromTimestamp(t, out CacheVersion closest)); }).ToArray(); // Read each line and store the tag indices in the result map while (true) { var line = reader.ReadLine(); if (line == null) { break; } if (string.IsNullOrWhiteSpace(line)) { continue; } // Parse each tag index as a hex number var tags = line.Split(','); var tagIndices = tags.Select(t => { if (int.TryParse(t, NumberStyles.HexNumber, null, out int r)) { return(r); } return(-1); }).ToArray(); // Now connect all of them to the first tag for (var i = 1; i < tagIndices.Length; i++) { if (tagIndices[i] >= 0) { result.Add(versions[0], tagIndices[0], versions[i], tagIndices[i]); } } } return(result); }
public override object Execute(List <string> args) { if (args.Count != 1) { return(false); } var destDir = new DirectoryInfo(args[0]); if (!destDir.Exists) { destDir.Create(); } foreach (var entry in TagGroup.Instances) { if (entry.Key.Value == -1) { continue; } var tagGroupName = CacheContext.GetString(entry.Value.Name).ToSnakeCase(); var tagStructureInfo = TagStructure.GetTagStructureInfo(TagDefinition.Find(entry.Key), CacheContext.Version); foreach (var type in tagStructureInfo.Types.Reverse <Type>()) { var info = type.GetCustomAttributes <TagStructureAttribute>(false).Where(attr => CacheVersionDetection.IsBetween(CacheContext.Version, attr.MinVersion, attr.MaxVersion)).First(); if (info.Name == null) { Console.WriteLine($"WARNING: {type.FullName} has no tag structure name defined!"); continue; } ExportType(type, new FileInfo(Path.Combine(destDir.FullName, $"{info.Name}.hpp"))); } } return(true); }
public override object Execute(List <string> args) { if (args.Count < 1) { return(false); } var memory = false; while (args.Count > 1) { switch (args[0].ToLower()) { case "memory": memory = true; break; default: throw new FormatException(args[0]); } args.RemoveAt(0); } var blamCacheFile = new FileInfo(args[0]); if (!blamCacheFile.Exists) { Console.WriteLine($"CacheFile {blamCacheFile.FullName} does not exist"); return(true); } Console.Write("Loading blam cache file..."); CacheFile blamCache = null; using (var cacheStream = new FileStream(blamCacheFile.FullName, FileMode.Open, FileAccess.Read)) { var reader = new EndianReader(cacheStream, EndianFormat.BigEndian); var head = reader.ReadInt32(); if (head == 1684104552) { reader.Format = EndianFormat.LittleEndian; } var v = reader.ReadInt32(); switch (v) { case 8: // Gen2 reader.SeekTo(36); switch (reader.ReadInt32()) { case 0: // Halo 2 Xbox reader.SeekTo(288); break; case -1: // Halo 2 Vista reader.SeekTo(300); break; } break; default: // Gen3+ reader.SeekTo(284); break; } var version = CacheVersionDetection.GetFromBuildName(reader.ReadString(32)); switch (version) { case CacheVersion.Halo2Xbox: case CacheVersion.Halo2Vista: if (blamCacheFile.Name != "mainmenu.map") { new CacheFileGen2(CacheContext, new FileInfo(Path.Combine(blamCacheFile.Directory.FullName, "mainmenu.map")), version, memory); } if (blamCacheFile.Name != "shared.map") { new CacheFileGen2(CacheContext, new FileInfo(Path.Combine(blamCacheFile.Directory.FullName, "shared.map")), version, memory); } if (blamCacheFile.Name != "single_player_shared.map") { new CacheFileGen2(CacheContext, new FileInfo(Path.Combine(blamCacheFile.Directory.FullName, "single_player_shared.map")), version, memory); } blamCache = new CacheFileGen2(CacheContext, blamCacheFile, version, memory); break; case CacheVersion.Halo3Retail: case CacheVersion.Halo3ODST: case CacheVersion.HaloReach: blamCache = new CacheFileGen3(CacheContext, blamCacheFile, version, memory); break; default: // Same question here as above. throw new NotSupportedException(CacheVersionDetection.GetBuildName(version)); } } ContextStack.Push(PortingContextFactory.Create(ContextStack, CacheContext, blamCache)); Console.WriteLine("done."); return(true); }
public List <ModelAnimationGraph.ResourceGroup> ConvertModelAnimationGraphResourceGroups(Stream cacheStream, Stream blamCacheStream, Dictionary <ResourceLocation, Stream> resourceStreams, List <ModelAnimationGraph.ResourceGroup> resourceGroups) { var resourceDefinitions = new List <ModelAnimationTagResource>(); foreach (var group in resourceGroups) { var resourceDefinition = BlamCache.ResourceCache.GetModelAnimationTagResource(group.ResourceReference); if (resourceDefinition == null) { group.ResourceReference = null; continue; } for (var memberIndex = 0; memberIndex < resourceDefinition.GroupMembers.Count; memberIndex++) { var member = resourceDefinition.GroupMembers[memberIndex]; var animationData = member.AnimationData.Data; using (var sourceStream = new MemoryStream(animationData)) using (var sourceReader = new EndianReader(sourceStream, CacheVersionDetection.IsLittleEndian(BlamCache.Version) ? EndianFormat.LittleEndian : EndianFormat.BigEndian)) using (var destStream = new MemoryStream()) using (var destWriter = new EndianWriter(destStream, CacheVersionDetection.IsLittleEndian(CacheContext.Version) ? EndianFormat.LittleEndian : EndianFormat.BigEndian)) { var dataContext = new DataSerializationContext(sourceReader, destWriter); ModelAnimationTagResource.GroupMember.Codec codec; ModelAnimationTagResource.GroupMember.FrameInfo frameInfo; if (member.BaseHeader != ModelAnimationTagResource.GroupMemberHeaderType.Overlay) { codec = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Codec>(dataContext); CacheContext.Serializer.Serialize(dataContext, codec); var Format1 = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Format1>(dataContext); CacheContext.Serializer.Serialize(dataContext, Format1); for (int i = 0; i < codec.RotationNodeCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.RotationFrame>(dataContext)); } sourceStream.Position = Format1.DataStart; destStream.Position = sourceStream.Position; for (int i = 0; i < codec.PositionNodeCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.PositionFrame>(dataContext)); } sourceStream.Position = Format1.ScaleFramesOffset; destStream.Position = sourceStream.Position; for (int i = 0; i < codec.ScaleNodeCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } } // If the overlay header is alone, member.OverlayOffset = 0 sourceStream.Position = member.OverlayOffset; destStream.Position = member.OverlayOffset; codec = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Codec>(dataContext); CacheContext.Serializer.Serialize(dataContext, codec); // deserialize second header. or as first header if the type1/format1 header isn't used. switch (codec.AnimationCodec) { case ModelAnimationTagResource.AnimationCompressionFormats.Type3: // should merge with type1 var header = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Format1>(dataContext); CacheContext.Serializer.Serialize(dataContext, header); for (int nodeIndex = 0; nodeIndex < codec.RotationNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.RotationFrame>(dataContext)); } } sourceStream.Position = member.OverlayOffset + header.DataStart; destStream.Position = sourceStream.Position; for (int nodeIndex = 0; nodeIndex < codec.PositionNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.PositionFrame>(dataContext)); } } sourceStream.Position = member.OverlayOffset + header.ScaleFramesOffset; destStream.Position = sourceStream.Position; for (int nodeIndex = 0; nodeIndex < codec.ScaleNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } } break; case ModelAnimationTagResource.AnimationCompressionFormats.Type4: case ModelAnimationTagResource.AnimationCompressionFormats.Type5: case ModelAnimationTagResource.AnimationCompressionFormats.Type6: case ModelAnimationTagResource.AnimationCompressionFormats.Type7: var overlay = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Overlay>(dataContext); CacheContext.Serializer.Serialize(dataContext, overlay); #region Description // Description by DemonicSandwich from http://remnantmods.com/forums/viewtopic.php?f=13&t=1574 (matches my previous observations) // Format 6 uses Keyframes the way there are supposed to be used. As KEY frames, with the majority of the frames being Tweens. // // This format adds two extra blocks of data to it's structure. // One block that determines how many Keyframes each Node will have, and an offset to to where it's Markers start from. // // Advantages: // This format requires far fewer Keyframes to make a complex animation. // You do not need a keyframe for each render frame. // Disadvantages: // It's a bit more complex to work with. // Since it's Keyrame Markers are only 1 byte in size, you're animation cannot be longer than 256 frames, or ~8.5 seconds for non - machine objects. > 12 bits for gen3, max 0xFFF frames // Machines are still limited to 256 frames but the frames can be stretched out. #endregion var RotationFrameCount = new List <uint>(); var PositionFrameCount = new List <uint>(); var ScaleFrameCount = new List <uint>(); for (int i = 0; i < codec.RotationNodeCount; i++) { frameInfo = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfo>(dataContext); CacheContext.Serializer.Serialize(dataContext, frameInfo); var keyframesOffset = frameInfo.FrameCount & 0x00FFF000; // unused in this conversion var keyframes = frameInfo.FrameCount & 0x00000FFF; RotationFrameCount.Add(keyframes); } for (int i = 0; i < codec.PositionNodeCount; i++) { frameInfo = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfo>(dataContext); CacheContext.Serializer.Serialize(dataContext, frameInfo); var keyframesOffset = frameInfo.FrameCount & 0x00FFF000; var keyframes = frameInfo.FrameCount & 0x00000FFF; PositionFrameCount.Add(keyframes); } for (int i = 0; i < codec.ScaleNodeCount; i++) { frameInfo = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfo>(dataContext); CacheContext.Serializer.Serialize(dataContext, frameInfo); var keyframesOffset = frameInfo.FrameCount & 0x00FFF000; var keyframes = frameInfo.FrameCount & 0x00000FFF; ScaleFrameCount.Add(keyframes); } sourceStream.Position = member.OverlayOffset + overlay.RotationKeyframesOffset; destStream.Position = sourceStream.Position; foreach (var framecount in RotationFrameCount) { for (int i = 0; i < framecount; i++) { if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type4) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type5) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type6) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type7) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } } } sourceStream.Position = member.OverlayOffset + overlay.PositionKeyframesOffset; destStream.Position = sourceStream.Position; foreach (var framecount in PositionFrameCount) { for (int i = 0; i < framecount; i++) { if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type4) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type5) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type6) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type7) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } } } sourceStream.Position = member.OverlayOffset + overlay.ScaleKeyframesOffset; destStream.Position = sourceStream.Position; foreach (var framecount in ScaleFrameCount) { for (int i = 0; i < framecount; i++) { if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type4) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type5) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type6) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type7) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } } } sourceStream.Position = member.OverlayOffset + overlay.RotationFramesOffset; destStream.Position = sourceStream.Position; foreach (var framecount in RotationFrameCount) { for (int i = 0; i < framecount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.RotationFrame>(dataContext)); } } sourceStream.Position = member.OverlayOffset + overlay.PositionFramesOffset; destStream.Position = sourceStream.Position; foreach (var framecount in PositionFrameCount) { for (int i = 0; i < framecount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.PositionFrame>(dataContext)); } } sourceStream.Position = member.OverlayOffset + overlay.ScaleFramesOffset; destStream.Position = sourceStream.Position; foreach (var framecount in ScaleFrameCount) { for (int i = 0; i < framecount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } } break; case ModelAnimationTagResource.AnimationCompressionFormats.Type8: // Type 8 is basically a type 3 but with rotation frames using 4 floats, or a realQuaternion var Format8 = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Format8>(dataContext); CacheContext.Serializer.Serialize(dataContext, Format8); for (int nodeIndex = 0; nodeIndex < codec.RotationNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.RotationFrameFloat>(dataContext)); } } sourceStream.Position = member.OverlayOffset + Format8.PositionFramesOffset; destStream.Position = sourceStream.Position; for (int nodeIndex = 0; nodeIndex < codec.PositionNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.PositionFrame>(dataContext)); } } sourceStream.Position = member.OverlayOffset + Format8.ScaleFramesOffset; destStream.Position = sourceStream.Position; for (int nodeIndex = 0; nodeIndex < codec.ScaleNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } } break; case ModelAnimationTagResource.AnimationCompressionFormats.None_: // empty data, copy buffer and skip member.AnimationData.Data = sourceStream.ToArray(); continue; default: throw new DataMisalignedException(); } #region How Footer/Flags works // Better description by DemonicSandwich from http://remnantmods.com/forums/viewtopic.php?f=13&t=1574 : Node List Block: (matches my previous observations) // Just a block of flags. Tick a flag and the respective node will be affected by animation. // The size of this block should always be a multiple of 12. It's size is determined my the meta value Node List Size [byte, offset: 61] // When set to 12, the list can handle objects with a node count up to 32 (0-31). // When set to 24, the object can have 64 nodes and so on. // The block is split into 3 groups of flags. // The first group determines what nodes are affected by rotation, the second group for position, and the third group for scale. // // If looking at it in hex, the Node ticks for each group will be in order as follows: // [7][6][5][4][3][2][1][0] - [15][14][13][12][11][10][9][8] - etc. // Each flag corresponding to a Node index. #endregion #region Footer/Flag block // There's one bitfield32 for every 32 nodes that are animated which i'll call a node flags. // There's at least 3 flags if the animation only has an overlay header, which i'll call a flag set. // There's at least 6 flags if the animation has both a base header and an overlay header, so 2 sets. // If the animated nodes count is over 32, then a new flags set is added. // 1 set per header is added, such as 32 nodes = 1 set, 64 = 2 sets, 96 = 3 sets etc , 128-256 maybe max sourceStream.Position = member.OverlayOffset + member.FlagsOffset; destStream.Position = sourceStream.Position; var footerSizeBase = (byte)member.BaseHeader / 4; for (int flagsCount = 0; flagsCount < footerSizeBase; flagsCount++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } var footerSizeOverlay = (byte)member.OverlayHeader / 4; for (int flagsCount = 0; flagsCount < footerSizeOverlay; flagsCount++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } #endregion switch (member.MovementDataType) { case ModelAnimationTagResource.GroupMemberMovementDataType.None: if (member.Unknown1 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDyaw>(dataContext)); } } if (member.Unknown2 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDyaw>(dataContext)); } } break; case ModelAnimationTagResource.GroupMemberMovementDataType.dx_dy: if (member.Unknown1 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDy>(dataContext)); } } if (member.Unknown2 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDyaw>(dataContext)); } } break; case ModelAnimationTagResource.GroupMemberMovementDataType.dx_dy_dyaw: if (member.Unknown1 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDyaw>(dataContext)); } } if (member.Unknown2 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDyaw>(dataContext)); } } break; case ModelAnimationTagResource.GroupMemberMovementDataType.dx_dy_dz_dyaw: if (member.Unknown1 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDzDyaw>(dataContext)); } } if (member.Unknown2 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDzDyaw>(dataContext)); } } break; default: break; } // set new data member.AnimationData.Data = destStream.ToArray(); } } group.ResourceReference = CacheContext.ResourceCache.CreateModelAnimationGraphResource(resourceDefinition); } return(resourceGroups); }