Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
ac7f62f
chore(wip): checkpoint pre-refactor audit changes
Haerbin23456 Mar 17, 2026
faa8d3c
refactor(phase-a): add verification pipeline, localization guards, an…
Haerbin23456 Mar 17, 2026
3edb300
refactor(ui): replace dashboard last-scan string mode with enum
Haerbin23456 Mar 17, 2026
b15a154
refactor(ui): localize dashboard extension actions and status messages
Haerbin23456 Mar 17, 2026
4eb4f65
refactor(ui): centralize dashboard category tags as constants
Haerbin23456 Mar 17, 2026
1206422
refactor(core): centralize benchmark semantics and remove key magic-s…
Haerbin23456 Mar 17, 2026
6412b3e
refactor(ui): replace dashboard status trigger literals with shared c…
Haerbin23456 Mar 17, 2026
7da5cc1
chore(quality): guard against status magic literals in core benchmark…
Haerbin23456 Mar 17, 2026
9de52df
refactor(semantics): centralize status tokens and category location h…
Haerbin23456 Mar 17, 2026
4fcbfd4
refactor(ui): centralize category filtering and type status visibilit…
Haerbin23456 Mar 17, 2026
467455f
refactor(semantics): unify packaged-extension capability checks acros…
Haerbin23456 Mar 17, 2026
fb605ae
refactor(core): centralize interface and location semantics for bench…
Haerbin23456 Mar 17, 2026
4048902
refactor(semantics): optimize category resolution and add precedence …
Haerbin23456 Mar 17, 2026
6f0e3aa
chore(quality): harden semantic helper guardrails and sync audit prog…
Haerbin23456 Mar 17, 2026
2abb2c2
refactor(vm): extract shared scan-session and STA file scan helpers
Haerbin23456 Mar 18, 2026
1f12ffe
refactor(vm): add disposable lifecycle and balanced event subscriptio…
Haerbin23456 Mar 18, 2026
79ee3d3
refactor(core): replace runtime magic numbers with centralized semant…
Haerbin23456 Mar 18, 2026
bc3d156
refactor(core): extract benchmark statistics calculator from dashboar…
Haerbin23456 Mar 18, 2026
12d7b2e
refactor(core): harden static verb key parsing and guard malformed en…
Haerbin23456 Mar 18, 2026
6a26b68
refactor(ipc): centralize protocol/runtime semantics for hook pipe cl…
Haerbin23456 Mar 18, 2026
48c46d6
refactor(ipc): centralize probe and buffer runtime semantics in hook …
Haerbin23456 Mar 18, 2026
0bb084d
refactor(ipc): consolidate retry delay flow in hook client
Haerbin23456 Mar 18, 2026
7b01836
refactor(core): localize benchmark detailed status messages via local…
Haerbin23456 Mar 18, 2026
044511f
refactor(vm): extract registry path normalization into helper
Haerbin23456 Mar 18, 2026
889c1f6
refactor(ipc): unify response delimiter semantics and remove remainin…
Haerbin23456 Mar 18, 2026
360c552
refactor(vm): consolidate scan outcome notifications and error handli…
Haerbin23456 Mar 18, 2026
d80236b
refactor(quality): reduce source-check boilerplate with reusable helpers
Haerbin23456 Mar 18, 2026
286d3f2
refactor(vm): add disposed guards to async filter and event handlers
Haerbin23456 Mar 18, 2026
1c8f4f5
refactor(vm): centralize reconnect and clipboard retry timings in run…
Haerbin23456 Mar 18, 2026
c145eee
refactor(vm): centralize clipboard COM retry error code in runtime se…
Haerbin23456 Mar 18, 2026
5120ee4
refactor(core): use priority-based location category resolution in se…
Haerbin23456 Mar 18, 2026
1a36b20
refactor(core): centralize static-verb registry location and disabled…
Haerbin23456 Mar 18, 2026
69029a7
refactor(core): centralize timeout-like hook error classification sem…
Haerbin23456 Mar 18, 2026
783c9d4
refactor(ipc): centralize round-trip completion and retry flow helpers
Haerbin23456 Mar 18, 2026
7988400
docs(audit): update stage-a progress with recent semantic centralizat…
Haerbin23456 Mar 18, 2026
aa21c8c
refactor(core): centralize unstable-handler tokens and env switch sem…
Haerbin23456 Mar 18, 2026
2436a8d
refactor(quality): derive forbidden literal checks from semantics and…
Haerbin23456 Mar 18, 2026
591e858
refactor(core): centralize registry scanner location labels and forma…
Haerbin23456 Mar 18, 2026
fc7cb43
refactor(core): unify static-verb key semantics across scanner and be…
Haerbin23456 Mar 18, 2026
59f752c
refactor(core): localize unknown-clsid fallback name and enforce with…
Haerbin23456 Mar 18, 2026
7c3754e
refactor(core): centralize registry scanner path templates into seman…
Haerbin23456 Mar 18, 2026
4285f7f
refactor(core): centralize association type tokens and scanner path m…
Haerbin23456 Mar 18, 2026
2ec4497
refactor(ui): semanticize icon source markers and localize manifest a…
Haerbin23456 Mar 18, 2026
76fb648
refactor(core): centralize icon location protocol semantics across pa…
Haerbin23456 Mar 18, 2026
9427ac0
refactor(core): extract package manifest parsing literals into shared…
Haerbin23456 Mar 18, 2026
8ece0b7
refactor(core): remove unused package manifest namespace fields
Haerbin23456 Mar 18, 2026
b2acc2e
refactor(core): replace real-shell unsupported sentinel literal with …
Haerbin23456 Mar 18, 2026
3bb948f
refactor(core): centralize COM registry metadata schema literals in s…
Haerbin23456 Mar 18, 2026
055fd40
refactor(ui): centralize icon parsing strategy constants in benchmark…
Haerbin23456 Mar 18, 2026
6528c5b
refactor(core): deduplicate packaged-com class-index path semantics
Haerbin23456 Mar 18, 2026
2fd93dc
refactor(core): semanticize residual registry and icon parsing literals
Haerbin23456 Mar 18, 2026
b520300
refactor(ipc): switch hook pipe to strict framed CMP1 protocol
Haerbin23456 Mar 18, 2026
5448a02
refactor(core): streamline benchmark execution orchestration
Haerbin23456 Mar 18, 2026
ed6061a
fix(core): enforce strict file-target semantics for analyze-file flow
Haerbin23456 Mar 18, 2026
55c3133
refactor(core): simplify hook IPC retry flow and remove dead paths
Haerbin23456 Mar 18, 2026
86e6e3a
refactor(core): deduplicate path-specific registry scan flow
Haerbin23456 Mar 18, 2026
954310a
refactor(core): decompose benchmark hook enrichment pipeline
Haerbin23456 Mar 18, 2026
45e5c3a
hardening(hook): reject remote clients on named pipe server
Haerbin23456 Mar 18, 2026
034e386
refactor(core): remove noisy semantics and split clsid metadata resol…
Haerbin23456 Mar 18, 2026
93a45a3
temp
Haerbin23456 Mar 18, 2026
a07a365
refactor(ipc): enforce framed protocol and remove dead runtime constants
Haerbin23456 Mar 18, 2026
40df67f
refactor(core): simplify benchmark and registry scan orchestration
Haerbin23456 Mar 18, 2026
fed4827
refactor(ipc): streamline retry locking and surface probe failure rea…
Haerbin23456 Mar 18, 2026
db73d2c
refactor(core): centralize measured-result post-processing in benchma…
Haerbin23456 Mar 18, 2026
dd6a17f
refactor(scanner): extract clsid parsing helpers for location scan
Haerbin23456 Mar 18, 2026
4091a74
refactor(core): flatten clsid metadata resolution branch flow
Haerbin23456 Mar 18, 2026
b935a47
refactor(ipc): propagate hook error codes and fallback reasons to dia…
Haerbin23456 Mar 18, 2026
c86e61a
refactor(status): migrate benchmark statuses to typed enum semantics
Haerbin23456 Mar 18, 2026
a4d5d31
refactor(ui): localize BenchmarkStatus display via typed mapping
Haerbin23456 Mar 18, 2026
1fe11cc
feat(logging): add structured JSON scan lifecycle and IPC events
Haerbin23456 Mar 18, 2026
eb00f5b
feat(logging): include observed menu display names in scan item events
Haerbin23456 Mar 18, 2026
5763086
feat(logging): normalize scan dimensions and enrich probe diagnostics
Haerbin23456 Mar 18, 2026
a24dd1d
chore(logging): cap app.log size and trim old entries
Haerbin23456 Mar 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
- name: Build Solution
run: dotnet build ContextMenuProfiler.sln -c Release

