package cat import ( "fmt" "syscall" "unsafe" ole "github.com/go-ole/go-ole" "golang.org/x/sys/windows/registry" ) // Last-resort activation path for OmniRig, used only when the normal // CreateObject fails. // // OmniRig is a 32-bit program and its installer splits its COM identity across // registry views — on a working machine the ProgID sits in the 64-bit view while // the CLSID and its LocalServer32 exist only under WOW6432Node. That LOOKS like // it should break a 64-bit client, and it was my first theory for "OmniRig not // found"; measuring it on a machine with exactly that layout disproved it. COM // resolves an out-of-process server across views by itself, and the plain // CreateObject succeeds. // // What this still covers is the case where the ProgID is not visible to us at all // (a partial or 32-bit-only registration), where CreateObject has nothing to // resolve. Here we read the CLSID from BOTH views ourselves and activate the // 32-bit local server explicitly. CLSCTX_ACTIVATE_32_BIT_SERVER is the documented // flag for that; go-ole hard-codes CLSCTX_SERVER and keeps CoCreateInstance // unexported, hence the direct call. // // It costs nothing when the normal path works, and the reason it ran at all is // logged — so if it ever rescues a real installation we will see it. const clsctxActivate32BitServer = 0x40000 var ( modole32 = syscall.NewLazyDLL("ole32.dll") procCoCreateInst32 = modole32.NewProc("CoCreateInstance") ) // omnirigCLSIDFromRegistry reads OmniRig's CLSID from the ProgID key, looking in // the 64-bit view first and then the 32-bit one. Returned as a GUID ready for // CoCreateInstance. func omnirigCLSIDFromRegistry(progID string) (*ole.GUID, error) { for _, access := range []uint32{registry.QUERY_VALUE, registry.QUERY_VALUE | registry.WOW64_32KEY} { k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Classes\`+progID+`\CLSID`, access) if err != nil { continue } s, _, err := k.GetStringValue("") k.Close() if err != nil || s == "" { continue } if g := ole.NewGUID(s); g != nil { return g, nil } } return nil, fmt.Errorf("no CLSID registered for %s in either registry view", progID) } // createOmniRig32 activates OmniRig's 32-bit out-of-process server explicitly, // bypassing the 64-bit registry lookup that CoCreateInstance would otherwise do. func createOmniRig32(progID string) (*ole.IDispatch, error) { clsid, err := omnirigCLSIDFromRegistry(progID) if err != nil { return nil, err } var unk *ole.IUnknown hr, _, _ := procCoCreateInst32.Call( uintptr(unsafe.Pointer(clsid)), 0, // no aggregation uintptr(ole.CLSCTX_LOCAL_SERVER|clsctxActivate32BitServer), uintptr(unsafe.Pointer(ole.IID_IUnknown)), uintptr(unsafe.Pointer(&unk))) if hr != 0 { return nil, fmt.Errorf("CoCreateInstance(32-bit local server) failed: %w", ole.NewError(hr)) } disp, err := unk.QueryInterface(ole.IID_IDispatch) unk.Release() if err != nil { return nil, fmt.Errorf("query IDispatch: %w", err) } return disp, nil }