今回は箸休め

その 5 で後回しにしてた

  • FindCtDeviceRegistry … デバイスのプロダクト ID に一致するレジストリエントリを検索する
  • CreateCtDeviceRegistry … レジストリエントリが無ければ作成
  • LocadCtDeviceLibraries … レジストリに列挙されてる DLL をプロセスにロードする

およびその 3 で後回しにしてた

  • RunPatchMixDSP … レジストリから PatchMix DSP の実行ファイルのパスを読み込んで起動する

を復元したらこんな感じってだけのご報告。

class CCtHelperDlg : public CDialog
{
...
private:
	struct CCtAttachedDevice {
		BYTE deviceDescription[0x100];
		HDEVNOTIFY deviceDetachedNotification;
	} attachedDevices[4];
	DWORD moduleCount;
	DWORD attachedDeviceCount;
	HMODULE modules[10];
	struct CCtRegDWORD {
		TCHAR key[0x80];
		DWORD value;
	} moduleRegistries[10];
	HDEVNOTIFY deviceAttacedNotification;
	HANDLE registryWatchdogThread;
	BOOL startPatchMiXDSP;
	void RegisterCtDeviceNotification();
	HRESULT FindCtDeviceRegistry(int, int);
	HRESULT CreateCtDeviceRegistry(int, int);
	HRESULT LoadCtDeviceLibraries(int, int);
	HRESULT TraverseAttachedCtDevices();
	HRESULT RunPatchMixDSP();
};

#define arraycount(array)	(sizeof(array)/sizeof(array[0]))

HRESULT CCtHelperDlg::FindCtDeviceRegistry(int vendorId, int productId)
{
	TCHAR sub[MAX_PATH];
	HKEY subhkey;
	_stprintf_s(sub, arraycount(sub), _T("%s\\%s%x%s%x\\%s"), _T("Software\\Creative Tech\\CtHelper"), _T("PCI&VEN_"), vendorId, _T("&DEV_000"), productId, _T("Enable"));
	if (RegOpenKey(HKEY_CURRENT_USER, sub, &subhkey) == ERROR_SUCCESS) {
		DWORD idx = 0;
		for (struct CCtRegDWORD* p = &moduleRegistries[0]; /**/; ++p) {
			DWORD keysiz = arraycount(p->key);
			DWORD valuesiz = sizeof(p->value);
			if (RegEnumValue(subhkey, idx++, p->key, &keysiz, NULL, NULL, (LPBYTE)&p->value, &valuesiz) != ERROR_SUCCESS)
				break;
		}
		RegCloseKey(subhkey);
		return S_OK;
	}
	return E_FAIL;
}

HRESULT CCtHelperDlg::CreateCtDeviceRegistry(int vendorId, int productId)
{
	TCHAR src[MAX_PATH];
	HKEY srchkey;
	_stprintf_s(src, arraycount(src), _T("%s\\%s%x%s%x"), _T("System\\CurrentControlSet\\Control\\Creative Tech\\CtHelper"), _T("PCI&VEN_"), vendorId, _T("&DEV_000"), productId);
	if (RegOpenKey(HKEY_LOCAL_MACHINE, src, &srchkey) == ERROR_SUCCESS) {
		DWORD idx = 0;
		for (struct CCtRegDWORD* p = &moduleRegistries[0]; /**/; ++p) {
			TCHAR key[0x80];
			ZeroMemory(key, sizeof(key));
			_stprintf_s(key, arraycount(key), _T("%s%d"), _T("DLL_0"), idx + 1);
			DWORD keySiz = arraycount(key);
			DWORD valueSiz = arraycount(p->key);
			if (RegEnumValue(srchkey, idx++, key, &keySiz, NULL, NULL, (LPBYTE)p->key, &valueSiz) != ERROR_SUCCESS)
				break;
			TCHAR dst[MAX_PATH];
			HKEY dsthkey;
			_stprintf_s(dst, arraycount(dst), _T("%s\\%s%x%s%x\\%s"), _T("Software\\Creative Tech\\CtHelper"), _T("PCI&VEN_"), vendorId, _T("&DEV_000"), productId, _T("Enable"));
			if (RegCreateKeyEx(HKEY_CURRENT_USER, dst, 0, NULL, 0, KEY_READ | KEY_CREATE_SUB_KEY | KEY_SET_VALUE, NULL, &dsthkey, NULL) == ERROR_SUCCESS) {
				p->value = 1;
				RegSetValueEx(dsthkey, p->key, 0, REG_DWORD, (LPBYTE)&p->value, sizeof(p->value));
				RegCloseKey(dsthkey);
			}
		}
		RegCloseKey(srchkey);
		return S_OK;
	}
	return E_FAIL;
}

