first commit

This commit is contained in:
2025-08-22 17:42:23 -04:00
commit a6c09a5890
120 changed files with 11443 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,76 @@
package winmd
import (
"github.com/tdakkota/win32metadata/md"
"github.com/tdakkota/win32metadata/types"
)
// GetMethodOverloadName finds and returns the overload attribute for the given method
func GetMethodOverloadName(ctx *types.Context, methodDef *types.MethodDef) string {
cAttrTable := 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 func
if cAttrParentTable, _ := cAttr.Parent.Table(); cAttrParentTable != md.MethodDef {
continue
}
var parentMethodDef types.MethodDef
row, ok := cAttr.Parent.Row(ctx)
if !ok {
continue
}
if err := parentMethodDef.FromRow(row); err != nil {
continue
}
// does the blob belong to the method we're looking for?
if parentMethodDef.Name != methodDef.Name || string(parentMethodDef.Signature) != string(methodDef.Signature) {
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(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(ctx)
if !ok {
continue
}
if err := attrTypeRef.FromRow(row); err != nil {
continue
}
if attrTypeRef.TypeNamespace+"."+attrTypeRef.TypeName == AttributeTypeOverloadAttribute {
// Metadata values start with 0x01 0x00 and ends with 0x00 0x00
mdVal := cAttr.Value[2 : len(cAttr.Value)-2]
// the next value is the length of the string
mdVal = mdVal[1:]
return string(mdVal)
}
}
return methodDef.Name
}

89
internal/winmd/store.go Normal file
View File

@ -0,0 +1,89 @@
package winmd
import (
"fmt"
"github.com/go-kit/log"
"github.com/tdakkota/win32metadata/md"
"github.com/tdakkota/win32metadata/types"
)
// ClassNotFoundError is returned when a class is not found.
type ClassNotFoundError struct {
Class string
}
func (e *ClassNotFoundError) Error() string {
return fmt.Sprintf("class %s was not found", e.Class)
}
// Store holds the windows metadata contexts. It can be used to get the metadata across multiple files.
type Store struct {
contexts map[string]*types.Context
logger log.Logger
}
// NewStore loads all windows metadata files and returns a new Store.
func NewStore(logger log.Logger) (*Store, error) {
contexts := make(map[string]*types.Context)
winmdFiles, err := allFiles()
if err != nil {
return nil, err
}
// parse and store all files in memory
for _, f := range winmdFiles {
winmdCtx, err := parseWinMDFile(f.Name())
if err != nil {
return nil, err
}
contexts[f.Name()] = winmdCtx
}
return &Store{
contexts: contexts,
logger: logger,
}, nil
}
func parseWinMDFile(path string) (*types.Context, error) {
f, err := open(path)
if err != nil {
return nil, err
}
defer func() { _ = f.Close() }()
return types.FromPE(f)
}
// TypeDefByName returns a type definition that matches the given name.
func (mds *Store) TypeDefByName(class string) (*TypeDef, error) {
// the type can belong to any of the contexts
for _, ctx := range mds.contexts {
if td := mds.typeDefByNameAndCtx(class, ctx); td != nil {
return td, nil // return the first match
}
}
return nil, &ClassNotFoundError{Class: class}
}
func (mds *Store) typeDefByNameAndCtx(class string, ctx *types.Context) *TypeDef {
typeDefTable := ctx.Table(md.TypeDef)
for i := uint32(0); i < typeDefTable.RowCount(); i++ {
var typeDef types.TypeDef
if err := typeDef.FromRow(typeDefTable.Row(i)); err != nil {
continue // keep searching instead of failing
}
if typeDef.TypeNamespace+"."+typeDef.TypeName == class {
return &TypeDef{
TypeDef: typeDef,
HasContext: HasContext{ctx},
logger: mds.logger,
}
}
}
return nil
}

310
internal/winmd/typedef.go Normal file
View File

@ -0,0 +1,310 @@
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
}

61
internal/winmd/winmd.go Normal file
View File

@ -0,0 +1,61 @@
package winmd
import (
"bytes"
"debug/pe"
"embed"
"io/fs"
"io/ioutil"
"github.com/tdakkota/win32metadata/types"
)
// Custom Attributes
const (
AttributeTypeGUID = "Windows.Foundation.Metadata.GuidAttribute"
AttributeTypeExclusiveTo = "Windows.Foundation.Metadata.ExclusiveToAttribute"
AttributeTypeStaticAttribute = "Windows.Foundation.Metadata.StaticAttribute"
AttributeTypeActivatableAttribute = "Windows.Foundation.Metadata.ActivatableAttribute"
AttributeTypeDefaultAttribute = "Windows.Foundation.Metadata.DefaultAttribute"
AttributeTypeOverloadAttribute = "Windows.Foundation.Metadata.OverloadAttribute"
)
// HasContext is a helper struct that holds the original context of a metadata element.
type HasContext struct {
originalCtx *types.Context
}
// Ctx return the original context of the element.
func (hctx *HasContext) Ctx() *types.Context {
return hctx.originalCtx
}
//go:embed metadata/*.winmd
var files embed.FS
// allFiles returns all winmd files embedded in the binary.
func allFiles() ([]fs.DirEntry, error) {
return files.ReadDir("metadata")
}
// open reads the given file and returns a pe.File instance.
// The user should close the returned instance once he is done working with it.
func open(path string) (*pe.File, error) {
f, err := files.Open("metadata/" + path)
if err != nil {
return nil, err
}
defer func() { _ = f.Close() }()
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
pef, err := pe.NewFile(bytes.NewReader(data))
if err != nil {
return nil, err
}
return pef, nil
}