#include "api.h" #include #include #include namespace raven { namespace { uint64_t le(std::span bytes, size_t at, size_t width) { uint64_t value = 0; for (size_t byte = 0; byte < width; ++byte) value |= uint64_t(bytes[at + byte]) << (byte * 8); return value; } } uint32_t device_caps(const DesktopObjects &objects, const DesktopDC &dc, int32_t index) { if (!objects.bitmap(dc.bitmap)) throw uint32_t(6); const auto &screen = objects.nonclient_metrics(); // Every context answers for the device it is compatible with, the display: a memory DC reports the screen, not the bitmap // selected into it, as Windows memory DCs do. switch (index) { case 2: return 1; // TECHNOLOGY: DT_RASDISPLAY case 4: return uint32_t(int64_t(screen.screen_x) * 254 / 960); // HORZSIZE in mm at 96 dpi case 6: return uint32_t(int64_t(screen.screen_y) * 254 / 960); // VERTSIZE case 8: return uint32_t(screen.screen_x); // HORZRES case 10: return uint32_t(screen.screen_y); // VERTRES case 12: return 32; // BITSPIXEL case 14: return 1; // PLANES case 24: return uint32_t(-1); // NUMCOLORS: more than 8 bits per pixel case 28: return 0xff; // CURVECAPS: every curve case 30: return 0xfe; // LINECAPS: every line case 32: return 0xff; // POLYGONALCAPS: every polygon case 34: return 0x7807; // TEXTCAPS: OP_CHARACTER | OP_STROKE | CP_STROKE | UA | SO | RA | VA case 36: return 1; // CLIPCAPS: CP_RECTANGLE // RC_BITBLT | RC_BITMAP64 | RC_GDI20_OUTPUT | RC_DI_BITMAP | RC_DIBTODEV | RC_BIGFONT | RC_STRETCHBLT | RC_FLOODFILL | RC_STRETCHDIB // | RC_DEVBITS, what a true-colour display reports. case 38: return 0x7e99; case 40: case 42: return 36; // ASPECTX / ASPECTY case 44: return 51; // ASPECTXY case 88: case 90: return 96; // LOGPIXELSX / LOGPIXELSY case 104: return 0; // SIZEPALETTE: no palette device case 106: return 0; // NUMRESERVED case 108: return 24; // COLORRES default: return 0; } } bool NativeDesktop::caps_dispatch(std::string_view name) { const bool caps = name == "GetDeviceCaps", rop = name == "SetROP2", layout = name == "SetLayout"; const bool pixel = name == "SetPixel", bits = name == "SetDIBitsToDevice"; if (!caps && !rop && !layout && !pixel && !bits) return false; const auto args = process.api_arguments(caps || rop || layout ? 2 : pixel ? 4 : 12); uint64_t result = 0; bool changed = false; uint64_t window = 0; bool leased = false; try { auto *dc = objects.dc(args[0]); if (!dc) throw uint32_t(6); window = dc->window; leased = dc->leased; if (caps) { result = device_caps(objects, *dc, int32_t(uint32_t(args[1]))); } else if (rop) { const auto mode = uint32_t(args[1]); if (mode < 1 || mode > 16) throw uint32_t(87); result = dc->rop2; dc->rop2 = mode; } else if (layout) { // Right-to-left mirroring is not implemented, so only the default layout can be selected. if (uint32_t(args[1]) & ~8U) throw uint32_t(120); result = 0; } else { const auto x = int64_t(int32_t(uint32_t(args[1]))) + dc->origin_x, y = int64_t(int32_t(uint32_t(args[2]))) + dc->origin_y; const auto color = uint32_t(args[3]); auto *bitmap = objects.bitmap(dc->bitmap); if (!bitmap || bitmap->depth != 32 || (color & 0xff000000U)) throw uint32_t(120); if (x < 0 || y < 0 || x >= bitmap->width || y >= bitmap->height || !dc->contains(x, y)) throw uint32_t(87); const auto pixels = bitmap->pixels(); const auto row = bitmap->top_down ? y : bitmap->height - 1 - y; const auto at = (size_t(row) * bitmap->width + size_t(x)) * 4; pixels[at] = uint8_t(color >> 16); pixels[at + 1] = uint8_t(color >> 8); pixels[at + 2] = uint8_t(color); pixels[at + 3] = 0; result = color; changed = true; } if (bits) { // SetDIBitsToDevice(hdc, xDest, yDest, w, h, xSrc, ySrc, StartScan, cLines, lpBits, lpbmi, ColorUse) const auto destination_x = int64_t(int32_t(uint32_t(args[1]))), destination_y = int64_t(int32_t(uint32_t(args[2]))); const auto width = uint32_t(args[3]), height = uint32_t(args[4]); const auto source_x = int64_t(int32_t(uint32_t(args[5]))), source_y = int64_t(int32_t(uint32_t(args[6]))); const auto start = uint32_t(args[7]), lines = uint32_t(args[8]); if (uint32_t(args[11]) != 0 || width > 16384 || height > 16384) throw uint32_t(120); const auto header = process.read_memory(args[10], 40); if (le(header, 0, 4) != 40) throw uint32_t(87); const auto image_width = int32_t(uint32_t(le(header, 4, 4))); const auto raw_height = int32_t(uint32_t(le(header, 8, 4))); const auto planes = uint32_t(le(header, 12, 2)), depth = uint32_t(le(header, 14, 2)); const auto compression = uint32_t(le(header, 16, 4)); uint32_t colors = uint32_t(le(header, 32, 4)); if (planes != 1 || compression != 0 || image_width <= 0 || raw_height == 0 || raw_height == INT32_MIN || image_width > 16384 || (depth != 1 && depth != 4 && depth != 8 && depth != 24 && depth != 32)) throw uint32_t(120); const bool top_down = raw_height < 0; const uint32_t image_height = uint32_t(top_down ? -raw_height : raw_height); if (image_height > 16384 || start > image_height || lines > image_height - start) throw uint32_t(87); if (depth <= 8 && (!colors || colors > (1U << depth))) colors = 1U << depth; std::vector table; if (depth <= 8) table = process.read_memory(args[10] + 40, size_t(colors) * 4); const size_t stride = ((size_t(image_width) * depth + 31) / 32) * 4; const auto payload = process.read_memory(args[9], stride * lines); auto *bitmap = objects.bitmap(dc->bitmap); if (!bitmap || bitmap->depth != 32) throw uint32_t(120); const auto pixels = bitmap->pixels(); for (uint32_t row = 0; row < height; ++row) { // Bottom-up DIBs count source rows from their last scan line. const int64_t dib_row = top_down ? source_y + row : source_y + (int64_t(height) - 1 - row); const int64_t scan = dib_row; if (scan < int64_t(start) || scan >= int64_t(start) + lines || scan >= image_height) continue; const auto stored = size_t(scan - start); const auto *line = payload.data() + stored * stride; for (uint32_t column = 0; column < width; ++column) { const int64_t sx = source_x + column; if (sx < 0 || sx >= image_width) continue; uint8_t blue = 0, green = 0, red = 0; if (depth == 24 || depth == 32) { const auto *p = line + size_t(sx) * (depth / 8); blue = p[0]; green = p[1]; red = p[2]; } else { const uint32_t per_byte = 8 / depth; const uint8_t byte = line[size_t(sx) / per_byte]; const uint32_t shift = (per_byte - 1 - uint32_t(sx) % per_byte) * depth; const uint32_t index = (byte >> shift) & ((1U << depth) - 1); if (index >= colors) continue; blue = table[index * 4]; green = table[index * 4 + 1]; red = table[index * 4 + 2]; } const auto px = destination_x + column + dc->origin_x, py = destination_y + row + dc->origin_y; if (px < 0 || py < 0 || px >= bitmap->width || py >= bitmap->height || !dc->contains(px, py)) continue; const auto target_row = bitmap->top_down ? py : bitmap->height - 1 - py; const auto at = (size_t(target_row) * bitmap->width + size_t(px)) * 4; pixels[at] = blue; pixels[at + 1] = green; pixels[at + 2] = red; pixels[at + 3] = 0; changed = true; } } result = lines; } } catch (uint32_t error) { environment.set_last_error(error); result = layout || pixel ? UINT32_MAX : 0; } catch (const std::bad_alloc &) { environment.set_last_error(8); } catch (const std::runtime_error &failure) { const std::string_view message = failure.what(); if (message.find("readable") == message.npos && message.find("writable") == message.npos) throw; environment.set_last_error(998); result = pixel ? UINT32_MAX : 0; } if (changed && window && leased && updated) updated(window); process.return_api(result); return true; } }