HRESULT CCtHelperDlg::LoadCtDeviceLibraries(int vendorId, int productId)
{
	TCHAR sub[MAX_PATH];
	HKEY subhkey;
	_stprintf_s(sub, arraycount(sub), _T("%s\\%s%x%s%x\\%s"), _T("Software\\Creative Tech\\CtHelper"), _T("PCI&VEN_"), vendorId, _T("&DEV_000"), productId, _T("Enable"));
	if (RegOpenKey(HKEY_CURRENT_USER, sub, &subhkey) == ERROR_SUCCESS) {
		DWORD idx = 0;
		for (struct CCtRegDWORD* p = &moduleRegistries[0]; /**/; ++p) {
			DWORD keysiz = arraycount(p->key);
			DWORD valuesiz = sizeof(p->value);
			if (RegEnumValue(subhkey, idx++, p->key, &keysiz, NULL, NULL, (LPBYTE)&p->value, &valuesiz) != ERROR_SUCCESS)
				break;
			HMODULE moduleHandle = GetModuleHandle(p->key);
			if (moduleHandle == NULL) {
				moduleHandle = LoadLibrary(p->key);
				modules[moduleCount] = moduleHandle;
				if (modules[moduleCount] != NULL) {
					FARPROC initDLL = GetProcAddress(modules[moduleCount], "InitDLL");
					if (initDLL != NULL)
						(*initDLL)();
					++moduleCount;
				}
			}
		}
		RegCloseKey(subhkey);
		return S_OK;
	}
	return E_FAIL;
}

__pragma(pack(push, 1))
struct CCtDeviceInfoEx {
	GUID guid;
	DWORD32 vendorId;
	DWORD32 productId;
	DWORD32 subsysId;
	BYTE padding[520];
};
__pragma(pack(pop))

HRESULT CCtHelperDlg::TraverseAttachedCtDevices()
{
	moduleCount = 0;
	if (SUCCEEDED(CoInitialize(NULL))) {
		IDevConDevice2* iDevConDevice2;
		if (SUCCEEDED(CoCreateInstance(__uuidof(DevConDevice), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&iDevConDevice2)))) {
			long numDevices;
			if (SUCCEEDED(iDevConDevice2->GetNumDevices(&numDevices))) {
				for (long i = 0; i < numDevices; ++i) {
					struct CCtDeviceInfoEx deviceInfoEx;
					if (SUCCEEDED(iDevConDevice2->GetDeviceInfoEx(i, (long*)&deviceInfoEx))) {
						if (FAILED(FindCtDeviceRegistry(deviceInfoEx.vendorId, deviceInfoEx.productId))) {
							if (FAILED(CreateCtDeviceRegistry(deviceInfoEx.vendorId, deviceInfoEx.productId)))
								continue;
						}
						LoadCtDeviceLibraries(deviceInfoEx.vendorId, deviceInfoEx.productId);
					}
				}
			}
			iDevConDevice2->Release();
		}
		CoUninitialize();
	}
	return (moduleCount != 0) ? S_OK : E_FAIL;
}

