84 lines
3.0 KiB
Go
84 lines
3.0 KiB
Go
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
|
|
}
|