- name: Run Quality Checks
run: dotnet run --project ContextMenuProfiler.QualityChecks\ContextMenuProfiler.QualityChecks.csproj -c Release --no-build

- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
Expand Down
148 changes: 106 additions & 42 deletions ContextMenuProfiler.Hook/src/ipc_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,61 @@
static const LONG kMaxConcurrentPipeClients = 4;
static volatile LONG g_ActivePipeClients = 0;
static const DWORD kWorkerTimeoutMs = 1800;
static const DWORD kFrameHeaderBytes = 4;
static const size_t kMaxRequestBytes = 16384;
static const size_t kMaxResponseBytes = 65536;

struct IpcWorkItem {
char request[2048];
std::string request;
char response[65536];
int maxLen;
volatile LONG releaseByWorker;
};

static bool ReadExactFromPipe(HANDLE hPipe, void* buffer, DWORD bytesToRead) {
BYTE* cursor = reinterpret_cast<BYTE*>(buffer);
DWORD totalRead = 0;
while (totalRead < bytesToRead) {
DWORD chunkRead = 0;
BOOL ok = ReadFile(hPipe, cursor + totalRead, bytesToRead - totalRead, &chunkRead, NULL);
if (!ok || chunkRead == 0) {
return false;
}

totalRead += chunkRead;
}

return true;
}