HRESULT CCtHelperDlg::RunPatchMixDSP()
{
	HRESULT ret = E_FAIL;
	if (SUCCEEDED(CoInitialize(NULL))) {
		IDevConDevice2* iDevConDevice2;
		if (SUCCEEDED(CoCreateInstance(__uuidof(DevConDevice), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&iDevConDevice2)))) {
			long numDevices;
			if (SUCCEEDED(iDevConDevice2->GetNumDevices(&numDevices))) {
				for (long i = 0; i < numDevices; ++i) {
					struct CCtDeviceInfoEx deviceInfoEx;
					STARTUPINFO startupInfo;
					PROCESS_INFORMATION processInfomation;
					ZeroMemory(&startupInfo, sizeof(startupInfo));
					startupInfo.cb = sizeof(startupInfo);
					processInfomation.hProcess = NULL;
					processInfomation.hThread = NULL;
					processInfomation.dwProcessId = 0;
					processInfomation.dwThreadId = 0;
					if (SUCCEEDED(iDevConDevice2->GetDeviceInfoEx(i, (long*)&deviceInfoEx))) {
						TCHAR sub[MAX_PATH];
						HKEY subhkey;
						_stprintf_s(sub, arraycount(sub), _T("%s\\%s\\%s%lx%s%lx%s%.8lx"), _T("System\\CurrentControlSet\\Control\\Creative Tech\\CtHelper"), _T("Launch"), _T("PCI&VEN_"), deviceInfoEx.vendorId, _T("&DEV_000"), deviceInfoEx.productId, _T("&SUBSYS_"), deviceInfoEx.subsysId);
						if (RegOpenKey(HKEY_LOCAL_MACHINE, &sub[0], &subhkey) == ERROR_SUCCESS) {
							TCHAR run[MAX_PATH];
							DWORD regType;
							DWORD runSiz = arraycount(run);
							if (RegQueryValueEx(subhkey, _T("Run"), 0, &regType, (LPBYTE)&run, &runSiz) == ERROR_SUCCESS) {
								TCHAR procname[MAX_PATH];
								TCHAR* p = _tcsrchr(run, _T('\\'));
								if (p != NULL) {
									++p;
									_tcscpy_s(procname, arraycount(procname), p);
									p = _tcschr(procname, _T('\"'));
									if (p != NULL)
										*p = _T('\0');
								} else {
									_tcscpy_s(procname, arraycount(procname), run);
								}
								HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
								if (snapshot != INVALID_HANDLE_VALUE) {
									PROCESSENTRY32 processEntry;
									BOOL missingPatchMiXDSP = TRUE;
									processEntry.dwSize = sizeof(processEntry);
									if (Process32First(snapshot, &processEntry) == TRUE) {
										TCHAR* q = processEntry.szExeFile;
										do {
											if (!_tcscmp(q, procname))
												missingPatchMiXDSP = FALSE;
										} while (Process32Next(snapshot, &processEntry) == TRUE);
									}
									CloseHandle(snapshot);
									if (missingPatchMiXDSP == TRUE || needPatchMiXDSP == TRUE) {
										needPatchMiXDSP = FALSE;
										if (CreateProcess(NULL, run, NULL, NULL, 0, 0, NULL, NULL, &startupInfo, &processInfomation) != 0) {
											CloseHandle(processInfomation.hProcess);
											CloseHandle(processInfomation.hThread);
											ret = S_OK;
										}
									}
								}
							}
							RegCloseKey(subhkey);
						}
					}
				}
			}
			if (iDevConDevice2 != NULL)
				iDevConDevice2->Release();
		}
		CoUninitialize();
	}
	return ret;
}

いつも通り Visual Studio Community 2026 では _stprintf(=sprintf) などはエラーになるので *_s などのセキュア関数に置換してある。

それにしても _stprintf_s の書式指定みるだけでアレだ、ふつうこう書くよね。

	_stprintf_s(sub, arraycount(sub), _T("Software\\Creative Tech\\CtHelper\\PCI&VEN_%04x&DEV_%04x%x\\Enable"), vendorId, productId);

DEV_000ってなんだよ 16 以上製品出す気無いのか?と問い詰めたくなるが、実際出すことなく E-MU 部門は潰れたので有言実行といえよう。

あとメンバ変数 moduleRegistries および modules に最大 10 しかモジュール読み込むスペース無いのはまあいいとして

  • 簡単に実装できる境界チェックすらやってない
  • レジストリ情報をこのメンバ変数 moduleRegistries にコピーしてるけど、都度読み直して再利用しないのだからそもそもスタック変数でよくね
  • RegEnumValue で列挙して Key/Value 取得してるのに Key に DLL_0 をコピーする必要無いよね

あたりは俺がレビュアーなら小一時間問い詰めてる、それとそもそも

  • HKEY_CURRENT_USER から情報読む
  • 無ければ HKEY_LOCAL_MACHINE を HKEY_CURRENT_USER にコピーする

みたいにプロセスに読み込むモジュールをユーザー毎に設定変更できるように設計してる意味ってあるのこれ。 デバイス刺さってるバスの差、つーか CardBus かそれ以外かの違いしか無いものをユーザー毎にする意味って。

次回

例の COM/OLE Viewer で タイプライブラリが表示されず IDL ぶっこ抜けない CTDPROXY.DLL の調査せんとならん。 DllGetObjectClass 他シンボルはあるので COM なのは間違いないみたいだけど CTDCIFCE.DLL と違って COM の実装に ATL 使ってないっぽいのである。

あと実際に CoCreateInstance() 呼んでも E_FAIL が返ってくるだけなので、無視してしまっていいのかもしれん。 コピペミスって REFIID がそもそも間違ってたという酷いオチ。