package winmd import ( "fmt" "strconv" "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/tdakkota/win32metadata/md" "github.com/tdakkota/win32metadata/types" ) // TypeDef is a helper struct that wraps types.TypeDef and stores the original context // of the typeDef. type TypeDef struct { types.TypeDef HasContext logger log.Logger } // QualifiedID holds the namespace and the name of a qualified element. This may be a type, a static function or a field type QualifiedID struct { Namespace string Name string } // GetValueForEnumField returns the value of the requested enum field. func (typeDef *TypeDef) GetValueForEnumField(fieldIndex uint32) (string, error) { // For each Enum value definition, there is a corresponding row in the Constant table to store the integer value for the enum value. tableConstants := typeDef.Ctx().Table(md.Constant) for i := uint32(0); i < tableConstants.RowCount(); i++ { var constant types.Constant if err := constant.FromRow(tableConstants.Row(i)); err != nil { return "", err } if t, _ := constant.Parent.Table(); t != md.Field { continue } // does the blob belong to the field we're looking for? // The parent is an index into the field table that holds the associated enum value record if constant.Parent.TableIndex() != fieldIndex { continue } // The value is a blob that we need to read as little endian var blobIndex uint32 for i, b := range constant.Value { blobIndex += uint32(b) << (i * 8) } return strconv.Itoa(int(blobIndex)), nil } return "", fmt.Errorf("no value found for field %d", fieldIndex) } // GetAttributeWithType returns the value of the given attribute type and fails if not found. func (typeDef *TypeDef) GetAttributeWithType(lookupAttrTypeClass string) ([]byte, error) { result := typeDef.GetTypeDefAttributesWithType(lookupAttrTypeClass) if len(result) == 0 { return nil, fmt.Errorf("type %s has no custom attribute %s", typeDef.TypeNamespace+"."+typeDef.TypeName, lookupAttrTypeClass) } else if len(result) > 1 { _ = level.Warn(typeDef.logger).Log( "msg", "type has multiple custom attributes, returning the first one", "type", typeDef.TypeNamespace+"."+typeDef.TypeName, "attr", lookupAttrTypeClass, ) } return result[0], nil } // GetTypeDefAttributesWithType returns the values of all the attributes that match the given type. func (typeDef *TypeDef) GetTypeDefAttributesWithType(lookupAttrTypeClass string) [][]byte { result := make([][]byte, 0) cAttrTable := typeDef.Ctx().Table(md.CustomAttribute) for i := uint32(0); i < cAttrTable.RowCount(); i++ { var cAttr types.CustomAttribute if err := cAttr.FromRow(cAttrTable.Row(i)); err != nil { continue } // - Parent: The owner of the Attribute must be the given typeDef if cAttrParentTable, _ := cAttr.Parent.Table(); cAttrParentTable != md.TypeDef { continue } var parentTypeDef TypeDef row, ok := cAttr.Parent.Row(typeDef.Ctx()) if !ok { continue } if err := parentTypeDef.FromRow(row); err != nil { continue } // does the blob belong to the type we're looking for? if parentTypeDef.TypeNamespace != typeDef.TypeNamespace || parentTypeDef.TypeName != typeDef.TypeName { continue } // - Type: the attribute type must be the given type // the cAttr.Type table can be either a MemberRef or a MethodRef. // Since we are looking for a type, we will only consider the MemberRef. if cAttrTypeTable, _ := cAttr.Type.Table(); cAttrTypeTable != md.MemberRef { continue } var attrTypeMemberRef types.MemberRef row, ok = cAttr.Type.Row(typeDef.Ctx()) if !ok { continue } if err := attrTypeMemberRef.FromRow(row); err != nil { continue } // we need to check the MemberRef Class // the value can belong to several tables, but we are only going to check for TypeRef if classTable, _ := attrTypeMemberRef.Class.Table(); classTable != md.TypeRef { continue } var attrTypeRef types.TypeRef row, ok = attrTypeMemberRef.Class.Row(typeDef.Ctx()) if !ok { continue } if err := attrTypeRef.FromRow(row); err != nil { continue } if attrTypeRef.TypeNamespace+"."+attrTypeRef.TypeName == lookupAttrTypeClass { result = append(result, cAttr.Value) } } return result } // GetImplementedInterfaces returns the interfaces implemented by the type. func (typeDef *TypeDef) GetImplementedInterfaces() ([]QualifiedID, error) { interfaces := make([]QualifiedID, 0) tableInterfaceImpl := typeDef.Ctx().Table(md.InterfaceImpl) for i := uint32(0); i < tableInterfaceImpl.RowCount(); i++ { var interfaceImpl types.InterfaceImpl if err := interfaceImpl.FromRow(tableInterfaceImpl.Row(i)); err != nil { return nil, err } classTd, err := interfaceImpl.ResolveClass(typeDef.Ctx()) if err != nil { return nil, err } if classTd.TypeNamespace+"."+classTd.TypeName != typeDef.TypeNamespace+"."+typeDef.TypeName { // not the class we are looking for continue } if t, ok := interfaceImpl.Interface.Table(); ok && t == md.TypeSpec { // ignore type spec rows continue } ifaceNS, ifaceName, err := typeDef.Ctx().ResolveTypeDefOrRefName(interfaceImpl.Interface) if err != nil { return nil, err } interfaces = append(interfaces, QualifiedID{Namespace: ifaceNS, Name: ifaceName}) } return interfaces, nil } // Extends returns true if the type extends the given class func (typeDef *TypeDef) Extends(class string) (bool, error) { ns, name, err := typeDef.Ctx().ResolveTypeDefOrRefName(typeDef.TypeDef.Extends) if err != nil { return false, err } return ns+"."+name == class, nil } // GetGenericParams returns the generic parameters of the type. func (typeDef *TypeDef) GetGenericParams() ([]*types.GenericParam, error) { params := make([]*types.GenericParam, 0) tableGenericParam := typeDef.Ctx().Table(md.GenericParam) for i := uint32(0); i < tableGenericParam.RowCount(); i++ { var genericParam types.GenericParam if err := genericParam.FromRow(tableGenericParam.Row(i)); err != nil { continue } // - Owner: The owner of the Attribute must be the given typeDef if genericParamOwnerTable, _ := genericParam.Owner.Table(); genericParamOwnerTable != md.TypeDef { continue } var ownerTypeDef types.TypeDef row, ok := genericParam.Owner.Row(typeDef.Ctx()) if !ok { continue } if err := ownerTypeDef.FromRow(row); err != nil { continue } // does the blob belong to the type we're looking for? if ownerTypeDef.TypeNamespace != typeDef.TypeNamespace || ownerTypeDef.TypeName != typeDef.TypeName { continue } params = append(params, &genericParam) } if len(params) == 0 { return nil, fmt.Errorf("could not find generic params for type %s.%s", typeDef.TypeNamespace, typeDef.TypeName) } return params, nil } // IsInterface returns true if the type is an interface func (typeDef *TypeDef) IsInterface() bool { return typeDef.Flags.Interface() } // IsEnum returns true if the type is an enum func (typeDef *TypeDef) IsEnum() bool { ok, err := typeDef.Extends("System.Enum") if err != nil { _ = level.Error(typeDef.logger).Log("msg", "error resolving type extends, all classes should extend at least System.Object", "err", err) return false } return ok } // IsDelegate returns true if the type is a delegate func (typeDef *TypeDef) IsDelegate() bool { if !(typeDef.Flags.Public() && typeDef.Flags.Sealed()) { return false } ok, err := typeDef.Extends("System.MulticastDelegate") if err != nil { _ = level.Error(typeDef.logger).Log("msg", "error resolving type extends, all classes should extend at least System.Object", "err", err) return false } return ok } // IsStruct returns true if the type is a struct func (typeDef *TypeDef) IsStruct() bool { ok, err := typeDef.Extends("System.ValueType") if err != nil { _ = level.Error(typeDef.logger).Log("msg", "error resolving type extends, all classes should extend at least System.Object", "err", err) return false } return ok } // IsRuntimeClass returns true if the type is a runtime class func (typeDef *TypeDef) IsRuntimeClass() bool { // Flags: all runtime classes must carry the public, auto layout, class, and tdWindowsRuntime flags. return typeDef.Flags.Public() && typeDef.Flags.AutoLayout() && typeDef.Flags.Class() && typeDef.Flags&0x4000 != 0 } // GUID returns the GUID of the type. func (typeDef *TypeDef) GUID() (string, error) { blob, err := typeDef.GetAttributeWithType(AttributeTypeGUID) if err != nil { return "", err } return guidBlobToString(blob) } // guidBlobToString converts an array into the textual representation of a GUID func guidBlobToString(b types.Blob) (string, error) { // the guid is a blob of 20 bytes if len(b) != 20 { return "", fmt.Errorf("invalid GUID blob length: %d", len(b)) } // that starts with 0100 if b[0] != 0x01 || b[1] != 0x00 { return "", fmt.Errorf("invalid GUID blob header, expected '0x01 0x00' but found '0x%02x 0x%02x'", b[0], b[1]) } // and ends with 0000 if b[18] != 0x00 || b[19] != 0x00 { return "", fmt.Errorf("invalid GUID blob footer, expected '0x00 0x00' but found '0x%02x 0x%02x'", b[18], b[19]) } guid := b[2 : len(b)-2] // the string version has 5 parts separated by '-' return fmt.Sprintf("%08x-%04x-%04x-%04x-%04x%08x", // The first 3 are encoded as little endian uint32(guid[0])|uint32(guid[1])<<8|uint32(guid[2])<<16|uint32(guid[3])<<24, uint16(guid[4])|uint16(guid[5])<<8, uint16(guid[6])|uint16(guid[7])<<8, //the rest is not uint16(guid[8])<<8|uint16(guid[9]), uint16(guid[10])<<8|uint16(guid[11]), uint32(guid[12])<<24|uint32(guid[13])<<16|uint32(guid[14])<<8|uint32(guid[15])), nil }