Пример #1
0
        private static CacheVersion DetectCacheVersion(EndianReader reader)
        {
            var version   = GetMapFileVersion(reader);
            var buildDate = GetBuildDate(reader, version);

            return(CacheVersionDetection.GetFromBuildName(buildDate));
        }
Пример #2
0
        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();
            }
        }
Пример #3
0
        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);
        }
Пример #4
0
            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));
                }
            }
Пример #5
0
        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);
        }
Пример #6
0
 /// <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));
 }
Пример #7
0
        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);
            }
        }
Пример #8
0
        /// <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);
            }
        }
Пример #9
0
        /// <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"))));
            }
        }
Пример #10
0
        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);
        }
Пример #11
0
        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();
                }
        }
Пример #12
0
        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);
        }
Пример #13
0
        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);
        }
Пример #14
0
        /// <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);
                    }
                }
            }
        }
Пример #15
0
        /// <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);
                }
            }
        }
Пример #16
0
        /// <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);
            }
        }
Пример #17
0
        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);
        }
Пример #18
0
        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);
                }
            }
        }
Пример #19
0
        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);
        }
Пример #20
0
        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);
        }
Пример #21
0
        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);
        }
Пример #22
0
        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();
        }
Пример #23
0
        /// <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);
        }
Пример #24
0
        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);
        }
Пример #25
0
        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);
        }
Пример #26
0
        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);
        }