#include "windows.h" #include "nonclient.h" #include #include namespace raven { namespace { constexpr uint32_t ws_visible = 0x10000000U; uint64_t extra_access(std::vector &bytes, int32_t index, size_t width, bool write, uint64_t value) { if (index < 0 || size_t(index) > bytes.size() || width > bytes.size() - size_t(index)) throw uint32_t(1413); uint64_t previous = 0; std::memcpy(&previous, bytes.data() + index, width); if (write) std::memcpy(bytes.data() + index, &value, width); return previous; } } // Window and class data access: every documented index, for both widths and both character sets. void NativeWindows::register_long_api(ApiRegistry ®istry) { const auto window_long = [this](ApiCall &call, bool write, bool wide) { auto *window = objects.window(call[0]); if (!window) { call.fail(1400); return; } const int32_t index = call.i32(1); const uint64_t value = write ? (wide ? call[2] : uint64_t(uint32_t(call[2]))) : 0; const auto narrow = [&](uint64_t result) { return wide ? result : uint64_t(uint32_t(result)); }; try { if (index >= 0) { call.ret(narrow(extra_access(window->extra, index, wide ? 8 : 4, write, value))); return; } // Pointer-sized values have no 32-bit accessor on a 64-bit system; on a 32-bit one they are 32 bits wide. if (!wide && process.mode() != NativeMode::x86 && (index == -4 || index == -6 || index == -8)) { call.fail(1413); return; } switch (index) { case -21: { const auto previous = window->userdata; if (write) window->userdata = wide ? value : uint64_t(int64_t(int32_t(uint32_t(value)))); call.ret(narrow(previous)); return; } case -4: { const auto previous = window->procedure; if (write) { if (!value) { call.fail(87); return; } window->procedure = value; } call.ret(narrow(previous)); return; } case -6: { const auto previous = window->instance; if (write) window->instance = value; call.ret(narrow(previous)); return; } case -12: { const auto previous = window->child_id; if (write) window->child_id = value; call.ret(narrow(previous)); return; } case -8: { const auto previous = window->parent ? window->parent : window->owner; if (write) { if (window->parent) { const uint64_t arguments[] = {call[0], value}; (void)arguments; window->parent = value; } else { window->owner = value; } } call.ret(narrow(previous)); return; } case -16: case -20: { const bool style = index == -16; // The index travels in the message as a signed word of the program's own width. const uint64_t index_word = process.mode() == NativeMode::x86 ? uint64_t(uint32_t(index)) : uint64_t(int64_t(index)); const uint32_t previous = style ? window->style : window->ex_style; if (write) { uint32_t next = uint32_t(value); // The window may adjust the change before it applies. const auto scratch = scratch_allocate(8); std::vector bytes(8); std::memcpy(bytes.data(), &previous, 4); std::memcpy(bytes.data() + 4, &next, 4); process.write_memory(scratch, bytes); send(call[0], 0x7c, index_word, scratch); if (!objects.window(call[0])) return; const auto changed = process.read_memory(scratch, 8); std::memcpy(&next, changed.data() + 4, 4); window = objects.window(call[0]); if (style) { window->style = next; const bool visible = (next & ws_visible) != 0; if (visible != window->visible) window->visible = visible; } else { window->ex_style = next; } std::memcpy(bytes.data() + 4, &next, 4); process.write_memory(scratch, bytes); send(call[0], 0x7d, index_word, scratch); if (objects.window(call[0]) && objects.window_visible(call[0])) { invalidate_tree(call[0], true); drawing(call[0]); } } call.ret(previous); return; } default: call.fail(1413); } } catch (const uint32_t &error) { call.fail(error); } }; for (const auto *name : {"GetWindowLongW", "GetWindowLongA"}) registry.add(name, 2, [=](ApiCall &call) { window_long(call, false, false); }); for (const auto *name : {"GetWindowLongPtrW", "GetWindowLongPtrA"}) registry.add(name, 2, [=](ApiCall &call) { window_long(call, false, true); }); for (const auto *name : {"SetWindowLongW", "SetWindowLongA"}) registry.add(name, 3, [=](ApiCall &call) { window_long(call, true, false); }); for (const auto *name : {"SetWindowLongPtrW", "SetWindowLongPtrA"}) registry.add(name, 3, [=](ApiCall &call) { window_long(call, true, true); }); const auto class_long = [this](ApiCall &call, bool write, bool wide) { const auto *window = objects.window(call[0]); if (!window) { call.fail(1400); return; } auto *definition = objects.window_class(window->class_id); const int32_t index = call.i32(1); const uint64_t value = write ? (wide ? call[2] : uint64_t(uint32_t(call[2]))) : 0; const auto narrow = [&](uint64_t result) { return wide ? result : uint64_t(uint32_t(result)); }; try { if (index >= 0) { call.ret(narrow(extra_access(definition->extra, index, wide ? 8 : 4, write, value))); return; } const auto exchange = [&](uint64_t &field) { const auto previous = field; if (write) field = value; call.ret(narrow(previous)); }; switch (index) { case -32: call.ret(definition->atom); return; case -26: { uint64_t style = definition->style; const auto previous = style; if (write) definition->style = uint32_t(value); call.ret(narrow(previous)); return; } case -20: call.ret(definition->extra.size()); return; case -18: { const auto previous = definition->window_extra; if (write) definition->window_extra = uint32_t(value); call.ret(previous); return; } case -16: exchange(definition->instance); return; case -24: exchange(definition->procedure); return; case -10: exchange(definition->background); return; case -12: exchange(definition->cursor); return; case -14: exchange(definition->icon); return; case -34: { uint64_t small = 0; exchange(small); return; } case -8: call.ret(0); return; default: call.fail(1413); } } catch (const uint32_t &error) { call.fail(error); } }; for (const auto *name : {"GetClassLongW", "GetClassLongA"}) registry.add(name, 2, [=](ApiCall &call) { class_long(call, false, false); }); for (const auto *name : {"GetClassLongPtrW", "GetClassLongPtrA"}) registry.add(name, 2, [=](ApiCall &call) { class_long(call, false, true); }); for (const auto *name : {"SetClassLongW", "SetClassLongA"}) registry.add(name, 3, [=](ApiCall &call) { class_long(call, true, false); }); for (const auto *name : {"SetClassLongPtrW", "SetClassLongPtrA"}) registry.add(name, 3, [=](ApiCall &call) { class_long(call, true, true); }); registry.add("CallWindowProcW", 5, [this](ApiCall &call) { if (!call[0]) { call.fail(87); return; } const uint64_t arguments[] = {call[1], call[2], call[3], call[4]}; const auto result = invoke(call[0], arguments); if (result.stop != NativeStop::returned) throw result; call.ret(result.value); }); // System metrics: the classic desktop values; the frame values come from the nonclient metrics in use. // The frame a style adds around a client rectangle, at a given DPI: the frame of 96 DPI scaled by the ratio. registry.add("AdjustWindowRectExForDpi", 5, [this](ApiCall &call) { const uint32_t dpi = call.u32(4); if (!call[0] || !dpi) { call.fail(87); return; } const auto bytes = call.read(call[0], 16); int32_t value[4]; std::memcpy(value, bytes.data(), 16); const auto frame = adjust_window_rect(objects.nonclient_metrics(), {0, 0, 0, 0}, call.u32(1), call.u32(2) != 0, call.u32(3)); const auto scale = [dpi](int32_t extent) { return int32_t((int64_t(extent) * dpi + (extent < 0 ? -48 : 48)) / 96); }; const int32_t adjusted[4] = {value[0] + scale(frame.left), value[1] + scale(frame.top), value[2] + scale(frame.right), value[3] + scale(frame.bottom)}; call.write(call[0], std::span(reinterpret_cast(adjusted), 16)); call.ret(1); }); // The clip rectangle: the whole screen unless the program confined the cursor. registry.add("GetClipCursor", 1, [this](ApiCall &call) { if (!call[0]) { call.fail(87); return; } const auto &screen = objects.nonclient_metrics(); const std::array whole{0, 0, int32_t(screen.screen_x), int32_t(screen.screen_y)}; const auto rect = clip_rect.value_or(whole); call.write(call[0], std::span(reinterpret_cast(rect.data()), 16)); call.ret(1); }); // Confining the cursor keeps the host pointer in the part of the program's window the rectangle covers (holding it still when the // rectangle is a point); the whole screen or no rectangle lets it go. registry.add("ClipCursor", 1, [this](ApiCall &call) { const auto &screen = objects.nonclient_metrics(); std::optional> rect; if (call[0]) { const auto bytes = call.read(call[0], 16); std::array value{}; std::memcpy(value.data(), bytes.data(), 16); value[0] = std::max(value[0], 0); value[1] = std::max(value[1], 0); value[2] = std::min(value[2], int32_t(screen.screen_x)); value[3] = std::min(value[3], int32_t(screen.screen_y)); if (value[2] < value[0] || value[3] < value[1]) { call.fail(87); return; } if (value[0] > 0 || value[1] > 0 || value[2] < int32_t(screen.screen_x) || value[3] < int32_t(screen.screen_y)) rect = value; } uint64_t window = 0; if (rect) { // The window that holds the rectangle: the active one when it does, otherwise the first visible top-level window that does. const int32_t cx = ((*rect)[0] + (*rect)[2]) / 2, cy = ((*rect)[1] + (*rect)[3]) / 2; const auto holds = [&](uint64_t handle) { const auto *candidate = objects.window(handle); return candidate && !candidate->parent && objects.window_visible(handle) && cx >= candidate->outer.left && cx < candidate->outer.right && cy >= candidate->outer.top && cy < candidate->outer.bottom; }; if (active && holds(active)) window = active; else for (const auto handle : objects.window_handles()) if (holds(handle)) { window = handle; break; } } clip_rect = rect; if (presentation.confine) { if (clip_window && clip_window != window) presentation.confine(clip_window, std::nullopt); clip_window = window; if (window) { const auto *target = objects.window(window); presentation.confine(window, std::array{(*rect)[0] - target->outer.left, (*rect)[1] - target->outer.top, (*rect)[2] - target->outer.left, (*rect)[3] - target->outer.top}); } } call.ret(1); }); // Touch input reaches windows as mouse input here; registering for it changes nothing and succeeds. registry.add("RegisterTouchWindow", 2, [this](ApiCall &call) { if (!objects.window(call[0])) { call.fail(1400); return; } call.ret(1); }); registry.add("UnregisterTouchWindow", 1, [this](ApiCall &call) { if (!objects.window(call[0])) { call.fail(1400); return; } call.ret(1); }); registry.add("GetSystemMetrics", 1, [this](ApiCall &call) { const auto &screen = objects.nonclient_metrics(); const int32_t index = call.i32(0); switch (index) { case 0: call.ret(uint32_t(screen.screen_x)); return; case 1: call.ret(uint32_t(screen.screen_y)); return; case 2: case 3: case 9: case 10: case 20: case 21: call.ret(17); return; case 4: call.ret(uint32_t(screen.caption)); return; case 5: case 6: call.ret(uint32_t(screen.border_x)); return; case 7: call.ret(uint32_t(screen.dialog_x)); return; case 8: call.ret(uint32_t(screen.dialog_y)); return; case 11: case 12: case 13: case 14: call.ret(32); return; case 15: call.ret(uint32_t(screen.menu)); return; case 16: call.ret(uint32_t(screen.screen_x)); return; case 17: call.ret(uint32_t(screen.screen_y - screen.caption)); return; case 19: call.ret(1); return; case 22: case 23: case 41: case 44: case 63: case 67: case 70: case 73: case 74: case 82: case 86: case 87: case 88: case 89: call.ret(0); return; case 28: call.ret(136); return; case 29: call.ret(39); return; case 30: call.ret(uint32_t(screen.button_x)); return; case 31: call.ret(uint32_t(screen.button_y)); return; case 32: call.ret(uint32_t(screen.frame_x)); return; case 33: call.ret(uint32_t(screen.frame_y)); return; case 34: call.ret(136); return; case 35: call.ret(39); return; case 36: case 37: case 68: case 69: call.ret(4); return; case 38: case 39: call.ret(75); return; case 40: call.ret(0); return; case 42: call.ret(0); return; case 43: call.ret(3); return; case 45: case 46: call.ret(2); return; case 47: case 48: call.ret(160); return; case 49: case 50: call.ret(16); return; case 51: call.ret(uint32_t(screen.small_caption)); return; case 52: call.ret(uint32_t(screen.small_button_x)); return; case 53: call.ret(uint32_t(screen.small_button_y)); return; case 54: call.ret(uint32_t(screen.small_button_x)); return; case 55: call.ret(uint32_t(screen.menu)); return; case 56: call.ret(8); return; case 57: call.ret(160); return; case 58: call.ret(24); return; case 59: case 61: call.ret(uint32_t(screen.screen_x + 8)); return; case 60: case 62: call.ret(uint32_t(screen.screen_y + 8)); return; case 71: case 72: call.ret(15); return; case 75: case 91: call.ret(1); return; case 76: case 77: call.ret(0); return; case 78: call.ret(uint32_t(screen.screen_x)); return; case 79: call.ret(uint32_t(screen.screen_y)); return; case 80: case 81: call.ret(1); return; case 83: case 84: call.ret(1); return; case 92: call.ret(0); return; case 97: call.ret(0); return; case 0x1000: case 0x2000: case 0x2001: call.ret(0); return; default: call.ret(0); return; } }); } }