feat(diagnostic): 完成阶段 0C 视觉与 IME 坐标探测
This commit is contained in:
@@ -0,0 +1,722 @@
|
||||
#include "VisualProbeCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
namespace VisualProbe
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using SetProcessDpiAwarenessContextFn = BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT);
|
||||
using GetDpiForWindowFn = UINT(WINAPI*)(HWND);
|
||||
using GetThreadDpiAwarenessContextFn = DPI_AWARENESS_CONTEXT(WINAPI*)();
|
||||
using GetWindowDpiAwarenessContextFn = DPI_AWARENESS_CONTEXT(WINAPI*)(HWND);
|
||||
using GetAwarenessFromDpiAwarenessContextFn = DPI_AWARENESS(WINAPI*)(DPI_AWARENESS_CONTEXT);
|
||||
using GetProcessDpiAwarenessFn = HRESULT(WINAPI*)(HANDLE, int*);
|
||||
|
||||
std::string awarenessName(int awareness)
|
||||
{
|
||||
switch (awareness)
|
||||
{
|
||||
case 0: return "unaware";
|
||||
case 1: return "system";
|
||||
case 2: return "per-monitor";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string contextAwarenessName(DPI_AWARENESS_CONTEXT context) noexcept
|
||||
{
|
||||
HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
||||
const auto getAwareness = user32 ? reinterpret_cast<GetAwarenessFromDpiAwarenessContextFn>(
|
||||
GetProcAddress(user32, "GetAwarenessFromDpiAwarenessContext")) : nullptr;
|
||||
if (!getAwareness || !context)
|
||||
return "not_available";
|
||||
return awarenessName(static_cast<int>(getAwareness(context)));
|
||||
}
|
||||
|
||||
DPI_AWARENESS_CONTEXT requestedContext(const std::string& requested) noexcept
|
||||
{
|
||||
if (requested == "unaware") return DPI_AWARENESS_CONTEXT_UNAWARE;
|
||||
if (requested == "system") return DPI_AWARENESS_CONTEXT_SYSTEM_AWARE;
|
||||
if (requested == "per-monitor") return DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE;
|
||||
if (requested == "per-monitor-v2") return DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::uint64_t fnv1a(const std::vector<std::uint32_t>& pixels) noexcept
|
||||
{
|
||||
std::uint64_t value = 1469598103934665603ull;
|
||||
for (const std::uint32_t pixel : pixels)
|
||||
{
|
||||
for (unsigned int shift = 0; shift < 32; shift += 8)
|
||||
{
|
||||
value ^= static_cast<unsigned char>((pixel >> shift) & 0xFFu);
|
||||
value *= 1099511628211ull;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> readPixels(
|
||||
HDC hdc,
|
||||
int width,
|
||||
int height,
|
||||
int sourceX = 0,
|
||||
int sourceY = 0) noexcept
|
||||
{
|
||||
std::vector<std::uint32_t> pixels;
|
||||
if (!hdc || width <= 0 || height <= 0)
|
||||
return pixels;
|
||||
BITMAPINFO info{};
|
||||
info.bmiHeader.biSize = sizeof(info.bmiHeader);
|
||||
info.bmiHeader.biWidth = width;
|
||||
info.bmiHeader.biHeight = -height;
|
||||
info.bmiHeader.biPlanes = 1;
|
||||
info.bmiHeader.biBitCount = 32;
|
||||
info.bmiHeader.biCompression = BI_RGB;
|
||||
void* bits = nullptr;
|
||||
HDC memoryDc = CreateCompatibleDC(hdc);
|
||||
if (!memoryDc)
|
||||
return pixels;
|
||||
HBITMAP bitmap = CreateDIBSection(hdc, &info, DIB_RGB_COLORS, &bits, nullptr, 0);
|
||||
if (!bitmap || !bits)
|
||||
{
|
||||
if (bitmap) DeleteObject(bitmap);
|
||||
DeleteDC(memoryDc);
|
||||
return pixels;
|
||||
}
|
||||
HGDIOBJ previous = SelectObject(memoryDc, bitmap);
|
||||
const bool copied = previous && BitBlt(memoryDc, 0, 0, width, height,
|
||||
hdc, sourceX, sourceY, SRCCOPY) != FALSE;
|
||||
if (copied)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto* first = static_cast<const std::uint32_t*>(bits);
|
||||
pixels.assign(first, first + static_cast<std::size_t>(width) * height);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pixels.clear();
|
||||
}
|
||||
}
|
||||
if (previous) SelectObject(memoryDc, previous);
|
||||
DeleteObject(bitmap);
|
||||
DeleteDC(memoryDc);
|
||||
return pixels;
|
||||
}
|
||||
|
||||
bool xformEqual(const XFORM& left, const XFORM& right) noexcept
|
||||
{
|
||||
constexpr float Epsilon = 0.0001f;
|
||||
return std::fabs(left.eM11 - right.eM11) < Epsilon &&
|
||||
std::fabs(left.eM12 - right.eM12) < Epsilon &&
|
||||
std::fabs(left.eM21 - right.eM21) < Epsilon &&
|
||||
std::fabs(left.eM22 - right.eM22) < Epsilon &&
|
||||
std::fabs(left.eDx - right.eDx) < Epsilon &&
|
||||
std::fabs(left.eDy - right.eDy) < Epsilon;
|
||||
}
|
||||
|
||||
std::string captureOsBuild() noexcept
|
||||
{
|
||||
using RtlGetVersionFn = LONG(WINAPI*)(OSVERSIONINFOW*);
|
||||
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
|
||||
const auto rtlGetVersion = ntdll ? reinterpret_cast<RtlGetVersionFn>(
|
||||
GetProcAddress(ntdll, "RtlGetVersion")) : nullptr;
|
||||
OSVERSIONINFOW version{};
|
||||
version.dwOSVersionInfoSize = sizeof(version);
|
||||
if (!rtlGetVersion || rtlGetVersion(&version) != 0)
|
||||
return "not_available";
|
||||
std::ostringstream output;
|
||||
output << version.dwMajorVersion << '.' << version.dwMinorVersion
|
||||
<< '.' << version.dwBuildNumber;
|
||||
return output.str();
|
||||
}
|
||||
}
|
||||
|
||||
bool parseOptions(int argc, char** argv, Options& options, std::string& error)
|
||||
{
|
||||
for (int index = 1; index < argc; ++index)
|
||||
{
|
||||
const std::string argument = argv[index];
|
||||
if ((argument == "--mode" || argument == "--output" || argument == "--dpi-awareness" ||
|
||||
argument == "--ime-name" || argument == "--auto-close-ms") &&
|
||||
index + 1 >= argc)
|
||||
{
|
||||
error = "Missing value for " + argument;
|
||||
return false;
|
||||
}
|
||||
if (argument == "--mode") options.mode = argv[++index];
|
||||
else if (argument == "--output") options.outputDirectory = argv[++index];
|
||||
else if (argument == "--dpi-awareness") options.requestedDpiAwareness = argv[++index];
|
||||
else if (argument == "--ime-name") options.expectedImeName = argv[++index];
|
||||
else if (argument == "--auto-close-ms")
|
||||
{
|
||||
try
|
||||
{
|
||||
options.autoCloseMilliseconds = static_cast<unsigned int>(std::stoul(argv[++index]));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
error = "Invalid auto-close duration";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
error = "Unknown argument: " + argument;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (options.mode != "wide-text" && options.mode != "dpi-coordinate" &&
|
||||
options.mode != "ime-position")
|
||||
{
|
||||
error = "Mode must be wide-text, dpi-coordinate, or ime-position";
|
||||
return false;
|
||||
}
|
||||
if (options.outputDirectory.empty())
|
||||
{
|
||||
error = "Output directory is required";
|
||||
return false;
|
||||
}
|
||||
const std::vector<std::string> awarenessValues{
|
||||
"inherit", "unaware", "system", "per-monitor", "per-monitor-v2"
|
||||
};
|
||||
if (std::find(awarenessValues.begin(), awarenessValues.end(), options.requestedDpiAwareness) ==
|
||||
awarenessValues.end())
|
||||
{
|
||||
error = "Unsupported DPI awareness: " + options.requestedDpiAwareness;
|
||||
return false;
|
||||
}
|
||||
const std::vector<std::string> imeNames{ "unspecified", "MicrosoftPinyin", "Sogou" };
|
||||
if (std::find(imeNames.begin(), imeNames.end(), options.expectedImeName) == imeNames.end())
|
||||
{
|
||||
error = "Unsupported IME name: " + options.expectedImeName;
|
||||
return false;
|
||||
}
|
||||
std::error_code fileError;
|
||||
std::filesystem::create_directories(options.outputDirectory / "evidence", fileError);
|
||||
if (fileError)
|
||||
{
|
||||
error = "Cannot create output directory: " + fileError.message();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
DpiConfiguration applyRequestedDpiAwareness(const std::string& requested) noexcept
|
||||
{
|
||||
DpiConfiguration result;
|
||||
result.requested = requested;
|
||||
if (requested == "inherit")
|
||||
{
|
||||
result.applied = true;
|
||||
return result;
|
||||
}
|
||||
result.attempted = true;
|
||||
HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
||||
const auto setContext = user32 ? reinterpret_cast<SetProcessDpiAwarenessContextFn>(
|
||||
GetProcAddress(user32, "SetProcessDpiAwarenessContext")) : nullptr;
|
||||
if (!setContext)
|
||||
{
|
||||
result.error = ERROR_PROC_NOT_FOUND;
|
||||
return result;
|
||||
}
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
result.applied = setContext(requestedContext(requested)) != FALSE;
|
||||
result.error = result.applied ? ERROR_SUCCESS : GetLastError();
|
||||
return result;
|
||||
}
|
||||
|
||||
EnvironmentSnapshot captureEnvironment(HWND hwnd) noexcept
|
||||
{
|
||||
EnvironmentSnapshot snapshot;
|
||||
snapshot.processId = GetCurrentProcessId();
|
||||
snapshot.threadId = GetCurrentThreadId();
|
||||
snapshot.windowUnicode = hwnd && IsWindowUnicode(hwnd) != FALSE;
|
||||
snapshot.osBuild = captureOsBuild();
|
||||
const TCHAR* easyxVersion = GetEasyXVer();
|
||||
snapshot.easyxVersion = easyxVersion ? easyxVersion : "not_available";
|
||||
if (hwnd)
|
||||
{
|
||||
GetClientRect(hwnd, &snapshot.clientRect);
|
||||
GetWindowRect(hwnd, &snapshot.windowRect);
|
||||
}
|
||||
|
||||
HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
||||
const auto getDpi = user32 ? reinterpret_cast<GetDpiForWindowFn>(
|
||||
GetProcAddress(user32, "GetDpiForWindow")) : nullptr;
|
||||
const auto getThreadContext = user32 ? reinterpret_cast<GetThreadDpiAwarenessContextFn>(
|
||||
GetProcAddress(user32, "GetThreadDpiAwarenessContext")) : nullptr;
|
||||
const auto getWindowContext = user32 ? reinterpret_cast<GetWindowDpiAwarenessContextFn>(
|
||||
GetProcAddress(user32, "GetWindowDpiAwarenessContext")) : nullptr;
|
||||
snapshot.dpiForWindow = getDpi && hwnd ? getDpi(hwnd) : 0;
|
||||
snapshot.threadAwareness = contextAwarenessName(getThreadContext ? getThreadContext() : nullptr);
|
||||
snapshot.windowAwareness = contextAwarenessName(
|
||||
getWindowContext && hwnd ? getWindowContext(hwnd) : nullptr);
|
||||
|
||||
HMODULE shcore = LoadLibraryW(L"shcore.dll");
|
||||
const auto getProcessAwareness = shcore ? reinterpret_cast<GetProcessDpiAwarenessFn>(
|
||||
GetProcAddress(shcore, "GetProcessDpiAwareness")) : nullptr;
|
||||
int processAwareness = -1;
|
||||
if (getProcessAwareness && SUCCEEDED(getProcessAwareness(nullptr, &processAwareness)))
|
||||
snapshot.processAwareness = awarenessName(processAwareness);
|
||||
else
|
||||
snapshot.processAwareness = "not_available";
|
||||
if (shcore) FreeLibrary(shcore);
|
||||
|
||||
HDC screen = GetDC(nullptr);
|
||||
if (screen)
|
||||
{
|
||||
snapshot.deviceDpiX = GetDeviceCaps(screen, LOGPIXELSX);
|
||||
snapshot.deviceDpiY = GetDeviceCaps(screen, LOGPIXELSY);
|
||||
ReleaseDC(nullptr, screen);
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
GdiState captureGdiState(HDC hdc) noexcept
|
||||
{
|
||||
GdiState state;
|
||||
if (!hdc) return state;
|
||||
state.mapMode = GetMapMode(hdc);
|
||||
GetViewportOrgEx(hdc, &state.viewportOrigin);
|
||||
GetWindowOrgEx(hdc, &state.windowOrigin);
|
||||
GetViewportExtEx(hdc, &state.viewportExtent);
|
||||
GetWindowExtEx(hdc, &state.windowExtent);
|
||||
state.graphicsMode = GetGraphicsMode(hdc);
|
||||
state.worldTransformAvailable = GetWorldTransform(hdc, &state.worldTransform) != FALSE;
|
||||
state.backgroundMode = GetBkMode(hdc);
|
||||
state.textColor = GetTextColor(hdc);
|
||||
state.backgroundColor = GetBkColor(hdc);
|
||||
state.textAlign = GetTextAlign(hdc);
|
||||
state.textCharacterExtra = GetTextCharacterExtra(hdc);
|
||||
state.clipType = GetClipBox(hdc, &state.clipBox);
|
||||
return state;
|
||||
}
|
||||
|
||||
bool sameGdiState(const GdiState& left, const GdiState& right) noexcept
|
||||
{
|
||||
return left.mapMode == right.mapMode &&
|
||||
left.viewportOrigin.x == right.viewportOrigin.x &&
|
||||
left.viewportOrigin.y == right.viewportOrigin.y &&
|
||||
left.windowOrigin.x == right.windowOrigin.x &&
|
||||
left.windowOrigin.y == right.windowOrigin.y &&
|
||||
left.viewportExtent.cx == right.viewportExtent.cx &&
|
||||
left.viewportExtent.cy == right.viewportExtent.cy &&
|
||||
left.windowExtent.cx == right.windowExtent.cx &&
|
||||
left.windowExtent.cy == right.windowExtent.cy &&
|
||||
left.graphicsMode == right.graphicsMode &&
|
||||
left.worldTransformAvailable == right.worldTransformAvailable &&
|
||||
(!left.worldTransformAvailable || xformEqual(left.worldTransform, right.worldTransform)) &&
|
||||
left.backgroundMode == right.backgroundMode &&
|
||||
left.textColor == right.textColor &&
|
||||
left.backgroundColor == right.backgroundColor &&
|
||||
left.textAlign == right.textAlign &&
|
||||
left.textCharacterExtra == right.textCharacterExtra &&
|
||||
left.clipType == right.clipType &&
|
||||
EqualRect(&left.clipBox, &right.clipBox) != FALSE;
|
||||
}
|
||||
|
||||
SurfaceFingerprint captureSurfaceFingerprint(HDC hdc, int width, int height) noexcept
|
||||
{
|
||||
SurfaceFingerprint fingerprint;
|
||||
fingerprint.hdc = reinterpret_cast<std::uintptr_t>(hdc);
|
||||
fingerprint.hbitmap = reinterpret_cast<std::uintptr_t>(
|
||||
hdc ? GetCurrentObject(hdc, OBJ_BITMAP) : nullptr);
|
||||
fingerprint.hfont = reinterpret_cast<std::uintptr_t>(
|
||||
hdc ? GetCurrentObject(hdc, OBJ_FONT) : nullptr);
|
||||
if (fingerprint.hfont)
|
||||
{
|
||||
fingerprint.logFontAvailable = GetObjectW(
|
||||
reinterpret_cast<HFONT>(fingerprint.hfont),
|
||||
sizeof(fingerprint.logFont), &fingerprint.logFont) == sizeof(fingerprint.logFont);
|
||||
}
|
||||
if (fingerprint.hbitmap)
|
||||
{
|
||||
BITMAP bitmap{};
|
||||
if (GetObject(reinterpret_cast<HBITMAP>(fingerprint.hbitmap), sizeof(bitmap), &bitmap) ==
|
||||
sizeof(bitmap))
|
||||
{
|
||||
fingerprint.bitmapInfoAvailable = true;
|
||||
fingerprint.bitmapWidth = bitmap.bmWidth;
|
||||
fingerprint.bitmapHeight = bitmap.bmHeight;
|
||||
fingerprint.planes = bitmap.bmPlanes;
|
||||
fingerprint.bitsPerPixel = bitmap.bmBitsPixel;
|
||||
fingerprint.stride = bitmap.bmWidthBytes;
|
||||
}
|
||||
DIBSECTION section{};
|
||||
fingerprint.dibSection = GetObject(reinterpret_cast<HBITMAP>(fingerprint.hbitmap),
|
||||
sizeof(section), §ion) == sizeof(section);
|
||||
}
|
||||
const auto pixels = readPixels(hdc, width, height);
|
||||
fingerprint.pixelDigest = fnv1a(pixels);
|
||||
fingerprint.gdiState = captureGdiState(hdc);
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
std::uint64_t capturePixelRegionDigest(HDC hdc, const RECT& region) noexcept
|
||||
{
|
||||
const int width = region.right - region.left;
|
||||
const int height = region.bottom - region.top;
|
||||
return fnv1a(readPixels(hdc, width, height, region.left, region.top));
|
||||
}
|
||||
|
||||
PixelBounds findColorBounds(HDC hdc, const RECT& region, COLORREF color) noexcept
|
||||
{
|
||||
PixelBounds bounds;
|
||||
for (int y = region.top; y < region.bottom; ++y)
|
||||
{
|
||||
for (int x = region.left; x < region.right; ++x)
|
||||
{
|
||||
if (GetPixel(hdc, x, y) != color) continue;
|
||||
if (!bounds.present)
|
||||
{
|
||||
bounds.present = true;
|
||||
bounds.left = bounds.right = x;
|
||||
bounds.top = bounds.bottom = y;
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds.left = (std::min)(bounds.left, x);
|
||||
bounds.top = (std::min)(bounds.top, y);
|
||||
bounds.right = (std::max)(bounds.right, x);
|
||||
bounds.bottom = (std::max)(bounds.bottom, y);
|
||||
}
|
||||
++bounds.pixelCount;
|
||||
}
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
PixelBounds findChangedBounds(HDC hdc, const RECT& region, COLORREF background) noexcept
|
||||
{
|
||||
PixelBounds bounds;
|
||||
for (int y = region.top; y < region.bottom; ++y)
|
||||
{
|
||||
for (int x = region.left; x < region.right; ++x)
|
||||
{
|
||||
const COLORREF pixel = GetPixel(hdc, x, y);
|
||||
if (pixel == CLR_INVALID || pixel == background) continue;
|
||||
if (!bounds.present)
|
||||
{
|
||||
bounds.present = true;
|
||||
bounds.left = bounds.right = x;
|
||||
bounds.top = bounds.bottom = y;
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds.left = (std::min)(bounds.left, x);
|
||||
bounds.top = (std::min)(bounds.top, y);
|
||||
bounds.right = (std::max)(bounds.right, x);
|
||||
bounds.bottom = (std::max)(bounds.bottom, y);
|
||||
}
|
||||
++bounds.pixelCount;
|
||||
}
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
bool saveHdcImage(HDC source, int width, int height, const std::filesystem::path& path) noexcept
|
||||
{
|
||||
if (!source || width <= 0 || height <= 0) return false;
|
||||
try
|
||||
{
|
||||
IMAGE image(width, height);
|
||||
HDC destination = GetImageHDC(&image);
|
||||
if (!destination || BitBlt(destination, 0, 0, width, height, source, 0, 0, SRCCOPY) == FALSE)
|
||||
return false;
|
||||
saveimage(path.string().c_str(), &image);
|
||||
return std::filesystem::exists(path) && std::filesystem::file_size(path) > 0;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool captureClientImage(
|
||||
HWND hwnd,
|
||||
const std::filesystem::path& path,
|
||||
std::string* captureBackend) noexcept
|
||||
{
|
||||
if (!hwnd) return false;
|
||||
RECT client{};
|
||||
if (!GetClientRect(hwnd, &client)) return false;
|
||||
const int width = client.right - client.left;
|
||||
const int height = client.bottom - client.top;
|
||||
try
|
||||
{
|
||||
IMAGE image(width, height);
|
||||
HDC destination = GetImageHDC(&image);
|
||||
if (!destination) return false;
|
||||
PatBlt(destination, 0, 0, width, height, BLACKNESS);
|
||||
bool captured = false;
|
||||
HDC clientDc = GetDC(hwnd);
|
||||
if (clientDc)
|
||||
{
|
||||
captured = BitBlt(destination, 0, 0, width, height,
|
||||
clientDc, 0, 0, SRCCOPY) != FALSE;
|
||||
ReleaseDC(hwnd, clientDc);
|
||||
}
|
||||
if (captured)
|
||||
{
|
||||
if (captureBackend) *captureBackend = "client_getdc_bitblt";
|
||||
}
|
||||
else
|
||||
{
|
||||
captured = PrintWindow(hwnd, destination, PW_CLIENTONLY) != FALSE;
|
||||
if (captured && captureBackend) *captureBackend = "printwindow_fallback";
|
||||
}
|
||||
if (!captured) return false;
|
||||
saveimage(path.string().c_str(), &image);
|
||||
return std::filesystem::exists(path) && std::filesystem::file_size(path) > 0;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring makeUtf16(const std::vector<std::uint32_t>& codePoints, bool& valid)
|
||||
{
|
||||
std::wstring text;
|
||||
valid = true;
|
||||
for (const std::uint32_t codePoint : codePoints)
|
||||
{
|
||||
if (codePoint > 0x10FFFFu || (codePoint >= 0xD800u && codePoint <= 0xDFFFu))
|
||||
{
|
||||
valid = false;
|
||||
continue;
|
||||
}
|
||||
if (codePoint <= 0xFFFFu)
|
||||
text.push_back(static_cast<wchar_t>(codePoint));
|
||||
else
|
||||
{
|
||||
const std::uint32_t adjusted = codePoint - 0x10000u;
|
||||
text.push_back(static_cast<wchar_t>(0xD800u + (adjusted >> 10)));
|
||||
text.push_back(static_cast<wchar_t>(0xDC00u + (adjusted & 0x3FFu)));
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string utf16UnitsJson(const std::wstring& text)
|
||||
{
|
||||
std::ostringstream output;
|
||||
output << '[';
|
||||
for (std::size_t index = 0; index < text.size(); ++index)
|
||||
{
|
||||
if (index) output << ',';
|
||||
output << static_cast<unsigned int>(static_cast<std::uint16_t>(text[index]));
|
||||
}
|
||||
output << ']';
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::string codePointsJson(const std::vector<std::uint32_t>& codePoints)
|
||||
{
|
||||
std::ostringstream output;
|
||||
output << '[';
|
||||
for (std::size_t index = 0; index < codePoints.size(); ++index)
|
||||
{
|
||||
if (index) output << ',';
|
||||
output << codePoints[index];
|
||||
}
|
||||
output << ']';
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::string jsonString(const std::string& value)
|
||||
{
|
||||
std::ostringstream output;
|
||||
output << '"';
|
||||
for (const unsigned char character : value)
|
||||
{
|
||||
switch (character)
|
||||
{
|
||||
case '"': output << "\\\""; break;
|
||||
case '\\': output << "\\\\"; break;
|
||||
case '\b': output << "\\b"; break;
|
||||
case '\f': output << "\\f"; break;
|
||||
case '\n': output << "\\n"; break;
|
||||
case '\r': output << "\\r"; break;
|
||||
case '\t': output << "\\t"; break;
|
||||
default:
|
||||
if (character < 0x20u)
|
||||
output << "\\u" << std::hex << std::setw(4) << std::setfill('0')
|
||||
<< static_cast<unsigned int>(character) << std::dec;
|
||||
else
|
||||
output << static_cast<char>(character);
|
||||
break;
|
||||
}
|
||||
}
|
||||
output << '"';
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::string hexValue(std::uint64_t value)
|
||||
{
|
||||
std::ostringstream output;
|
||||
output << "0x" << std::hex << std::uppercase << value;
|
||||
return output.str();
|
||||
}
|
||||
|
||||
void writeBoolean(std::ostream& output, bool value)
|
||||
{
|
||||
output << (value ? "true" : "false");
|
||||
}
|
||||
|
||||
void writeRectJson(std::ostream& output, const RECT& rect)
|
||||
{
|
||||
output << "{\"left\":" << rect.left << ",\"top\":" << rect.top
|
||||
<< ",\"right\":" << rect.right << ",\"bottom\":" << rect.bottom << '}';
|
||||
}
|
||||
|
||||
void writePointJson(std::ostream& output, const POINT& point)
|
||||
{
|
||||
output << "{\"x\":" << point.x << ",\"y\":" << point.y << '}';
|
||||
}
|
||||
|
||||
void writeSizeJson(std::ostream& output, const SIZE& size)
|
||||
{
|
||||
output << "{\"cx\":" << size.cx << ",\"cy\":" << size.cy << '}';
|
||||
}
|
||||
|
||||
void writeBoundsJson(std::ostream& output, const PixelBounds& bounds)
|
||||
{
|
||||
output << "{\"present\":";
|
||||
writeBoolean(output, bounds.present);
|
||||
output << ",\"left\":" << bounds.left << ",\"top\":" << bounds.top
|
||||
<< ",\"right\":" << bounds.right << ",\"bottom\":" << bounds.bottom
|
||||
<< ",\"pixelCount\":" << bounds.pixelCount << '}';
|
||||
}
|
||||
|
||||
void writeGdiStateJson(std::ostream& output, const GdiState& state)
|
||||
{
|
||||
output << "{\"mapMode\":" << state.mapMode << ",\"viewportOrigin\":";
|
||||
writePointJson(output, state.viewportOrigin);
|
||||
output << ",\"windowOrigin\":";
|
||||
writePointJson(output, state.windowOrigin);
|
||||
output << ",\"viewportExtent\":";
|
||||
writeSizeJson(output, state.viewportExtent);
|
||||
output << ",\"windowExtent\":";
|
||||
writeSizeJson(output, state.windowExtent);
|
||||
output << ",\"graphicsMode\":" << state.graphicsMode
|
||||
<< ",\"worldTransformAvailable\":";
|
||||
writeBoolean(output, state.worldTransformAvailable);
|
||||
output << ",\"worldTransform\":{\"m11\":" << state.worldTransform.eM11
|
||||
<< ",\"m12\":" << state.worldTransform.eM12
|
||||
<< ",\"m21\":" << state.worldTransform.eM21
|
||||
<< ",\"m22\":" << state.worldTransform.eM22
|
||||
<< ",\"dx\":" << state.worldTransform.eDx
|
||||
<< ",\"dy\":" << state.worldTransform.eDy << "}"
|
||||
<< ",\"backgroundMode\":" << state.backgroundMode
|
||||
<< ",\"textColor\":" << state.textColor
|
||||
<< ",\"backgroundColor\":" << state.backgroundColor
|
||||
<< ",\"textAlign\":" << state.textAlign
|
||||
<< ",\"textCharacterExtra\":" << state.textCharacterExtra
|
||||
<< ",\"clipType\":" << state.clipType << ",\"clipBox\":";
|
||||
writeRectJson(output, state.clipBox);
|
||||
output << '}';
|
||||
}
|
||||
|
||||
void writeSurfaceFingerprintJson(std::ostream& output, const SurfaceFingerprint& fingerprint)
|
||||
{
|
||||
output << "{\"hdc\":" << jsonString(hexValue(fingerprint.hdc))
|
||||
<< ",\"hbitmap\":" << jsonString(hexValue(fingerprint.hbitmap))
|
||||
<< ",\"hfont\":" << jsonString(hexValue(fingerprint.hfont))
|
||||
<< ",\"bitmapInfoAvailable\":";
|
||||
writeBoolean(output, fingerprint.bitmapInfoAvailable);
|
||||
output << ",\"bitmapWidth\":" << fingerprint.bitmapWidth
|
||||
<< ",\"bitmapHeight\":" << fingerprint.bitmapHeight
|
||||
<< ",\"planes\":" << fingerprint.planes
|
||||
<< ",\"bitsPerPixel\":" << fingerprint.bitsPerPixel
|
||||
<< ",\"stride\":" << fingerprint.stride
|
||||
<< ",\"dibSection\":";
|
||||
writeBoolean(output, fingerprint.dibSection);
|
||||
output << ",\"logFontAvailable\":";
|
||||
writeBoolean(output, fingerprint.logFontAvailable);
|
||||
output << ",\"logFont\":{"
|
||||
<< "\"height\":" << fingerprint.logFont.lfHeight
|
||||
<< ",\"width\":" << fingerprint.logFont.lfWidth
|
||||
<< ",\"weight\":" << fingerprint.logFont.lfWeight
|
||||
<< ",\"italic\":" << static_cast<unsigned int>(fingerprint.logFont.lfItalic)
|
||||
<< ",\"charset\":" << static_cast<unsigned int>(fingerprint.logFont.lfCharSet)
|
||||
<< ",\"faceNameUtf16\":[";
|
||||
for (std::size_t index = 0;
|
||||
index < LF_FACESIZE && fingerprint.logFont.lfFaceName[index]; ++index)
|
||||
{
|
||||
if (index) output << ',';
|
||||
output << static_cast<unsigned int>(fingerprint.logFont.lfFaceName[index]);
|
||||
}
|
||||
output << "]}";
|
||||
output << ",\"pixelDigest\":" << jsonString(hexValue(fingerprint.pixelDigest))
|
||||
<< ",\"gdiState\":";
|
||||
writeGdiStateJson(output, fingerprint.gdiState);
|
||||
output << '}';
|
||||
}
|
||||
|
||||
bool writeRawResult(
|
||||
const Options& options,
|
||||
const DpiConfiguration& dpiConfiguration,
|
||||
const EnvironmentSnapshot& environment,
|
||||
bool completed,
|
||||
const std::vector<EvidenceFile>& evidence,
|
||||
const std::string& resultJson,
|
||||
std::string& error)
|
||||
{
|
||||
const auto path = options.outputDirectory / "raw-result.json";
|
||||
std::ofstream output(path, std::ios::binary | std::ios::trunc);
|
||||
if (!output)
|
||||
{
|
||||
error = "Cannot open raw result: " + path.string();
|
||||
return false;
|
||||
}
|
||||
output << "{\n \"rawSchemaVersion\":1,\n \"mode\":" << jsonString(options.mode)
|
||||
<< ",\n \"completed\":";
|
||||
writeBoolean(output, completed);
|
||||
output << ",\n \"dpiConfiguration\":{\"requested\":"
|
||||
<< jsonString(dpiConfiguration.requested) << ",\"attempted\":";
|
||||
writeBoolean(output, dpiConfiguration.attempted);
|
||||
output << ",\"applied\":";
|
||||
writeBoolean(output, dpiConfiguration.applied);
|
||||
output << ",\"error\":" << dpiConfiguration.error << "},\n \"environment\":{"
|
||||
<< "\"processId\":" << environment.processId
|
||||
<< ",\"threadId\":" << environment.threadId
|
||||
<< ",\"windowUnicode\":";
|
||||
writeBoolean(output, environment.windowUnicode);
|
||||
output << ",\"dpiForWindow\":" << environment.dpiForWindow
|
||||
<< ",\"deviceDpiX\":" << environment.deviceDpiX
|
||||
<< ",\"deviceDpiY\":" << environment.deviceDpiY
|
||||
<< ",\"processAwareness\":" << jsonString(environment.processAwareness)
|
||||
<< ",\"threadAwareness\":" << jsonString(environment.threadAwareness)
|
||||
<< ",\"windowAwareness\":" << jsonString(environment.windowAwareness)
|
||||
<< ",\"osBuild\":" << jsonString(environment.osBuild)
|
||||
<< ",\"easyxVersion\":" << jsonString(environment.easyxVersion)
|
||||
<< ",\"clientRect\":";
|
||||
writeRectJson(output, environment.clientRect);
|
||||
output << ",\"windowRect\":";
|
||||
writeRectJson(output, environment.windowRect);
|
||||
output << "},\n \"evidenceFiles\":[";
|
||||
for (std::size_t index = 0; index < evidence.size(); ++index)
|
||||
{
|
||||
if (index) output << ',';
|
||||
output << "{\"path\":" << jsonString(evidence[index].relativePath)
|
||||
<< ",\"purpose\":" << jsonString(evidence[index].purpose)
|
||||
<< ",\"captureBackend\":" << jsonString(evidence[index].captureBackend) << '}';
|
||||
}
|
||||
output << "],\n \"result\":" << resultJson << "\n}\n";
|
||||
if (!output.good())
|
||||
{
|
||||
error = "Failed writing raw result: " + path.string();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user