static bool WriteFrameToPipe(HANDLE hPipe, const char* payload, DWORD payloadLength) {
if (payloadLength > static_cast<DWORD>(kMaxResponseBytes)) {
return false;
}

DWORD written = 0;
DWORD frameLength = payloadLength;
if (!WriteFile(hPipe, &frameLength, kFrameHeaderBytes, &written, NULL) || written != kFrameHeaderBytes) {
return false;
}

if (payloadLength == 0) {
FlushFileBuffers(hPipe);
return true;
}

if (!WriteFile(hPipe, payload, payloadLength, &written, NULL) || written != payloadLength) {
return false;
}

FlushFileBuffers(hPipe);
return true;
}

static void WriteJsonFrameToPipe(HANDLE hPipe, const char* json) {
WriteFrameToPipe(hPipe, json, static_cast<DWORD>(strlen(json)));
}

void DoIpcWorkInternal(const char* request, char* response, int maxLen) {
std::string reqStr = request;

Expand All @@ -25,21 +72,33 @@ void DoIpcWorkInternal(const char* request, char* response, int maxLen) {
return;
}

std::string mode = "AUTO";
std::string mode;
std::string clsidStr, pathStr, dllHintStr;

if (reqStr.substr(0, 4) == "COM|") {
mode = "COM";
reqStr = reqStr.substr(4);
} else if (reqStr.substr(0, 5) == "ECMD|") {
mode = "ECMD";
reqStr = reqStr.substr(5);
if (reqStr.rfind("CMP1|", 0) != 0) {
snprintf(response, maxLen, "{\"success\":false,\"code\":\"E_PROTOCOL\",\"error\":\"Unsupported Protocol\"}");
return;
}

reqStr = reqStr.substr(5);
size_t sep0 = reqStr.find('|');
if (sep0 == std::string::npos) {
snprintf(response, maxLen, "{\"success\":false,\"code\":\"E_FORMAT\",\"error\":\"Missing Mode\"}");
return;
}

mode = reqStr.substr(0, sep0);
reqStr = reqStr.substr(sep0 + 1);

if (mode != "AUTO" && mode != "COM" && mode != "ECMD") {
snprintf(response, maxLen, "{\"success\":false,\"code\":\"E_MODE\",\"error\":\"Unsupported Mode\"}");
return;
}

// Format: CLSID|Path[|DllHint]
size_t sep1 = reqStr.find('|');
if (sep1 == std::string::npos) {
snprintf(response, maxLen, "{\"success\":false,\"error\":\"Format Error\"}");
snprintf(response, maxLen, "{\"success\":false,\"code\":\"E_FORMAT\",\"error\":\"Format Error\"}");
return;
}
clsidStr = reqStr.substr(0, sep1);
Expand All @@ -55,18 +114,27 @@ void DoIpcWorkInternal(const char* request, char* response, int maxLen) {

CLSID clsid;
wchar_t wClsid[64];
MultiByteToWideChar(CP_UTF8, 0, clsidStr.c_str(), -1, wClsid, 64);
if (MultiByteToWideChar(CP_UTF8, 0, clsidStr.c_str(), -1, wClsid, 64) <= 0) {
snprintf(response, maxLen, "{\"success\":false,\"code\":\"E_CLSID_UTF8\",\"error\":\"Bad CLSID Encoding\"}");
return;
}
if (FAILED(CLSIDFromString(wClsid, &clsid))) {
snprintf(response, maxLen, "{\"success\":false,\"error\":\"Bad CLSID\"}");
snprintf(response, maxLen, "{\"success\":false,\"code\":\"E_CLSID\",\"error\":\"Bad CLSID\"}");
return;
}

wchar_t wPath[MAX_PATH];
MultiByteToWideChar(CP_UTF8, 0, pathStr.c_str(), -1, wPath, MAX_PATH);
if (MultiByteToWideChar(CP_UTF8, 0, pathStr.c_str(), -1, wPath, MAX_PATH) <= 0) {
snprintf(response, maxLen, "{\"success\":false,\"code\":\"E_PATH_UTF8\",\"error\":\"Bad Path Encoding\"}");
return;
}

wchar_t wDllHint[MAX_PATH] = { 0 };
if (!dllHintStr.empty()) {
MultiByteToWideChar(CP_UTF8, 0, dllHintStr.c_str(), -1, wDllHint, MAX_PATH);
if (MultiByteToWideChar(CP_UTF8, 0, dllHintStr.c_str(), -1, wDllHint, MAX_PATH) <= 0) {
snprintf(response, maxLen, "{\"success\":false,\"code\":\"E_DLLHINT_UTF8\",\"error\":\"Bad DllHint Encoding\"}");
return;
}
}

// We'll pass the dllHint to the handlers
Expand Down Expand Up @@ -99,7 +167,7 @@ void DoIpcWork(const char* request, char* response, int maxLen) {
DWORD WINAPI DoIpcWorkThread(LPVOID param) {
IpcWorkItem* item = (IpcWorkItem*)param;
item->response[0] = '\0';
DoIpcWork(item->request, item->response, item->maxLen);
DoIpcWork(item->request.c_str(), item->response, item->maxLen);
if (InterlockedCompareExchange(&item->releaseByWorker, 0, 0) == 1) {
delete item;
}
Expand All @@ -110,41 +178,45 @@ DWORD WINAPI HandlePipeClientThread(LPVOID param) {
HANDLE hPipe = (HANDLE)param;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

char req[2048];
DWORD read = 0;
DWORD written = 0;

if (ReadFile(hPipe, req, 2047, &read, NULL) && read > 0) {
req[read] = '\0';
DWORD requestLength = 0;
if (ReadExactFromPipe(hPipe, &requestLength, kFrameHeaderBytes)) {
if (requestLength == 0 || requestLength > static_cast<DWORD>(kMaxRequestBytes)) {
WriteJsonFrameToPipe(hPipe, "{\"success\":false,\"code\":\"E_REQ_TOO_LARGE\",\"error\":\"Request Too Large\"}");
} else {
std::string req(requestLength, '\0');
if (!ReadExactFromPipe(hPipe, &req[0], requestLength)) {
WriteJsonFrameToPipe(hPipe, "{\"success\":false,\"code\":\"E_REQ_READ\",\"error\":\"Request Read Failed\"}");
} else {
IpcWorkItem* workItem = new IpcWorkItem();
strncpy_s(workItem->request, sizeof(workItem->request), req, _TRUNCATE);
workItem->request = std::move(req);
workItem->response[0] = '\0';
workItem->maxLen = 65535;
workItem->maxLen = static_cast<int>(kMaxResponseBytes) - 1;
workItem->releaseByWorker = 0;

HANDLE hWorkThread = CreateThread(NULL, 0, DoIpcWorkThread, workItem, 0, NULL);
if (!hWorkThread) {
const char* errRes = "{\"success\":false,\"error\":\"Hook Worker Launch Failed\"}";
WriteFile(hPipe, errRes, (DWORD)strlen(errRes), &written, NULL);
FlushFileBuffers(hPipe);
WriteJsonFrameToPipe(hPipe, "{\"success\":false,\"error\":\"Hook Worker Launch Failed\"}");
delete workItem;
} else {
DWORD waitRc = WaitForSingleObject(hWorkThread, kWorkerTimeoutMs);
if (waitRc == WAIT_OBJECT_0) {
int resLen = (int)strlen(workItem->response);
DWORD resLen = static_cast<DWORD>(strnlen_s(workItem->response, workItem->maxLen));
if (resLen > 0) {
WriteFile(hPipe, workItem->response, (DWORD)resLen, &written, NULL);
FlushFileBuffers(hPipe);
WriteFrameToPipe(hPipe, workItem->response, resLen);
} else {
WriteJsonFrameToPipe(hPipe, "{\"success\":false,\"error\":\"Empty Hook Response\"}");
}
delete workItem;
} else {
const char* timeoutRes = "{\"success\":false,\"error\":\"Hook Worker Timeout\"}";
WriteFile(hPipe, timeoutRes, (DWORD)strlen(timeoutRes), &written, NULL);
FlushFileBuffers(hPipe);
WriteJsonFrameToPipe(hPipe, "{\"success\":false,\"error\":\"Hook Worker Timeout\"}");
InterlockedExchange(&workItem->releaseByWorker, 1);
}
CloseHandle(hWorkThread);
}
}
}
} else {
WriteJsonFrameToPipe(hPipe, "{\"success\":false,\"code\":\"E_REQ_HEADER\",\"error\":\"Request Header Read Failed\"}");
}

DisconnectNamedPipe(hPipe);
Expand All @@ -160,17 +232,12 @@ DWORD WINAPI PipeThread(LPVOID) {
Gdiplus::GdiplusStartupInput gsi;
Gdiplus::GdiplusStartup(&g_GdiplusToken, &gsi, NULL);

PSECURITY_DESCRIPTOR pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(pSD, TRUE, NULL, FALSE);
SECURITY_ATTRIBUTES sa = { sizeof(sa), pSD, FALSE };

while (!g_ShouldExit) {
HANDLE hPipe = CreateNamedPipeA(
"\\\\.\\pipe\\ContextMenuProfilerHook",
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, 65536, 65536, 0, &sa);
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
PIPE_UNLIMITED_INSTANCES, 65536, 65536, 0, NULL);
if (hPipe == INVALID_HANDLE_VALUE) { Sleep(100); continue; }

if (ConnectNamedPipe(hPipe, NULL) || GetLastError() == ERROR_PIPE_CONNECTED) {
Expand All @@ -182,9 +249,7 @@ DWORD WINAPI PipeThread(LPVOID) {
if (active > kMaxConcurrentPipeClients) {
InterlockedDecrement(&g_ActivePipeClients);
const char* busyRes = "{\"success\":false,\"error\":\"Hook Busy\"}";
DWORD written = 0;
WriteFile(hPipe, busyRes, (DWORD)strlen(busyRes), &written, NULL);
FlushFileBuffers(hPipe);
WriteJsonFrameToPipe(hPipe, busyRes);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
continue;
Expand All @@ -203,7 +268,6 @@ DWORD WINAPI PipeThread(LPVOID) {

Gdiplus::GdiplusShutdown(g_GdiplusToken);
MH_Uninitialize();
LocalFree(pSD);
CoUninitialize();
LogToFile(L"--- Pipe Thread Exited Cleanly ---\n");
return 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\ContextMenuProfiler.UI\ContextMenuProfiler.UI.csproj" />
</ItemGroup>
</Project>
Loading
Loading