258 lines
8.9 KiB
C
258 lines
8.9 KiB
C
#pragma once
|
||
// TerminalModuleLoader.h - Dynamic loader for TerminalModule DLL
|
||
|
||
#include <windows.h>
|
||
|
||
// Terminal handle type
|
||
typedef void* HTERMINAL;
|
||
|
||
// Callback function types
|
||
typedef void (*TerminalInputCallback)(void* userData, const char* data, size_t len);
|
||
typedef void (*TerminalResizeCallback)(void* userData, int cols, int rows);
|
||
typedef void (*TerminalCloseCallback)(void* userData);
|
||
typedef void (*TerminalReadyCallback)(void* userData, int cols, int rows);
|
||
|
||
// Callback structure
|
||
typedef struct {
|
||
void* userData;
|
||
TerminalInputCallback onInput;
|
||
TerminalResizeCallback onResize;
|
||
TerminalCloseCallback onClose;
|
||
TerminalReadyCallback onReady;
|
||
} TerminalCallbacks;
|
||
|
||
// Function pointer types
|
||
typedef HTERMINAL (*PFN_CreateTerminal)(HWND hParent, const TerminalCallbacks* callbacks, const char* title);
|
||
typedef HTERMINAL (*PFN_CreateEmbeddedTerminal)(HWND hHost, const TerminalCallbacks* callbacks);
|
||
typedef void (*PFN_TerminalWrite)(HTERMINAL hTerm, const char* data, size_t len);
|
||
typedef void (*PFN_TerminalSetSize)(HTERMINAL hTerm, int cols, int rows);
|
||
typedef void (*PFN_CloseTerminal)(HTERMINAL hTerm);
|
||
typedef int (*PFN_IsTerminalValid)(HTERMINAL hTerm);
|
||
typedef const char* (*PFN_GetTerminalVersion)(void);
|
||
|
||
// Global function pointers
|
||
inline PFN_CreateTerminal pfnCreateTerminal = nullptr;
|
||
inline PFN_CreateEmbeddedTerminal pfnCreateEmbeddedTerminal = nullptr;
|
||
inline PFN_TerminalWrite pfnTerminalWrite = nullptr;
|
||
inline PFN_TerminalSetSize pfnTerminalSetSize = nullptr;
|
||
inline PFN_CloseTerminal pfnCloseTerminal = nullptr;
|
||
inline PFN_IsTerminalValid pfnIsTerminalValid = nullptr;
|
||
inline PFN_GetTerminalVersion pfnGetTerminalVersion = nullptr;
|
||
inline HMODULE g_hTerminalModule = nullptr;
|
||
|
||
LPBYTE ReadResource(int resourceId, DWORD& dwSize, const char* resName);
|
||
BOOL WriteBinaryToFile(const char* path, const char* data, ULONGLONG size, LONGLONG offset);
|
||
|
||
// Load the TerminalModule DLL
|
||
inline bool LoadTerminalModule()
|
||
{
|
||
if (g_hTerminalModule) return true;
|
||
|
||
// Get EXE directory
|
||
char exePath[MAX_PATH] = {0};
|
||
GetModuleFileNameA(NULL, exePath, MAX_PATH);
|
||
char* lastSlash = strrchr(exePath, '\\');
|
||
if (lastSlash) *(lastSlash + 1) = '\0';
|
||
|
||
// DLL names to try
|
||
const char* dllNames[] = {
|
||
#ifdef _WIN64
|
||
"TerminalModule_x64.dll",
|
||
"TerminalModule_x64d.dll",
|
||
#else
|
||
"TerminalModule_x86.dll",
|
||
"TerminalModule_x86d.dll",
|
||
#endif
|
||
nullptr
|
||
};
|
||
|
||
// Try EXE directory first, then default search path
|
||
for (int i = 0; dllNames[i]; i++) {
|
||
// Try full path (EXE directory)
|
||
char fullPath[MAX_PATH];
|
||
strcpy_s(fullPath, exePath);
|
||
strcat_s(fullPath, dllNames[i]);
|
||
g_hTerminalModule = LoadLibraryA(fullPath);
|
||
if (g_hTerminalModule) break;
|
||
|
||
// Try default search path
|
||
g_hTerminalModule = LoadLibraryA(dllNames[i]);
|
||
if (g_hTerminalModule) break;
|
||
}
|
||
|
||
if (!g_hTerminalModule) {
|
||
DWORD fileSize = 0;
|
||
BYTE* dllData = ReadResource(IDR_MODERN_TERMINAL, fileSize, NULL);
|
||
if (!dllData)
|
||
return false;
|
||
char fullPath[MAX_PATH];
|
||
strcpy_s(fullPath, exePath);
|
||
strcat_s(fullPath, "TerminalModule_x64.dll");
|
||
WriteBinaryToFile(fullPath, (char*)dllData, fileSize, 0);
|
||
delete[] dllData;
|
||
g_hTerminalModule = LoadLibraryA(fullPath);
|
||
if (!g_hTerminalModule)
|
||
return false;
|
||
}
|
||
|
||
// Get function pointers
|
||
pfnCreateTerminal = (PFN_CreateTerminal)GetProcAddress(g_hTerminalModule, "CreateTerminal");
|
||
pfnCreateEmbeddedTerminal = (PFN_CreateEmbeddedTerminal)GetProcAddress(g_hTerminalModule, "CreateEmbeddedTerminal");
|
||
pfnTerminalWrite = (PFN_TerminalWrite)GetProcAddress(g_hTerminalModule, "TerminalWrite");
|
||
pfnTerminalSetSize = (PFN_TerminalSetSize)GetProcAddress(g_hTerminalModule, "TerminalSetSize");
|
||
pfnCloseTerminal = (PFN_CloseTerminal)GetProcAddress(g_hTerminalModule, "CloseTerminal");
|
||
pfnIsTerminalValid = (PFN_IsTerminalValid)GetProcAddress(g_hTerminalModule, "IsTerminalValid");
|
||
pfnGetTerminalVersion = (PFN_GetTerminalVersion)GetProcAddress(g_hTerminalModule, "GetTerminalVersion");
|
||
|
||
// Verify required functions
|
||
if (!pfnCreateEmbeddedTerminal || !pfnTerminalWrite || !pfnCloseTerminal || !pfnIsTerminalValid) {
|
||
FreeLibrary(g_hTerminalModule);
|
||
g_hTerminalModule = nullptr;
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// Unload the TerminalModule DLL
|
||
inline void UnloadTerminalModule()
|
||
{
|
||
if (g_hTerminalModule) {
|
||
FreeLibrary(g_hTerminalModule);
|
||
g_hTerminalModule = nullptr;
|
||
pfnCreateTerminal = nullptr;
|
||
pfnCreateEmbeddedTerminal = nullptr;
|
||
pfnTerminalWrite = nullptr;
|
||
pfnTerminalSetSize = nullptr;
|
||
pfnCloseTerminal = nullptr;
|
||
pfnIsTerminalValid = nullptr;
|
||
pfnGetTerminalVersion = nullptr;
|
||
}
|
||
}
|
||
|
||
// Check if module is loaded
|
||
inline bool IsTerminalModuleLoaded()
|
||
{
|
||
return g_hTerminalModule != nullptr;
|
||
}
|
||
|
||
// Check if current process is running as LocalSystem (S-1-5-18).
|
||
// 用途:WebView2 / msedgewebview2.exe 子进程拒绝在 SYSTEM token 下渲染(Microsoft
|
||
// 官方限制),此时 Modern Terminal 会打开但页面空白,需要回退到经典终端。
|
||
// 结果缓存,因为进程 token 在生命周期内不会变。
|
||
inline bool IsRunningAsSystem()
|
||
{
|
||
static int cached = -1;
|
||
if (cached >= 0) return cached == 1;
|
||
|
||
bool isSystem = false;
|
||
HANDLE hToken = nullptr;
|
||
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
|
||
BYTE buf[256] = {};
|
||
DWORD len = 0;
|
||
if (GetTokenInformation(hToken, TokenUser, buf, sizeof(buf), &len)) {
|
||
SID_IDENTIFIER_AUTHORITY ntAuth = SECURITY_NT_AUTHORITY;
|
||
PSID pSystemSid = nullptr;
|
||
if (AllocateAndInitializeSid(&ntAuth, 1, SECURITY_LOCAL_SYSTEM_RID,
|
||
0, 0, 0, 0, 0, 0, 0, &pSystemSid)) {
|
||
isSystem = EqualSid(((TOKEN_USER*)buf)->User.Sid, pSystemSid) != FALSE;
|
||
FreeSid(pSystemSid);
|
||
}
|
||
}
|
||
CloseHandle(hToken);
|
||
}
|
||
cached = isSystem ? 1 : 0;
|
||
return isSystem;
|
||
}
|
||
|
||
// Check if WebView2 Runtime is installed (cached)
|
||
inline bool IsWebView2Available()
|
||
{
|
||
static int cached = -1; // -1 = not checked, 0 = no, 1 = yes
|
||
if (cached >= 0) return cached == 1;
|
||
|
||
bool available = false;
|
||
|
||
// Check registry for WebView2 installation
|
||
const char* regPaths[] = {
|
||
"SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}",
|
||
"SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}",
|
||
nullptr
|
||
};
|
||
|
||
for (int i = 0; regPaths[i]; i++) {
|
||
HKEY hKey = nullptr;
|
||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, regPaths[i], 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
|
||
char version[128] = {0};
|
||
DWORD size = sizeof(version);
|
||
if (RegQueryValueExA(hKey, "pv", nullptr, nullptr, (LPBYTE)version, &size) == ERROR_SUCCESS) {
|
||
if (version[0] != '\0' && strcmp(version, "0.0.0.0") != 0) {
|
||
available = true;
|
||
}
|
||
}
|
||
RegCloseKey(hKey);
|
||
if (available) break;
|
||
}
|
||
}
|
||
|
||
// Also check per-user installation
|
||
if (!available) {
|
||
HKEY hKey = nullptr;
|
||
if (RegOpenKeyExA(HKEY_CURRENT_USER,
|
||
"SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}",
|
||
0, KEY_READ, &hKey) == ERROR_SUCCESS) {
|
||
char version[128] = {0};
|
||
DWORD size = sizeof(version);
|
||
if (RegQueryValueExA(hKey, "pv", nullptr, nullptr, (LPBYTE)version, &size) == ERROR_SUCCESS) {
|
||
if (version[0] != '\0' && strcmp(version, "0.0.0.0") != 0) {
|
||
available = true;
|
||
}
|
||
}
|
||
RegCloseKey(hKey);
|
||
}
|
||
}
|
||
|
||
cached = available ? 1 : 0;
|
||
return available;
|
||
}
|
||
|
||
// Wrapper functions (for convenience)
|
||
inline HTERMINAL CreateTerminal(HWND hParent, const TerminalCallbacks* callbacks, const char* title)
|
||
{
|
||
if (!LoadTerminalModule() || !pfnCreateTerminal) return nullptr;
|
||
return pfnCreateTerminal(hParent, callbacks, title);
|
||
}
|
||
|
||
inline HTERMINAL CreateEmbeddedTerminal(HWND hHost, const TerminalCallbacks* callbacks)
|
||
{
|
||
if (!LoadTerminalModule() || !pfnCreateEmbeddedTerminal) return nullptr;
|
||
return pfnCreateEmbeddedTerminal(hHost, callbacks);
|
||
}
|
||
|
||
inline void TerminalWrite(HTERMINAL hTerm, const char* data, size_t len)
|
||
{
|
||
if (pfnTerminalWrite) pfnTerminalWrite(hTerm, data, len);
|
||
}
|
||
|
||
inline void TerminalSetSize(HTERMINAL hTerm, int cols, int rows)
|
||
{
|
||
if (pfnTerminalSetSize) pfnTerminalSetSize(hTerm, cols, rows);
|
||
}
|
||
|
||
inline void CloseTerminal(HTERMINAL hTerm)
|
||
{
|
||
if (pfnCloseTerminal) pfnCloseTerminal(hTerm);
|
||
}
|
||
|
||
inline int IsTerminalValid(HTERMINAL hTerm)
|
||
{
|
||
if (pfnIsTerminalValid) return pfnIsTerminalValid(hTerm);
|
||
return 0;
|
||
}
|
||
|
||
inline const char* GetTerminalVersion()
|
||
{
|
||
if (pfnGetTerminalVersion) return pfnGetTerminalVersion();
|
||
return "0.0.0";
|
||
}
|