fix(shell): support QQ 9.9.36 Windows file query aliases

This commit is contained in:
手瓜一十雪
2026-10-07 00:07:42 +08:00
parent 463d3ea5b1
commit ec6aadaeea
5 changed files with 133 additions and 0 deletions
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.20)
project(NapCatWindowsLoaderTest LANGUAGES CXX)
add_executable(windows-loader-test probe.cpp)
+96
View File
@@ -0,0 +1,96 @@
#include <windows.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
using QueryByName = BOOL(WINAPI *)(PCWSTR, FILE_INFO_BY_NAME_CLASS, PVOID, ULONG);
int wmain(int argumentCount, wchar_t **arguments)
{
if (argumentCount != 3)
return 2;
const wchar_t *providers[] = {L"kernel32.dll", L"kernelbase.dll", L"api-ms-win-core-file-l2-1-4.dll"};
HMODULE providerModules[3]{};
QueryByName nativeQueries[3]{};
unsigned char nativeCode[3][12]{};
unsigned char nativeInfo[3][4][1024]{};
BOOL nativeResults[3][4]{};
DWORD nativeErrors[3][4]{};
for (int providerIndex = 0; providerIndex < 3; ++providerIndex)
{
providerModules[providerIndex] = LoadLibraryW(providers[providerIndex]);
auto query = reinterpret_cast<QueryByName>(GetProcAddress(providerModules[providerIndex], "GetFileInformationByName"));
if (!query)
return 3;
nativeQueries[providerIndex] = query;
std::memcpy(nativeCode[providerIndex], reinterpret_cast<void *>(query), sizeof(nativeCode[providerIndex]));
for (int infoClass = 0; infoClass < 4; ++infoClass)
{
SetLastError(ERROR_SUCCESS);
nativeResults[providerIndex][infoClass] = query(arguments[2], static_cast<FILE_INFO_BY_NAME_CLASS>(infoClass), nativeInfo[providerIndex][infoClass], sizeof(nativeInfo[providerIndex][infoClass]));
nativeErrors[providerIndex][infoClass] = GetLastError();
}
}
FILE_STAT_BASIC_INFORMATION expected{};
if (!nativeQueries[0](arguments[2], FileStatBasicByNameInfo, &expected, sizeof(expected)))
return 4;
if (_wputenv_s(L"NAPCAT_LOAD_PATH", arguments[2]) || _wputenv_s(L"NAPCAT_PATCH_PACKAGE", arguments[2]))
return 5;
if (!LoadLibraryW(arguments[1]))
{
std::printf("LoadLibrary failed: %lu\n", GetLastError());
return 6;
}
const std::wstring missingRoot = L"C:\\napcat-loader-test-" + std::to_wstring(GetCurrentProcessId());
const std::wstring loaderPath = missingRoot + L"\\loadNapCat.js";
const std::wstring packagePath = missingRoot + L"\\resources\\app\\package.json";
const std::wstring unrelatedPath = missingRoot + L"\\unrelated.txt";
const wchar_t *redirectedPaths[] = {loaderPath.c_str(), packagePath.c_str()};
int failures = 0;
for (int providerIndex = 0; providerIndex < 3; ++providerIndex)
{
auto query = reinterpret_cast<QueryByName>(GetProcAddress(providerModules[providerIndex], "GetFileInformationByName"));
bool preserved = std::memcmp(nativeCode[providerIndex], reinterpret_cast<void *>(nativeQueries[providerIndex]), sizeof(nativeCode[providerIndex])) == 0;
std::wprintf(L"%ls native code preserved: %ls\n", providers[providerIndex], preserved ? L"PASS" : L"FAIL");
failures += !preserved;
for (int infoClass = 0; infoClass < 4; ++infoClass)
{
unsigned char actual[1024]{};
SetLastError(ERROR_SUCCESS);
BOOL result = query(arguments[2], static_cast<FILE_INFO_BY_NAME_CLASS>(infoClass), actual, sizeof(actual));
DWORD error = GetLastError();
bool passed = result == nativeResults[providerIndex][infoClass] &&
(result ? std::memcmp(actual, nativeInfo[providerIndex][infoClass], sizeof(actual)) == 0 : error == nativeErrors[providerIndex][infoClass]);
std::wprintf(L"%ls information class %d native parity: %ls\n", providers[providerIndex], infoClass, passed ? L"PASS" : L"FAIL");
failures += !passed;
}
for (const auto *redirectedPath : redirectedPaths)
{
FILE_STAT_BASIC_INFORMATION actual{};
BOOL result = query(redirectedPath, FileStatBasicByNameInfo, &actual, sizeof(actual));
bool passed = result && actual.EndOfFile.QuadPart == expected.EndOfFile.QuadPart &&
actual.FileId.QuadPart == expected.FileId.QuadPart && actual.FileAttributes == expected.FileAttributes;
std::wprintf(L"%ls redirect %ls: %ls\n", providers[providerIndex], redirectedPath, passed ? L"PASS" : L"FAIL");
failures += !passed;
}
FILE_STAT_BASIC_INFORMATION missing{};
SetLastError(ERROR_SUCCESS);
BOOL nativeResult = nativeQueries[providerIndex](unrelatedPath.c_str(), FileStatBasicByNameInfo, &missing, sizeof(missing));
DWORD nativeError = GetLastError();
SetLastError(ERROR_SUCCESS);
BOOL result = query(unrelatedPath.c_str(), FileStatBasicByNameInfo, &missing, sizeof(missing));
bool passed = !result && result == nativeResult && GetLastError() == nativeError;
std::wprintf(L"%ls unrelated missing file: %ls\n", providers[providerIndex], passed ? L"PASS" : L"FAIL");
failures += !passed;
}
FILE_STAT_BASIC_INFORMATION direct{};
BOOL result = GetFileInformationByName(loaderPath.c_str(), FileStatBasicByNameInfo, &direct, sizeof(direct));
bool passed = result && direct.FileId.QuadPart == expected.FileId.QuadPart && direct.EndOfFile.QuadPart == expected.EndOfFile.QuadPart;
std::printf("static import redirection: %s\n", passed ? "PASS" : "FAIL");
failures += !passed;
return failures ? 1 : 0;
}
+20
View File
@@ -9,6 +9,26 @@ on:
permissions: write-all
jobs:
windows-loader-test:
name: Windows Loader Test
needs: build
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- uses: actions/download-artifact@v7
with:
name: NapCat.Shell
path: shell-dist
- name: Verify Windows loader file queries
shell: pwsh
run: |
cmake -S .github/tests/windows-loader -B "$env:RUNNER_TEMP/windows-loader-test" -A x64
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
cmake --build "$env:RUNNER_TEMP/windows-loader-test" --config Release
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& "$env:RUNNER_TEMP/windows-loader-test/Release/windows-loader-test.exe" "$pwd/shell-dist/NapCatWinBootHook.dll" "$pwd/shell-dist/loadNapCat.js"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
typecheck-and-test:
name: Type Check & Test
runs-on: ubuntu-latest
+14
View File
@@ -47,6 +47,7 @@ jobs:
- name: Build NapCat.${{ matrix.target.name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NAPCAT_VERSION: ${{ inputs.version }}
run: |
pnpm i
pnpm --filter napcat-webui-frontend run build || exit 1
@@ -192,6 +193,9 @@ jobs:
needs: assemble-windows-node
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
with:
ref: ${{ inputs.version }}
- uses: actions/download-artifact@v7
with:
name: NapCat.Shell.Windows.Node
@@ -206,6 +210,16 @@ jobs:
& .\node.exe -e "const wrapper = require('./wrapper.node'); if (!wrapper.NodeQQNTWrapperUtil) throw new Error('Missing NodeQQNTWrapperUtil'); console.log('wrapper.node native-load PASS'); process.exit(0);"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Verify Windows loader file queries
shell: pwsh
run: |
cmake -S .github/tests/windows-loader -B "$env:RUNNER_TEMP/windows-loader-test" -A x64
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
cmake --build "$env:RUNNER_TEMP/windows-loader-test" --config Release
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& "$env:RUNNER_TEMP/windows-loader-test/Release/windows-loader-test.exe" "$pwd/windows-node/napcat/NapCatWinBootHook.dll" "$pwd/windows-node/napcat/loadNapCat.js"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
release-napcat:
needs: [build, assemble-windows-node, validate-windows-node]
runs-on: ubuntu-latest
Binary file not shown.