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)); } }
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); }
/// <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 ((attr.Version != CacheVersion.Unknown && attr.Version == Info.Version) || (attr.Version == CacheVersion.Unknown && CacheVersionDetection.IsBetween(Info.Version, attr.MinVersion, attr.MaxVersion))) { 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 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); } } }
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); }
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); }