311 lines
9.5 KiB
Go
311 lines
9.5 KiB
Go
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
|
|
}
|