#include "api.h" #include "gdi.h" #include "nt/api.h" #include "nt/text.h" #include #include #include #include #include namespace raven { namespace { constexpr uint32_t clr_invalid = 0xffffffffU; struct Point { int32_t x, y; }; Point read_point(const ApiCall &call, uint64_t address) { return {call.load(address), call.load(address + 4)}; } DesktopRect read_rect(const ApiCall &call, uint64_t address) { return {call.load(address), call.load(address + 4), call.load(address + 8), call.load(address + 12)}; } void write_point(const ApiCall &call, uint64_t address, int32_t x, int32_t y) { if (!address) return; call.store(address, x); call.store(address + 4, y); } // One decoded device-independent bitmap: top-down BGRA rows with no alpha semantics unless the source carried it. struct DibImage { int32_t width = 0, height = 0; std::vector pixels; bool has_alpha = false; }; // Reads BITMAPINFO plus the pixel data and converts any supported depth to 32-bit pixels (0xAARRGGBB), rows top to bottom. // Fewer rows than the image height may be supplied; they are the first rows in storage order and the others stay zero. DibImage decode_dib(const ApiCall &call, uint64_t info, uint64_t bits, uint32_t usage, uint32_t rows_available = 0, int32_t *source_height_out = nullptr) { const auto header_size = call.load(info); if (header_size < 40) throw uint32_t(87); DibImage image; const auto width = call.load(info + 4); const auto height = call.load(info + 8); const auto bit_count = call.load(info + 14); const auto compression = call.load(info + 16); auto colors_used = call.load(info + 32); if (width <= 0 || !height || width > 16384 || std::abs(int64_t(height)) > 16384) throw uint32_t(87); if (compression != 0 && compression != 3) throw uint32_t(120); const bool top_down = height < 0; const uint32_t rows = uint32_t(std::abs(int64_t(height))); if (source_height_out) *source_height_out = int32_t(rows); if (!(bit_count == 1 || bit_count == 4 || bit_count == 8 || bit_count == 16 || bit_count == 24 || bit_count == 32)) throw uint32_t(87); uint64_t palette = info + header_size; uint32_t masks[3] = {0, 0, 0}; if (compression == 3) { for (unsigned i = 0; i < 3; ++i) masks[i] = call.load(palette + i * 4); palette += header_size == 40 ? 12 : 0; } if (bit_count <= 8 && (!colors_used || colors_used > (1U << bit_count))) colors_used = 1U << bit_count; std::vector table(bit_count <= 8 ? 256 : 0, 0); if (bit_count <= 8 && usage != 1) for (uint32_t i = 0; i < colors_used; ++i) table[i] = call.load(palette + i * 4) & 0xffffff; const size_t stride = ((size_t(width) * bit_count + 31) / 32) * 4; const uint32_t take = rows_available ? std::min(rows_available, rows) : rows; image.width = width; image.height = int32_t(rows); image.pixels.assign(size_t(width) * rows, 0); const auto payload = call.read(bits, stride * take); for (uint32_t stored = 0; stored < take; ++stored) { const uint32_t row = top_down ? stored : rows - 1 - stored; const uint8_t *line = payload.data() + size_t(stored) * stride; for (int32_t x = 0; x < width; ++x) { uint32_t value = 0; switch (bit_count) { case 1: value = table[(line[x / 8] >> (7 - x % 8)) & 1]; break; case 4: value = table[(line[x / 2] >> (x % 2 ? 0 : 4)) & 15]; break; case 8: value = table[line[x]]; break; case 16: { const uint32_t word = line[x * 2] | (uint32_t(line[x * 2 + 1]) << 8); if (compression == 3 && masks[1] == 0x7e0) value = (((word >> 11) & 31) * 255 / 31) << 16 | (((word >> 5) & 63) * 255 / 63) << 8 | ((word & 31) * 255 / 31); else value = (((word >> 10) & 31) * 255 / 31) << 16 | (((word >> 5) & 31) * 255 / 31) << 8 | ((word & 31) * 255 / 31); break; } case 24: value = line[x * 3 + 2] << 16 | line[x * 3 + 1] << 8 | line[x * 3]; break; default: value = uint32_t(line[x * 4 + 2]) << 16 | uint32_t(line[x * 4 + 1]) << 8 | line[x * 4] | uint32_t(line[x * 4 + 3]) << 24; if (line[x * 4 + 3]) image.has_alpha = true; } image.pixels[size_t(row) * width + x] = value; } } return image; } uint32_t colorref(uint32_t rgb) { return ((rgb & 0xff) << 16) | (rgb & 0xff00) | ((rgb >> 16) & 0xff); } // COLORREF to 0x00RRGGBB. uint32_t to_rgb(uint32_t color) { return colorref(color); } } void NativeDesktop::register_gdi_api(ApiRegistry ®istry) { // Marks the window behind a leased DC as changed so the next message retrieval presents it. const auto touched = [this](const DesktopDC &dc) { if (dc.window && dc.leased && updated) updated(dc.window); }; const auto require_dc = [this](const ApiCall &call, size_t index) -> DesktopDC & { auto *dc = objects.dc(call[index]); if (!dc) throw uint32_t(6); return *dc; }; // Runs a drawing handler, translating thrown Windows errors into last-error and a zero return. const auto guarded = [](ApiCall &call, uint64_t failure, const std::function &body) { try { body(); } catch (const uint32_t &error) { call.fail(error, failure); } catch (const std::bad_alloc &) { call.fail(8, failure); } }; // ---- device context state ---- registry.add("SaveDC", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); DesktopDCState state; state.bitmap = dc.bitmap; state.font = dc.font; state.brush = dc.brush; state.pen = dc.pen; state.clip = dc.clip; state.application_clip = dc.application_clip; state.bounds = dc.bounds; state.text_color = dc.text_color; state.background_color = dc.background_color; state.background_mode = dc.background_mode; state.rop2 = dc.rop2; state.text_align = dc.text_align; state.poly_fill = dc.poly_fill; state.origin_x = dc.origin_x; state.origin_y = dc.origin_y; state.position_x = dc.position_x; state.position_y = dc.position_y; state.brush_org_x = dc.brush_org_x; state.brush_org_y = dc.brush_org_y; state.window_org_x = dc.window_org_x; state.window_org_y = dc.window_org_y; state.viewport_org_x = dc.viewport_org_x; state.viewport_org_y = dc.viewport_org_y; state.graphics_mode = dc.graphics_mode; state.world = dc.world; dc.saved.push_back(std::move(state)); call.ret(dc.saved.size()); }); }); registry.add("RestoreDC", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const int32_t requested = call.i32(1); const int64_t index = requested < 0 ? int64_t(dc.saved.size()) + requested : int64_t(requested) - 1; if (index < 0 || index >= int64_t(dc.saved.size())) throw uint32_t(87); const auto state = dc.saved[size_t(index)]; dc.saved.resize(size_t(index)); // Objects deleted while saved cannot be reselected; the DC keeps what it has. if (objects.font(state.font)) objects.select_font(call[0], state.font); if (objects.brush(state.brush)) objects.select_brush(call[0], state.brush); if (objects.pen(state.pen)) objects.select_pen(call[0], state.pen); dc.clip = state.clip; dc.application_clip = state.application_clip; dc.bounds = state.bounds; dc.text_color = state.text_color; dc.background_color = state.background_color; dc.background_mode = state.background_mode; dc.rop2 = state.rop2; dc.text_align = state.text_align; dc.poly_fill = state.poly_fill; dc.origin_x = state.origin_x; dc.origin_y = state.origin_y; dc.position_x = state.position_x; dc.position_y = state.position_y; dc.brush_org_x = state.brush_org_x; dc.brush_org_y = state.brush_org_y; dc.window_org_x = state.window_org_x; dc.window_org_y = state.window_org_y; dc.viewport_org_x = state.viewport_org_x; dc.viewport_org_y = state.viewport_org_y; dc.user_dx = state.viewport_org_x - state.window_org_x; dc.user_dy = state.viewport_org_y - state.window_org_y; dc.graphics_mode = state.graphics_mode; dc.world = state.world; call.ret(1); }); }); registry.add("SetGraphicsMode", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const uint32_t mode = call.u32(1); if (mode != 1 && mode != 2) throw uint32_t(87); // Leaving the advanced mode drops a world transform that is not the identity, as Windows refuses to keep one. if (mode == 1 && dc.world != std::array{1, 0, 0, 1, 0, 0}) throw uint32_t(87); const auto previous = dc.graphics_mode; dc.graphics_mode = mode; call.ret(previous); }); }); registry.add("GetGraphicsMode", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { call.ret(require_dc(call, 0).graphics_mode); }); }); // XFORM: eM11, eM12, eM21, eM22, eDx, eDy. const auto read_xform = [](ApiCall &call, uint64_t address) { std::array value{}; const auto bytes = call.read(address, 24); std::memcpy(value.data(), bytes.data(), 24); return value; }; const auto multiply = [](const std::array &a, const std::array &b) { return std::array{a[0] * b[0] + a[1] * b[2], a[0] * b[1] + a[1] * b[3], a[2] * b[0] + a[3] * b[2], a[2] * b[1] + a[3] * b[3], a[4] * b[0] + a[5] * b[2] + b[4], a[4] * b[1] + a[5] * b[3] + b[5]}; }; registry.add("SetWorldTransform", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); if (dc.graphics_mode != 2 || !call[1]) throw uint32_t(87); dc.world = read_xform(call, call[1]); call.ret(1); }); }); registry.add("GetWorldTransform", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); if (!call[1]) throw uint32_t(87); call.write(call[1], std::span(reinterpret_cast(dc.world.data()), 24)); call.ret(1); }); }); // GetTransform(dc, which, XFORM): world to page (0x203) is the world transform; page to device (0x304) moves the window origin onto the // viewport origin, the whole MM_TEXT mapping these device contexts keep; world to device (0x204) is their product and device to world // (0x402) its inverse. GDI+ reads world to device to place what it draws on a context whose origin a program moved. registry.add("GetTransform", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const uint32_t which = call.u32(1); if (!call[2] || (which != 0x203 && which != 0x304 && which != 0x204 && which != 0x402)) throw uint32_t(87); const float dx = float(dc.viewport_org_x - dc.window_org_x), dy = float(dc.viewport_org_y - dc.window_org_y); const auto &w = dc.world; std::array result = which == 0x203 ? w : which == 0x304 ? std::array{1, 0, 0, 1, dx, dy} : std::array{w[0], w[1], w[2], w[3], w[4] + dx, w[5] + dy}; if (which == 0x402) { const auto t = result; const float determinant = t[0] * t[3] - t[1] * t[2]; if (determinant == 0) throw uint32_t(87); result = {t[3] / determinant, -t[1] / determinant, -t[2] / determinant, t[0] / determinant, (t[2] * t[5] - t[3] * t[4]) / determinant, (t[1] * t[4] - t[0] * t[5]) / determinant}; } call.write(call[2], std::span(reinterpret_cast(result.data()), 24)); call.ret(1); }); }); registry.add("ModifyWorldTransform", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const uint32_t mode = call.u32(2); // Resetting to the identity takes no matrix and changes nothing a compatible-mode context could use; the other modes need // GM_ADVANCED. if (mode < 1 || mode > 3 || (mode != 1 && (dc.graphics_mode != 2 || !call[1]))) throw uint32_t(87); if (mode == 1) dc.world = {1, 0, 0, 1, 0, 0}; else if (mode == 2) dc.world = multiply(read_xform(call, call[1]), dc.world); else dc.world = multiply(dc.world, read_xform(call, call[1])); call.ret(1); }); }); registry.add("SetTextAlign", 2, [=, this](ApiCall &call) { guarded(call, clr_invalid, [&] { auto &dc = require_dc(call, 0); const auto previous = dc.text_align; dc.text_align = call.u32(1); call.ret(previous); }); }); registry.add("GetTextAlign", 1, [=, this](ApiCall &call) { guarded(call, clr_invalid, [&] { call.ret(require_dc(call, 0).text_align); }); }); registry.add("SetPolyFillMode", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); if (call.u32(1) != 1 && call.u32(1) != 2) throw uint32_t(87); const auto previous = dc.poly_fill; dc.poly_fill = call.u32(1); call.ret(previous); }); }); registry.add("GetPolyFillMode", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { call.ret(require_dc(call, 0).poly_fill); }); }); registry.add("GetROP2", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { call.ret(require_dc(call, 0).rop2); }); }); registry.add("GetMapMode", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { require_dc(call, 0); call.ret(1); }); }); registry.add("SetMapMode", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { require_dc(call, 0); // Only the default mapping exists; requesting another one fails visibly. if (call.u32(1) != 1) throw uint32_t(120); call.ret(1); }); }); registry.add("SetStretchBltMode", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { require_dc(call, 0); call.ret(1); }); }); registry.add("SetBrushOrgEx", 4, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); write_point(call, call[3], dc.brush_org_x, dc.brush_org_y); dc.brush_org_x = call.i32(1); dc.brush_org_y = call.i32(2); call.ret(1); }); }); registry.add("GetBrushOrgEx", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); write_point(call, call[1], dc.brush_org_x, dc.brush_org_y); call.ret(1); }); }); // Window and viewport origins shift logical coordinates; the DC origin carries the sum and clip regions stay in device units. const auto shift_origin = [this](DesktopDC &dc, int32_t window_x, int32_t window_y, int32_t viewport_x, int32_t viewport_y) { const int32_t dx = (viewport_x - dc.viewport_org_x) - (window_x - dc.window_org_x); const int32_t dy = (viewport_y - dc.viewport_org_y) - (window_y - dc.window_org_y); dc.window_org_x = window_x; dc.window_org_y = window_y; dc.viewport_org_x = viewport_x; dc.viewport_org_y = viewport_y; dc.origin_x += dx; dc.origin_y += dy; dc.user_dx += dx; dc.user_dy += dy; }; registry.add("SetViewportOrgEx", 4, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); write_point(call, call[3], dc.viewport_org_x, dc.viewport_org_y); shift_origin(dc, dc.window_org_x, dc.window_org_y, call.i32(1), call.i32(2)); call.ret(1); }); }); registry.add("OffsetViewportOrgEx", 4, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); write_point(call, call[3], dc.viewport_org_x, dc.viewport_org_y); shift_origin(dc, dc.window_org_x, dc.window_org_y, dc.viewport_org_x + call.i32(1), dc.viewport_org_y + call.i32(2)); call.ret(1); }); }); registry.add("SetWindowOrgEx", 4, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); write_point(call, call[3], dc.window_org_x, dc.window_org_y); shift_origin(dc, call.i32(1), call.i32(2), dc.viewport_org_x, dc.viewport_org_y); call.ret(1); }); }); registry.add("OffsetWindowOrgEx", 4, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); write_point(call, call[3], dc.window_org_x, dc.window_org_y); shift_origin(dc, dc.window_org_x + call.i32(1), dc.window_org_y + call.i32(2), dc.viewport_org_x, dc.viewport_org_y); call.ret(1); }); }); registry.add("GetViewportOrgEx", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); write_point(call, call[1], dc.viewport_org_x, dc.viewport_org_y); call.ret(1); }); }); registry.add("GetWindowOrgEx", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); write_point(call, call[1], dc.window_org_x, dc.window_org_y); call.ret(1); }); }); // Logical and device coordinates differ only by the window and viewport origins. registry.add("DPtoLP", 3, [=, this](ApiCall &call) { // An unknown device context fails silently, and a non-positive count has nothing to convert. auto *dc = objects.dc(call[0]); if (!dc) { call.ret(0); return; } const int32_t count = call.i32(2); if (count > 65536) { call.fail(120, 0); return; } for (int32_t i = 0; i < count; ++i) { const auto at = call[1] + uint64_t(i) * 8; call.store(at, call.load(at) - dc->user_dx); call.store(at + 4, call.load(at + 4) - dc->user_dy); } call.ret(1); }); registry.add("LPtoDP", 3, [=, this](ApiCall &call) { // An unknown device context fails silently, and a non-positive count has nothing to convert. auto *dc = objects.dc(call[0]); if (!dc) { call.ret(0); return; } const int32_t count = call.i32(2); if (count > 65536) { call.fail(120, 0); return; } for (int32_t i = 0; i < count; ++i) { const auto at = call[1] + uint64_t(i) * 8; call.store(at, call.load(at) + dc->user_dx); call.store(at + 4, call.load(at + 4) + dc->user_dy); } call.ret(1); }); registry.add("GetNearestColor", 2, [=, this](ApiCall &call) { guarded(call, clr_invalid, [&] { require_dc(call, 0); call.ret(call.u32(1) & 0xffffff); }); }); registry.add("SelectPalette", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { require_dc(call, 0); call.ret(0); }); }); registry.add("GetPixel", 3, [=, this](ApiCall &call) { guarded(call, clr_invalid, [&] { auto &dc = require_dc(call, 0); Painter painter(objects, dc); const int64_t x = int64_t(call.i32(1)) + dc.origin_x, y = int64_t(call.i32(2)) + dc.origin_y; call.ret(painter.visible(x, y) ? painter.read(x, y) : clr_invalid); }); }); // ---- stock objects ---- registry.add("GetStockObject", 1, [=, this](ApiCall &call) { const uint32_t index = call.u32(0); if (index < 6) call.ret(desktop_stock_brush + index); else if (index <= 8) call.ret(desktop_stock_pen + index - 6); else if (index == 18) call.ret(desktop_stock_brush + 6); else if (index == 19) call.ret(desktop_stock_pen + 3); else if (index == 10 || index == 11 || index == 16) call.ret(desktop_fixed_font); else if (index == 12 || index == 17 || index == 14) call.ret(desktop_gui_font); else if (index == 13) call.ret(desktop_system_font); else if (index == 15) call.ret(0x7fff0001); else call.fail(87); }); registry.add("SetDCBrushColor", 2, [=, this](ApiCall &call) { guarded(call, clr_invalid, [&] { require_dc(call, 0); call.ret(objects.set_dc_brush_color(call.u32(1) & 0xffffff)); }); }); registry.add("SetDCPenColor", 2, [=, this](ApiCall &call) { guarded(call, clr_invalid, [&] { require_dc(call, 0); call.ret(objects.set_dc_pen_color(call.u32(1) & 0xffffff)); }); }); // ---- brushes and pens ---- // LOGBRUSH: style, color, then a hatch index or bitmap handle that is as wide as the program's pointers. registry.add("CreateBrushIndirect", 1, [=, this, ®istry](ApiCall &call) { guarded(call, 0, [&] { if (!call[0]) throw uint32_t(87); const auto style = call.load(call[0]); const auto color = call.load(call[0] + 4) & 0xffffff; const uint64_t detail = call.process.mode() == NativeMode::x86 ? call.load(call[0] + 8) : call.load(call[0] + 8); DesktopBrush brush; switch (style) { case 0: brush.color = color; break; case 1: brush.hollow = true; break; case 2: if (detail > 5) throw uint32_t(87); brush.color = color; brush.style = 2; brush.hatch = uint32_t(detail); break; case 3: { // A pattern brush takes the bitmap named by the third member. const auto entry = registry.find("CreatePatternBrush"); if (!entry) throw uint32_t(120); ApiCall inner(call.process, call.environment, {detail}); entry->second(inner); call.ret(inner.result); return; } default: throw uint32_t(120); } const auto handle = objects.add(brush); if (!handle) throw uint32_t(8); call.ret(handle); }); }); registry.add("CreateHatchBrush", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { if (call.u32(0) > 5) throw uint32_t(87); DesktopBrush brush; brush.color = call.u32(1) & 0xffffff; brush.style = 2; brush.hatch = call.u32(0); const auto handle = objects.add(brush); if (!handle) throw uint32_t(8); call.ret(handle); }); }); registry.add("CreatePatternBrush", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { const auto *bitmap = objects.bitmap(call[0]); if (!bitmap) throw uint32_t(6); DesktopBrush brush; brush.style = 3; brush.pattern_width = bitmap->width; brush.pattern_height = bitmap->height; brush.pattern_mono = bitmap->depth == 1; brush.pattern.assign(size_t(bitmap->width) * bitmap->height, 0); const auto pixels = bitmap->pixels(); for (uint32_t y = 0; y < bitmap->height; ++y) for (uint32_t x = 0; x < bitmap->width; ++x) { const size_t row = bitmap->top_down ? y : bitmap->height - 1 - y; uint32_t value; if (bitmap->depth == 1) { const size_t stride = ((size_t(bitmap->width) + 15) / 16) * 2; value = (pixels[row * stride + x / 8] >> (7 - x % 8)) & 1; } else { const auto *p = &pixels[(row * bitmap->width + x) * 4]; value = uint32_t(p[2]) | uint32_t(p[1]) << 8 | uint32_t(p[0]) << 16; } brush.pattern[size_t(y) * bitmap->width + x] = value; } const auto handle = objects.add(std::move(brush)); if (!handle) throw uint32_t(8); call.ret(handle); }); }); const auto make_pen = [this](uint32_t style, int32_t width, uint32_t color, bool geometric) { DesktopPen pen; pen.color = color & 0xffffff; pen.style = style; pen.hollow = style == 5; pen.width = width; pen.geometric = geometric; const auto handle = objects.add(pen); if (!handle) throw uint32_t(8); return handle; }; registry.add("CreatePen", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { if ((call.u32(0) & 0xff) > 6 || call.i32(1) < 0) throw uint32_t(87); call.ret(make_pen(call.u32(0) & 0xff, call.i32(1), call.u32(2), false)); }); }); registry.add("CreatePenIndirect", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { const auto style = call.load(call[0]); if ((style & 0xff) > 6) throw uint32_t(87); call.ret(make_pen(style & 0xff, call.load(call[0] + 4), call.load(call[0] + 12), false)); }); }); registry.add("ExtCreatePen", 5, [=, this](ApiCall &call) { guarded(call, 0, [&] { const auto style = call.u32(0); const bool geometric = (style & 0xf0000) == 0x10000; if ((style & 0xff) > 7 || !call[2]) throw uint32_t(87); call.ret(make_pen(std::min(style & 0xff, 6), int32_t(call.u32(1)), call.load(call[2] + 4), geometric)); }); }); // ---- bitmaps ---- registry.add("CreateBitmap", 5, [=, this](ApiCall &call) { guarded(call, 0, [&] { const int32_t width = call.i32(0), height = call.i32(1); const uint32_t planes = call.u32(2), bits_per_pixel = call.u32(3); if (width <= 0 || height <= 0 || width > 16384 || height > 16384 || planes != 1 || !(bits_per_pixel == 1 || bits_per_pixel == 4 || bits_per_pixel == 8 || bits_per_pixel == 16 || bits_per_pixel == 24 || bits_per_pixel == 32)) throw uint32_t(87); const auto handle = allocate_bitmap(uint32_t(width), uint32_t(height), bits_per_pixel == 1 ? 1 : 32, false, true); if (!handle) return; auto *bitmap = objects.bitmap(handle); if (call[4]) { // DDB bits are word-aligned rows, top row first. const size_t stride = ((size_t(width) * bits_per_pixel + 15) / 16) * 2; const auto source = call.read(call[4], stride * size_t(height)); auto destination = bitmap->pixels(); for (int32_t y = 0; y < height; ++y) for (int32_t x = 0; x < width; ++x) { const uint8_t *line = source.data() + size_t(y) * stride; if (bits_per_pixel == 1) { const size_t target_stride = ((size_t(width) + 15) / 16) * 2; const bool set = (line[x / 8] >> (7 - x % 8)) & 1; auto &byte = destination[size_t(y) * target_stride + x / 8]; byte = uint8_t(set ? (byte | (0x80 >> (x % 8))) : (byte & ~(0x80 >> (x % 8)))); continue; } uint8_t blue = 0, green = 0, red = 0; switch (bits_per_pixel) { case 32: blue = line[x * 4]; green = line[x * 4 + 1]; red = line[x * 4 + 2]; break; case 24: blue = line[x * 3]; green = line[x * 3 + 1]; red = line[x * 3 + 2]; break; case 16: { const uint32_t word = line[x * 2] | (uint32_t(line[x * 2 + 1]) << 8); red = uint8_t(((word >> 10) & 31) * 255 / 31); green = uint8_t(((word >> 5) & 31) * 255 / 31); blue = uint8_t((word & 31) * 255 / 31); break; } default: { const uint32_t shift = bits_per_pixel == 8 ? 0 : (x % 2 ? 0 : 4); const uint32_t index = bits_per_pixel == 8 ? line[x] : (line[x / 2] >> shift) & 15; blue = green = red = uint8_t(index * 255 / (bits_per_pixel == 8 ? 255 : 15)); } } auto *p = &destination[(size_t(y) * width + x) * 4]; p[0] = blue; p[1] = green; p[2] = red; p[3] = 0; } } else if (bitmap->depth == 1) { // Uninitialized monochrome bitmaps start black. } call.ret(handle); }); }); registry.add("SetBitmapDimensionEx", 4, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto *bitmap = objects.bitmap(call[0]); if (!bitmap) throw uint32_t(6); if (call[3]) { call.store(call[3], bitmap->dimension_x); call.store(call[3] + 4, bitmap->dimension_y); } bitmap->dimension_x = call.i32(1); bitmap->dimension_y = call.i32(2); call.ret(1); }); }); registry.add("GetBitmapDimensionEx", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { const auto *bitmap = objects.bitmap(call[0]); if (!bitmap || !call[1]) throw uint32_t(bitmap ? 87 : 6); call.store(call[1], bitmap->dimension_x); call.store(call[1] + 4, bitmap->dimension_y); call.ret(1); }); }); registry.add("SetBitmapBits", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto *bitmap = objects.bitmap(call[0]); if (!bitmap) throw uint32_t(6); auto pixels = bitmap->pixels(); const size_t size = std::min(call.u32(1), pixels.size()); const auto data = call.read(call[2], size); if (bitmap->top_down || bitmap->depth == 1) { std::copy(data.begin(), data.end(), pixels.begin()); } else { const size_t stride = size_t(bitmap->width) * 4; for (size_t offset = 0; offset < size; ++offset) { const size_t row = offset / stride; pixels[(bitmap->height - 1 - row) * stride + offset % stride] = data[offset]; } } call.ret(size); }); }); // ---- regions ---- const auto box_region = [](int32_t left, int32_t top, int32_t right, int32_t bottom) { DesktopRegion region; if (right > left && bottom > top) region.add({left, top, right, bottom}); return region; }; const auto adopt = [this](DesktopRegion region, ApiCall &call) { const auto handle = objects.add(std::move(region)); if (!handle) throw uint32_t(8); call.ret(handle); }; registry.add("CreateRectRgnIndirect", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { const auto rect = read_rect(call, call[0]); adopt(box_region(std::min(rect.left, rect.right), std::min(rect.top, rect.bottom), std::max(rect.left, rect.right), std::max(rect.top, rect.bottom)), call); }); }); registry.add("CreateRoundRectRgn", 6, [=, this](ApiCall &call) { guarded(call, 0, [&] { const int64_t left = call.i32(0), top = call.i32(1), right = call.i32(2), bottom = call.i32(3); if (right <= left || bottom <= top || right - left > 16384 || bottom - top > 16384) throw uint32_t(87); DesktopRegion region; const double rx = std::min(std::abs(call.i32(4)), double(right - left)) / 2.0, ry = std::min(std::abs(call.i32(5)), double(bottom - top)) / 2.0; for (int64_t y = top; y < bottom; ++y) { const double row = double(y - top) + 0.5; double inset = 0; const double height = double(bottom - top); double dy = row < ry ? ry - row : row > height - ry ? row - (height - ry) : 0; if (dy > 0 && ry > 0) inset = rx - rx * std::sqrt(std::max(0.0, 1.0 - (dy / ry) * (dy / ry))); region.add({int32_t(left + std::floor(inset + 0.5)), int32_t(y), int32_t(right - std::floor(inset + 0.5)), int32_t(y + 1)}); } adopt(std::move(region), call); }); }); // The pixels closed polygons cover, row by row at pixel centres: ALTERNATE (even_odd) or WINDING filling over the edges of all of // them together, each polygon closed back to its first point. const auto fill_polygons = [](const std::vector>> &polygons, bool even_odd) { double min_y = INFINITY, max_y = -INFINITY; for (const auto &polygon : polygons) for (const auto &point : polygon) { min_y = std::min(min_y, point[1]); max_y = std::max(max_y, point[1]); } DesktopRegion region; if (!(min_y < max_y)) return region; if (max_y - min_y > 16384) throw uint32_t(120); for (int32_t y = int32_t(std::floor(min_y)); y < int32_t(std::ceil(max_y)); ++y) { std::vector> crossings; const double center = y + 0.5; for (const auto &points : polygons) for (size_t i = 0; i < points.size(); ++i) { const auto &a = points[i], &b = points[(i + 1) % points.size()]; if (a[1] == b[1] || center < std::min(a[1], b[1]) || center >= std::max(a[1], b[1])) continue; const double t = (center - a[1]) / (b[1] - a[1]); crossings.push_back({a[0] + t * (b[0] - a[0]), b[1] > a[1] ? 1 : -1}); } std::sort(crossings.begin(), crossings.end()); int winding = 0; for (size_t i = 0; i + 1 < crossings.size(); ++i) { winding += crossings[i].second; if (!(even_odd ? i % 2 == 0 : winding != 0)) continue; const int32_t from = int32_t(std::ceil(crossings[i].first - 0.5)), to = int32_t(std::ceil(crossings[i + 1].first - 0.5)); if (to > from) region.add({from, y, to, y + 1}); } } return region; }; registry.add("CreatePolygonRgn", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { const int32_t count = call.i32(1); if (count < 3 || count > 65536) throw uint32_t(87); std::vector> points; for (int32_t i = 0; i < count; ++i) { const auto point = read_point(call, call[0] + uint64_t(i) * 8); points.push_back({double(point.x), double(point.y)}); } adopt(fill_polygons({points}, call.u32(2) != 2), call); }); }); // ---- paths ---- // BeginPath opens a bracket in which PolyDraw records figures instead of drawing; EndPath closes it, AbortPath drops it. registry.add("BeginPath", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { require_dc(call, 0).path = DesktopDC::Path{}; call.ret(1); }); }); registry.add("EndPath", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); if (!dc.path || !dc.path->open) throw uint32_t(1003); dc.path->open = false; call.ret(1); }); }); registry.add("AbortPath", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { require_dc(call, 0).path.reset(); call.ret(1); }); }); registry.add("CloseFigure", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); if (!dc.path || !dc.path->open || dc.path->figures.empty()) throw uint32_t(1003); // The next point starts a figure of its own. dc.path->figures.push_back({}); call.ret(1); }); }); // PolyDraw(dc, points, types, count): PT_MOVETO (6) starts a figure, PT_LINETO (2) and three PT_BEZIERTO (4) points extend it, and // PT_CLOSEFIGURE (1) on a point closes it. Inside a path bracket the figures are recorded; outside they are stroked with the pen. registry.add("PolyDraw", 4, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const int32_t count = call.i32(3); if (count < 0 || count > 65536 || (count && (!call[1] || !call[2]))) throw uint32_t(87); const auto types = call.read(call[2], size_t(count)); std::vector>> figures; std::array current{double(dc.position_x), double(dc.position_y)}; bool closed = true; const auto point_at = [&](int32_t index) { const auto point = read_point(call, call[1] + uint64_t(index) * 8); return std::array{double(point.x), double(point.y)}; }; const auto extend = [&](const std::array &point) { if (closed) figures.push_back({current}); closed = false; figures.back().push_back(point); current = point; }; for (int32_t index = 0; index < count; ++index) { const uint8_t type = types[size_t(index)] & ~1; if (type == 6) { current = point_at(index); closed = true; } else if (type == 2) { extend(point_at(index)); } else if (type == 4 && index + 2 < count && (types[size_t(index) + 1] & ~1) == 4 && (types[size_t(index) + 2] & ~1) == 4) { // A cubic Bezier from the current point, flattened into short lines. const auto start = current, first = point_at(index), second = point_at(index + 1), end = point_at(index + 2); for (int step = 1; step <= 16; ++step) { const double t = step / 16.0, u = 1 - t; extend({u * u * u * start[0] + 3 * u * u * t * first[0] + 3 * u * t * t * second[0] + t * t * t * end[0], u * u * u * start[1] + 3 * u * u * t * first[1] + 3 * u * t * t * second[1] + t * t * t * end[1]}); } index += 2; } else { throw uint32_t(87); } if ((types[size_t(index)] & 1) && !closed && !figures.empty()) { current = figures.back().front(); if (!dc.path || !dc.path->open) figures.back().push_back(current); closed = true; } } dc.position_x = int32_t(std::lround(current[0])); dc.position_y = int32_t(std::lround(current[1])); if (dc.path && dc.path->open) { if (!dc.path->figures.empty() && dc.path->figures.back().empty()) dc.path->figures.pop_back(); for (auto &figure : figures) dc.path->figures.push_back(std::move(figure)); call.ret(1); return; } Painter painter(objects, dc); for (const auto &figure : figures) { std::vector> points; for (const auto &point : figure) points.push_back({int32_t(std::lround(point[0])), int32_t(std::lround(point[1]))}); painter.polyline(points, selected_pen(objects, dc), false); } if (painter.changed()) touched(dc); call.ret(1); }); }); // FillPath, StrokePath and StrokeAndFillPath(dc): the closed path filled with the brush as the polygon fill mode says, its figures // outlined, closed, with the pen, or both; the path is used up. const auto draw_path = [=, this](ApiCall &call, bool fill, bool stroke) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); if (!dc.path || dc.path->open) throw uint32_t(1003); const auto figures = std::move(dc.path->figures); dc.path.reset(); Painter painter(objects, dc); if (fill) { const auto &brush = selected_brush(objects, dc); for (const auto &box : fill_polygons(figures, dc.poly_fill != 2).rectangles()) painter.fill(box.left, box.top, box.right, box.bottom, brush); } if (stroke) for (const auto &figure : figures) { std::vector> points; for (const auto &point : figure) points.push_back({int32_t(std::lround(point[0])), int32_t(std::lround(point[1]))}); if (points.size() > 1) painter.polyline(points, selected_pen(objects, dc), true); } if (painter.changed()) touched(dc); call.ret(1); }); }; registry.add("FillPath", 1, [draw_path](ApiCall &call) { draw_path(call, true, false); }); registry.add("StrokePath", 1, [draw_path](ApiCall &call) { draw_path(call, false, true); }); registry.add("StrokeAndFillPath", 1, [draw_path](ApiCall &call) { draw_path(call, true, true); }); // PathToRegion(dc): the region the closed path covers in device coordinates, filled as the polygon fill mode says; the path is used up. registry.add("PathToRegion", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); if (!dc.path || dc.path->open) throw uint32_t(1003); auto figures = std::move(dc.path->figures); dc.path.reset(); const double dx = double(dc.viewport_org_x) - dc.window_org_x, dy = double(dc.viewport_org_y) - dc.window_org_y; for (auto &figure : figures) for (auto &point : figure) { point[0] += dx; point[1] += dy; } adopt(fill_polygons(figures, dc.poly_fill != 2), call); }); }); // SelectClipPath(dc, mode): the closed path becomes the clipping region, combined with the clipping the program set as // ExtSelectClipRgn combines; the path is used up. registry.add("SelectClipPath", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const auto mode = call.u32(1); if (mode < 1 || mode > 5) throw uint32_t(87); if (!dc.path || dc.path->open) throw uint32_t(1003); auto figures = std::move(dc.path->figures); dc.path.reset(); const double dx = double(dc.viewport_org_x) - dc.window_org_x, dy = double(dc.viewport_org_y) - dc.window_org_y; for (auto &figure : figures) for (auto &point : figure) { point[0] += dx; point[1] += dy; } auto region = fill_polygons(figures, dc.poly_fill != 2); std::optional value; if (mode == 5 || !dc.application_clip) { value = std::move(region); } else { DesktopRegion next; next.combine(*dc.application_clip, region, mode); value = std::move(next); } if (!objects.select_clip(call[0], std::move(value))) throw uint32_t(8); call.ret(1); }); }); registry.add("ExtCreateRegion", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { if (call.u32(1) < 32 || !call[2]) throw uint32_t(87); // RGNDATA: RGNDATAHEADER (dwSize, iType, nCount, nRgnSize, rcBound) then RECTs. const auto header_size = call.load(call[2]); const auto count = call.load(call[2] + 8); if (header_size != 32 || count > 100000 || call.u32(1) < 32 + count * 16) throw uint32_t(87); DesktopRegion region; for (uint32_t i = 0; i < count; ++i) { const auto rect = read_rect(call, call[2] + 32 + uint64_t(i) * 16); if (rect.right > rect.left && rect.bottom > rect.top) region.add(rect); } adopt(std::move(region), call); }); }); registry.add("FillRgn", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const auto *region = objects.region(call[1]); const auto *brush = objects.brush(call[2]); if (!region || !brush) throw uint32_t(6); Painter painter(objects, dc); const auto bounds = region->bounds(); for (int32_t y = bounds.top; y < bounds.bottom; ++y) for (int32_t x = bounds.left; x < bounds.right; ++x) if (region->contains(x, y)) painter.fill(x, y, x + 1, y + 1, *brush); if (painter.changed()) touched(dc); call.ret(1); }); }); registry.add("FrameRgn", 5, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const auto *region = objects.region(call[1]); const auto *brush = objects.brush(call[2]); if (!region || !brush) throw uint32_t(6); Painter painter(objects, dc); const int32_t w = std::max(1, call.i32(3)), h = std::max(1, call.i32(4)); const auto bounds = region->bounds(); for (int32_t y = bounds.top; y < bounds.bottom; ++y) for (int32_t x = bounds.left; x < bounds.right; ++x) { if (!region->contains(x, y)) continue; const bool edge = !region->contains(x - w, y) || !region->contains(x + w, y) || !region->contains(x, y - h) || !region->contains(x, y + h); if (edge) painter.fill(x, y, x + 1, y + 1, *brush); } if (painter.changed()) touched(dc); call.ret(1); }); }); registry.add("RectVisible", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const auto rect = read_rect(call, call[1]); Painter painter(objects, dc); bool any = false; for (int32_t y = rect.top; y < rect.bottom && !any; ++y) for (int32_t x = rect.left; x < rect.right && !any; ++x) any = painter.visible(int64_t(x) + dc.origin_x, int64_t(y) + dc.origin_y); call.ret(any); }); }); // ---- shapes ---- registry.add("Rectangle", 5, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); Painter painter(objects, dc); int64_t left = call.i32(1), top = call.i32(2), right = call.i32(3), bottom = call.i32(4); if (right < left) std::swap(left, right); if (bottom < top) std::swap(top, bottom); const auto &brush = selected_brush(objects, dc); const auto &pen = selected_pen(objects, dc); if (right > left && bottom > top) { if (!brush.hollow) painter.fill(left + (pen.hollow ? 0 : 1), top + (pen.hollow ? 0 : 1), right - (pen.hollow ? 0 : 1), bottom - (pen.hollow ? 0 : 1), brush); if (!pen.hollow) { const std::pair corners[4] = {{int32_t(left), int32_t(top)}, {int32_t(right - 1), int32_t(top)}, {int32_t(right - 1), int32_t(bottom - 1)}, {int32_t(left), int32_t(bottom - 1)}}; painter.polyline(corners, pen, true); } } if (painter.changed()) touched(dc); call.ret(1); }); }); registry.add("RoundRect", 7, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); Painter painter(objects, dc); painter.ellipse(call.i32(1), call.i32(2), call.i32(3), call.i32(4), std::abs(call.i32(5)), std::abs(call.i32(6)), true); if (painter.changed()) touched(dc); call.ret(1); }); }); registry.add("Ellipse", 5, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); Painter painter(objects, dc); painter.ellipse(call.i32(1), call.i32(2), call.i32(3), call.i32(4), 0, 0, false); if (painter.changed()) touched(dc); call.ret(1); }); }); const auto read_points = [](const ApiCall &call, uint64_t address, int32_t count) { if (count < 0 || count > 65536) throw uint32_t(87); std::vector> points; points.reserve(size_t(count)); const auto bytes = call.read(address, size_t(count) * 8); for (int32_t i = 0; i < count; ++i) { int32_t x, y; std::memcpy(&x, bytes.data() + size_t(i) * 8, 4); std::memcpy(&y, bytes.data() + size_t(i) * 8 + 4, 4); points.emplace_back(x, y); } return points; }; registry.add("Polygon", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const auto points = read_points(call, call[1], call.i32(2)); if (points.size() < 2) throw uint32_t(87); Painter painter(objects, dc); painter.polygon(points); if (painter.changed()) touched(dc); call.ret(1); }); }); registry.add("Polyline", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const auto points = read_points(call, call[1], call.i32(2)); if (points.size() < 2) throw uint32_t(87); Painter painter(objects, dc); painter.polyline(points, selected_pen(objects, dc), false); if (painter.changed()) touched(dc); call.ret(1); }); }); registry.add("PolylineTo", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); auto points = read_points(call, call[1], call.i32(2)); if (points.empty()) throw uint32_t(87); points.insert(points.begin(), {dc.position_x, dc.position_y}); Painter painter(objects, dc); painter.polyline(points, selected_pen(objects, dc), false); dc.position_x = points.back().first; dc.position_y = points.back().second; if (painter.changed()) touched(dc); call.ret(1); }); }); registry.add("PolyPolyline", 4, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const uint32_t polylines = call.u32(3); if (polylines > 4096) throw uint32_t(87); const auto counts = call.read(call[2], size_t(polylines) * 4); Painter painter(objects, dc); uint64_t cursor = call[1]; for (uint32_t i = 0; i < polylines; ++i) { uint32_t count; std::memcpy(&count, counts.data() + size_t(i) * 4, 4); const auto points = read_points(call, cursor, int32_t(count)); painter.polyline(points, selected_pen(objects, dc), false); cursor += uint64_t(count) * 8; } if (painter.changed()) touched(dc); call.ret(1); }); }); registry.add("LineTo", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); Painter painter(objects, dc); Painter::Stroke stroke; painter.line(dc.position_x, dc.position_y, call.i32(1), call.i32(2), selected_pen(objects, dc), false, stroke); dc.position_x = call.i32(1); dc.position_y = call.i32(2); if (painter.changed()) touched(dc); call.ret(1); }); }); // ---- block transfer ---- const auto blit_to=[=, this](DesktopDC &destination, int32_t dx, int32_t dy, int32_t dw, int32_t dh, DesktopDC *source_dc, int32_t sx, int32_t sy, int32_t sw, int32_t sh, uint32_t rop) { if (blit(objects, destination, dx, dy, dw, dh, source_dc, sx, sy, sw, sh, rop)) touched(destination); }; registry.add("BitBlt", 9, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &destination = require_dc(call, 0); const uint32_t rop = call.u32(8); DesktopDC *source = call[5] ? objects.dc(call[5]) : nullptr; if (call[5] && !source) throw uint32_t(6); blit_to(destination, call.i32(1), call.i32(2), call.i32(3), call.i32(4), source, call.i32(6), call.i32(7), call.i32(3), call.i32(4), rop); call.ret(1); }); }); registry.add("StretchBlt", 11, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &destination = require_dc(call, 0); DesktopDC *source = call[5] ? objects.dc(call[5]) : nullptr; if (call[5] && !source) throw uint32_t(6); blit_to(destination, call.i32(1), call.i32(2), call.i32(3), call.i32(4), source, call.i32(6), call.i32(7), call.i32(8), call.i32(9), call.u32(10)); call.ret(1); }); }); // msimg32's AlphaBlend is gdi32's GdiAlphaBlend under its older name. registry.add("AlphaBlend", 11, [®istry](ApiCall &call) { registry.find("GdiAlphaBlend")->second(call); }); registry.add("PatBlt", 6, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &destination = require_dc(call, 0); blit_to(destination, call.i32(1), call.i32(2), call.i32(3), call.i32(4), nullptr, 0, 0, call.i32(3), call.i32(4), call.u32(5)); call.ret(1); }); }); registry.add("FillRect", 3, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const auto rect = read_rect(call, call[1]); uint64_t handle = call[2]; // Small integers are system color indices plus one. if (handle && handle <= 31) handle = objects.system_brush(int32_t(handle - 1)); const auto *brush = objects.brush(handle); if (!brush) throw uint32_t(handle > 0 && handle <= 32 ? 120 : 6); Painter painter(objects, dc); painter.fill(rect.left, rect.top, rect.right, rect.bottom, *brush); if (painter.changed()) touched(dc); call.ret(1); }); }); // GradientFill(dc, vertices, vertex count, mesh, mesh count, mode): rectangles blend from the first vertex's color to the second's along one axis. registry.add("GradientFill", 6, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const uint32_t mode = call.u32(5), vertex_count = call.u32(2), mesh_count = call.u32(4); if (mode > 1 || !call[1] || !call[3] || vertex_count > 4096 || mesh_count > 4096) throw uint32_t(mode > 2 ? 87 : 120); // TRIVERTEX: x, y, then red, green, blue and alpha as 16-bit values. const auto vertices = call.read(call[1], size_t(vertex_count) * 16); const auto mesh = call.read(call[3], size_t(mesh_count) * 8); const auto word = [](const std::vector &bytes, size_t at, size_t width) { uint32_t value = 0; std::memcpy(&value, bytes.data() + at, width); return value; }; Painter painter(objects, dc); for (uint32_t index = 0; index < mesh_count; ++index) { const uint32_t first = word(mesh, index * 8, 4), second = word(mesh, index * 8 + 4, 4); if (first >= vertex_count || second >= vertex_count) throw uint32_t(87); const auto at = [&](uint32_t vertex, size_t field, size_t width) { return word(vertices, vertex * 16 + field, width); }; const int32_t left = std::min(int32_t(at(first, 0, 4)), int32_t(at(second, 0, 4))), right = std::max(int32_t(at(first, 0, 4)), int32_t(at(second, 0, 4))); const int32_t top = std::min(int32_t(at(first, 4, 4)), int32_t(at(second, 4, 4))), bottom = std::max(int32_t(at(first, 4, 4)), int32_t(at(second, 4, 4))); const int32_t steps = mode == 0 ? right - left : bottom - top; for (int32_t step = 0; step < steps; ++step) { const auto channel = [&](size_t field) { const double from = at(first, field, 2) >> 8, to = at(second, field, 2) >> 8; return uint32_t(from + (to - from) * (steps > 1 ? double(step) / (steps - 1) : 0.0) + 0.5); }; DesktopBrush brush; brush.color = channel(8) | channel(10) << 8 | channel(12) << 16; if (mode == 0) painter.fill(left + step, top, left + step + 1, bottom, brush); else painter.fill(left, top + step, right, top + step + 1, brush); } } if (painter.changed()) touched(dc); call.ret(1); }); }); registry.add("FrameRect", 3, [=, this](ApiCall &call) { auto *dc = objects.dc(call[0]); const auto *brush = objects.brush(call[2]); if (!dc || !brush) { call.ret(0); return; } guarded(call, 0, [&] { const auto rect = read_rect(call, call[1]); if (rect.right <= rect.left || rect.bottom <= rect.top) { call.ret(0); return; } Painter painter(objects, *dc); painter.fill(rect.left, rect.top, rect.right, rect.top + 1, *brush); painter.fill(rect.left, rect.bottom - 1, rect.right, rect.bottom, *brush); painter.fill(rect.left, rect.top + 1, rect.left + 1, rect.bottom - 1, *brush); painter.fill(rect.right - 1, rect.top + 1, rect.right, rect.bottom - 1, *brush); if (painter.changed()) touched(*dc); call.ret(1); }); }); registry.add("GdiAlphaBlend", 11, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &destination = require_dc(call, 0); auto &source_dc = require_dc(call, 5); Painter painter(objects, destination); auto *source = objects.bitmap(source_dc.bitmap); if (!source || source->depth != 32) throw uint32_t(120); const uint32_t blend = call.u32(10); const uint32_t constant = (blend >> 16) & 0xff, format = (blend >> 24) & 0xff; const int32_t dx = call.i32(1), dy = call.i32(2), dw = call.i32(3), dh = call.i32(4); const int32_t sx = call.i32(6), sy = call.i32(7), sw = call.i32(8), sh = call.i32(9); if (dw <= 0 || dh <= 0 || sw <= 0 || sh <= 0) throw uint32_t(87); const auto pixels = source->pixels(); for (int32_t y = 0; y < dh; ++y) for (int32_t x = 0; x < dw; ++x) { const int64_t device_x = int64_t(dx) + x + destination.origin_x, device_y = int64_t(dy) + y + destination.origin_y; if (!painter.visible(device_x, device_y)) continue; const int64_t source_x = sx + int64_t(x) * sw / dw, source_y = sy + int64_t(y) * sh / dh; if (source_x < 0 || source_y < 0 || source_x >= source->width || source_y >= source->height) continue; const size_t row = source->top_down ? size_t(source_y) : source->height - 1 - size_t(source_y); const auto *p = &pixels[(row * source->width + size_t(source_x)) * 4]; uint32_t alpha = constant; uint32_t blue = p[0], green = p[1], red = p[2]; if (format & 1) { // Premultiplied source: scale by the constant alpha, then composite with the pixel's own alpha. const uint32_t pixel_alpha = p[3]; blue = blue * constant / 255; green = green * constant / 255; red = red * constant / 255; alpha = pixel_alpha * constant / 255; const uint32_t destination_color = painter.read(device_x, device_y); const uint32_t keep = 255 - alpha; const uint32_t out_blue = blue + color_blue(destination_color) * keep / 255; const uint32_t out_green = green + color_green(destination_color) * keep / 255; const uint32_t out_red = red + color_red(destination_color) * keep / 255; painter.plot(device_x, device_y, std::min(out_red, 255u) | std::min(out_green, 255u) << 8 | std::min(out_blue, 255u) << 16, 13); continue; } const uint32_t destination_color = painter.read(device_x, device_y); const uint32_t out_blue = (blue * alpha + color_blue(destination_color) * (255 - alpha)) / 255; const uint32_t out_green = (green * alpha + color_green(destination_color) * (255 - alpha)) / 255; const uint32_t out_red = (red * alpha + color_red(destination_color) * (255 - alpha)) / 255; painter.plot(device_x, device_y, out_red | out_green << 8 | out_blue << 16, 13); } if (painter.changed()) touched(destination); call.ret(1); }); }); // ---- device-independent bitmaps ---- registry.add("StretchDIBits", 13, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto &dc = require_dc(call, 0); const int32_t dx = call.i32(1), dy = call.i32(2), dw = call.i32(3), dh = call.i32(4); const int32_t sx = call.i32(5), sy = call.i32(6), sw = call.i32(7), sh = call.i32(8); const auto image = decode_dib(call, call[10], call[9], call.u32(11)); Painter painter(objects, dc); if (!dw || !dh || !sw || !sh) { call.ret(0); return; } const int64_t abs_dw = std::abs(int64_t(dw)), abs_dh = std::abs(int64_t(dh)), abs_sw = std::abs(int64_t(sw)), abs_sh = std::abs(int64_t(sh)); const bool flip_x = (dw < 0) != (sw < 0), flip_y = (dh < 0) != (sh < 0); const int64_t dest_left = dw < 0 ? int64_t(dx) + dw + 1 : dx, dest_top = dh < 0 ? int64_t(dy) + dh + 1 : dy; // DIB source rows count from the bottom: the origin is the lower-left corner for bottom-up images. const bool top_down = call.load(call[10] + 8) < 0; for (int64_t row = 0; row < abs_dh; ++row) for (int64_t column = 0; column < abs_dw; ++column) { const int64_t device_x = dest_left + column + dc.origin_x, device_y = dest_top + row + dc.origin_y; if (!painter.visible(device_x, device_y)) continue; const int64_t scaled_x = abs_dw == abs_sw ? column : column * abs_sw / abs_dw; const int64_t scaled_y = abs_dh == abs_sh ? row : row * abs_sh / abs_dh; const int64_t image_x = sx + (flip_x ? abs_sw - 1 - scaled_x : scaled_x); int64_t image_row = flip_y ? abs_sh - 1 - scaled_y : scaled_y; // Rows of a top-down DIB use sy as the first line; rows of a bottom-up DIB count up from the bottom. const int64_t image_y = top_down ? sy + image_row : image.height - 1 - (sy + (abs_sh - 1 - image_row)); if (image_x < 0 || image_x >= image.width || image_y < 0 || image_y >= image.height) continue; const uint32_t source_pixel = image.pixels[size_t(image_y) * image.width + size_t(image_x)]; painter.plot(device_x, device_y, colorref(source_pixel & 0xffffff), 13); // A DIB section keeps the alpha channel of what is copied into it. if (painter.bitmap.dib_section && painter.bitmap.depth == 32 && image.has_alpha) if (auto *target = painter.pixel_pointer(device_x, device_y)) target[3] = uint8_t(source_pixel >> 24); } if (painter.changed()) touched(dc); call.ret(uint64_t(abs_dh)); }); }); // CreateDIBitmap: a device bitmap of the header's size, filled from the bits when CBM_INIT asks for it. registry.add("CreateDIBitmap", 6, [®istry, this](ApiCall &call) { if (!call[1]) { call.fail(87); return; } const int32_t width = call.load(call[1] + 4), height = call.load(call[1] + 8); if (width <= 0 || !height) { call.fail(87); return; } const uint32_t rows = uint32_t(height < 0 ? -height : height); const uint64_t bitmap = allocate_bitmap(uint32_t(width), rows, 32, false, true); if (!bitmap) { call.fail(8); return; } if ((call.u32(2) & 4) && call[3] && call[4]) { ApiCall fill(call.process, call.environment, {call[0], bitmap, 0, rows, call[3], call[4], call[5]}); registry.find("SetDIBits")->second(fill); } call.ret(bitmap); }); registry.add("SetDIBits", 7, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto *bitmap = objects.bitmap(call[1]); if (!bitmap || bitmap->depth != 32) throw uint32_t(6); const uint32_t start = call.u32(2), lines = call.u32(3); int32_t rows = 0; const auto image = decode_dib(call, call[5], call[4], call.u32(6), lines, &rows); auto pixels = bitmap->pixels(); const bool top_down = call.load(call[5] + 8) < 0; uint32_t done = 0; for (uint32_t row = 0; row < lines && row + start < uint32_t(rows) && row + start < bitmap->height; ++row, ++done) { // Storage line start+row of the DIB lands on the bitmap row with the same top-to-bottom position. const uint32_t image_row = top_down ? start + row : uint32_t(rows) - 1 - (start + row); const size_t memory_row = bitmap->top_down ? image_row : size_t(bitmap->height) - 1 - image_row; if (image_row >= uint32_t(image.height) || image_row >= bitmap->height) continue; for (int32_t x = 0; x < std::min(image.width, int32_t(bitmap->width)); ++x) { auto *p = &pixels[(memory_row * bitmap->width + size_t(x)) * 4]; const uint32_t value = image.pixels[size_t(image_row) * image.width + size_t(x)]; p[0] = uint8_t(value); p[1] = uint8_t(value >> 8); p[2] = uint8_t(value >> 16); p[3] = uint8_t(value >> 24); } } call.ret(done); }); }); registry.add("GetDIBits", 7, [=, this](ApiCall &call) { guarded(call, 0, [&] { auto *bitmap = objects.bitmap(call[1]); if (!bitmap) throw uint32_t(6); const auto header_size = call.load(call[5]); if (header_size < 40) throw uint32_t(87); auto bit_count = call.load(call[5] + 14); if (!bit_count) { // A query fills in the format: width, height, planes and bit count of the selected bitmap. call.store(call[5] + 4, int32_t(bitmap->width)); call.store(call[5] + 8, int32_t(bitmap->height)); call.store(call[5] + 12, 1); call.store(call[5] + 14, uint16_t(bitmap->depth == 1 ? 1 : 32)); call.store(call[5] + 16, 0); call.store(call[5] + 20, uint32_t(((size_t(bitmap->width) * (bitmap->depth == 1 ? 1 : 32) + 31) / 32) * 4 * bitmap->height)); call.ret(1); return; } if (!call[4]) { call.ret(1); return; } if (bit_count != 32 && bit_count != 24 && bit_count != 8 && bit_count != 1) throw uint32_t(120); const uint32_t start = call.u32(2), lines = call.u32(3); const int32_t height_field = call.load(call[5] + 8); const bool top_down = height_field < 0; const size_t stride = ((size_t(bitmap->width) * bit_count + 31) / 32) * 4; if (start > bitmap->height) throw uint32_t(87); const uint32_t take = std::min(lines, bitmap->height - start); std::vector output(stride * take, 0); const auto pixels = bitmap->pixels(); for (uint32_t row = 0; row < take; ++row) { const uint32_t image_row = start + row; const size_t memory_row = bitmap->top_down ? image_row : bitmap->height - 1 - image_row; // A bottom-up DIB stores the image's last row first. const size_t out_row = top_down ? row : take - 1 - row; for (uint32_t x = 0; x < bitmap->width; ++x) { uint32_t color, alpha = 0; if (bitmap->depth == 1) { const size_t source_stride = ((size_t(bitmap->width) + 15) / 16) * 2; color = ((pixels[memory_row * source_stride + x / 8] >> (7 - x % 8)) & 1) ? 0xffffff : 0; } else { const auto *p = &pixels[(memory_row * bitmap->width + x) * 4]; color = uint32_t(p[2]) | uint32_t(p[1]) << 8 | uint32_t(p[0]) << 16; alpha = p[3]; } auto *target = &output[out_row * stride]; if (bit_count == 32) { // A bitmap that carries an alpha channel hands it back, which image lists use to tell ARGB images from plain ones. target[x * 4] = uint8_t(color >> 16); target[x * 4 + 1] = uint8_t(color >> 8); target[x * 4 + 2] = uint8_t(color); target[x * 4 + 3] = uint8_t(alpha); } else if (bit_count == 24) { target[x * 3] = uint8_t(color >> 16); target[x * 3 + 1] = uint8_t(color >> 8); target[x * 3 + 2] = uint8_t(color); } else if (bit_count == 8) { target[x] = uint8_t((color_red(color) * 30 + color_green(color) * 59 + color_blue(color) * 11) / 100); } else { target[x / 8] = uint8_t(target[x / 8] | ((color_red(color) + color_green(color) + color_blue(color) >= 384) ? (0x80 >> (x % 8)) : 0)); } } } call.write(call[4], output); // The colour table of an indexed result is part of the answer: black and white, or the grey ramp the indices stand for. if (bit_count <= 8) { const uint32_t entries = bit_count == 1 ? 2 : 256; std::vector table(entries * 4); for (uint32_t entry = 0; entry < entries; ++entry) { const uint8_t level = bit_count == 1 ? (entry ? 255 : 0) : uint8_t(entry); table[entry * 4] = table[entry * 4 + 1] = table[entry * 4 + 2] = level; } call.write(call[5] + header_size, table); } call.ret(take); }); }); registry.add("GetDIBColorTable", 4, [=, this](ApiCall &call) { guarded(call, 0, [&] { require_dc(call, 0); call.ret(0); }); }); registry.add("SetDIBColorTable", 4, [=, this](ApiCall &call) { guarded(call, 0, [&] { require_dc(call, 0); call.ret(0); }); }); registry.add("PlayEnhMetaFile", 3, [=, this](ApiCall &call) { call.fail(120, 0); }); // ---- printing is not provided ---- for (const auto *name : {"StartDocW", "StartDocA", "EndDoc", "StartPage", "EndPage", "AbortDoc"}) registry.add(name, 2, [](ApiCall &call) { call.fail(120, 0); }); registry.add("GdiGetCodePage", 1, [](ApiCall &call) { call.ret(1252); }); } void NativeDesktop::register_image_api(ApiRegistry ®istry) { const auto guarded = [](ApiCall &call, uint64_t failure, const std::function &body) { try { body(); } catch (const uint32_t &error) { call.fail(error, failure); } catch (const std::bad_alloc &) { call.fail(8, failure); } }; // A persistent guest page for names and small buffers that must be passed to resource lookups. const auto page = [this]() { if (!scratch) scratch = process.map_memory(8192, PROT_READ | PROT_WRITE); return scratch + 4096; }; // Resource names are integers or strings; an ANSI string is widened into the scratch page first. const auto wide_name = [=, this](ApiCall &call, uint64_t name, bool ansi) -> uint64_t { if (name < 0x10000 || !ansi) return name; const auto text = from_ansi(call.narrow(name, 512), 1252); std::vector bytes((text.size() + 1) * 2, 0); std::memcpy(bytes.data(), text.data(), text.size() * 2); call.write(page(), bytes); return page(); }; const auto make_bitmap = [this](const ApiCall &call, uint64_t info, uint64_t bits, bool keep_alpha) -> uint64_t { const auto image = decode_dib(call, info, bits, 0); const auto handle = allocate_bitmap(uint32_t(image.width), uint32_t(image.height), 32, false, true); if (!handle) throw uint32_t(8); auto *bitmap = objects.bitmap(handle); auto pixels = bitmap->pixels(); for (size_t i = 0; i < image.pixels.size(); ++i) { const uint32_t value = image.pixels[i]; pixels[i * 4] = uint8_t(value); pixels[i * 4 + 1] = uint8_t(value >> 8); pixels[i * 4 + 2] = uint8_t(value >> 16); pixels[i * 4 + 3] = keep_alpha && image.has_alpha ? uint8_t(value >> 24) : 0; } return handle; }; // Locates the pixel data of a packed DIB (header, optional masks, palette, then bits). const auto dib_bits = [](const ApiCall &call, uint64_t info) -> uint64_t { const auto header = call.load(info); const auto bit_count = call.load(info + 14); const auto compression = call.load(info + 16); auto colors = call.load(info + 32); if (bit_count <= 8 && (!colors || colors > (1U << bit_count))) colors = 1U << bit_count; return info + header + (bit_count <= 8 ? uint64_t(colors) * 4 : 0) + (compression == 3 && header == 40 ? 12 : 0); }; const auto bitmap_resource = [=, this](ApiCall &call, uint64_t instance, uint64_t name, bool keep_alpha) -> uint64_t { if (!instance && name >= 32700 && name < 32800) instance = oem_module; const auto view = resources.view(instance, 2, name); if (!view) throw uint32_t(1814); return make_bitmap(call, view->address, dib_bits(call, view->address), keep_alpha); }; const auto host_file = [=, this](const ApiCall &call, uint64_t name, bool ansi) -> std::vector { const auto path = ansi ? from_ansi(call.narrow(name, 1024), 1252) : call.wide(name, 1024); if (!read_host_file) throw uint32_t(2); const auto bytes = read_host_file(path); if (!bytes) throw uint32_t(2); return *bytes; }; const auto icon_resource = [=, this](ApiCall &call, uint64_t instance, uint64_t name, uint32_t cx, uint32_t cy) -> uint64_t { if (!scratch) scratch = process.map_memory(8192, PROT_READ | PROT_WRITE); const uint64_t forwarded[] = {instance, name, cx, cy, scratch}; const auto status = load_icon(forwarded); if (status) { throw uint32_t((status & 0xffff0000U) == 0x80070000U ? status & 0xffffU : 1814); } return call.load(scratch); }; // ICO file: directory of images, each a DIB (without file header) or PNG. const auto icon_file = [=, this](const std::vector &bytes, uint32_t cx, uint32_t cy) -> uint64_t { if (bytes.size() < 22 || bytes[2] != 1) throw uint32_t(13); const unsigned count = bytes[4] | bytes[5] << 8; size_t best = 0; uint32_t best_distance = UINT32_MAX; for (unsigned i = 0; i < count && 6 + (i + 1) * 16 <= bytes.size(); ++i) { const uint32_t width = bytes[6 + i * 16] ? bytes[6 + i * 16] : 256, height = bytes[7 + i * 16] ? bytes[7 + i * 16] : 256; const uint32_t distance = cx ? uint32_t(std::abs(int(width) - int(cx)) + std::abs(int(height) - int(cy))) : 1000 - width; if (distance < best_distance) { best_distance = distance; best = i; } } const uint32_t size = bytes[6 + best * 16 + 8] | bytes[6 + best * 16 + 9] << 8 | bytes[6 + best * 16 + 10] << 16 | uint32_t(bytes[6 + best * 16 + 11]) << 24; const uint32_t offset = bytes[6 + best * 16 + 12] | bytes[6 + best * 16 + 13] << 8 | bytes[6 + best * 16 + 14] << 16 | uint32_t(bytes[6 + best * 16 + 15]) << 24; if (offset > bytes.size() || size > bytes.size() - offset) throw uint32_t(13); const auto handle = create_icon(std::span(bytes.data() + offset, size), cx, cy, 0); if (!handle) throw uint32_t(environment.last_error()); return handle; }; const auto load_image = [=, this](ApiCall &call, bool ansi) { guarded(call, 0, [&] { const uint32_t type = call.u32(2), flags = call.u32(5); uint32_t cx = call.u32(3), cy = call.u32(4); if (flags & 0x40) cx = cy = 0; if (flags & 0x10) { const auto bytes = host_file(call, call[1], ansi); if (type == 0) { if (bytes.size() < 54 || bytes[0] != 'B') throw uint32_t(13); const auto buffer = process.map_memory((bytes.size() + 4095) & ~size_t(4095), PROT_READ | PROT_WRITE); uint64_t handle = 0; try { process.write_memory(buffer, bytes); handle = make_bitmap(call, buffer + 14, buffer + 14 + (dib_bits(call, buffer + 14) - (buffer + 14)), (flags & 0x2000) != 0); } catch (...) { process.unmap_memory(buffer); throw; } process.unmap_memory(buffer); call.ret(handle); } else if (type == 1) { call.ret(icon_file(bytes, cx, cy)); } else { call.ret(desktop_stock_cursor); } return; } const auto name = wide_name(call, call[1], ansi); if (type == 0) call.ret(bitmap_resource(call, call[0], name, (flags & 0x2000) != 0)); else if (type == 1) call.ret(icon_resource(call, call[0], name, cx, cy)); else if (call[0] == 0 && name >= 32512 && name < 32512 + desktop_stock_cursor_count) call.ret(desktop_stock_cursor + (name - 32512)); else // Module cursors are not rendered by the host; callers receive the arrow. call.ret(desktop_stock_cursor); }); }; registry.add("LoadImageW", 6, [=](ApiCall &call) { load_image(call, false); }); registry.add("LoadImageA", 6, [=](ApiCall &call) { load_image(call, true); }); const auto load_bitmap = [=, this](ApiCall &call, bool ansi) { guarded(call, 0, [&] { call.ret(bitmap_resource(call, call[0], wide_name(call, call[1], ansi), false)); }); }; registry.add("LoadBitmapW", 2, [=](ApiCall &call) { load_bitmap(call, false); }); registry.add("LoadBitmapA", 2, [=](ApiCall &call) { load_bitmap(call, true); }); registry.add("LoadIconA", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { if (!call[0]) throw uint32_t(120); call.ret(icon_resource(call, call[0], wide_name(call, call[1], true), 0, 0)); }); }); registry.add("LoadCursorA", 2, [=, this](ApiCall &call) { if (!call[0] && call[1] >= 32512 && call[1] < 32512 + desktop_stock_cursor_count) call.ret(desktop_stock_cursor + (call[1] - 32512)); else call.ret(desktop_stock_cursor); }); registry.add("LoadStringA", 4, [=, this](ApiCall &call) { guarded(call, 0, [&] { const int32_t capacity = call.i32(3); if (capacity <= 0 || capacity > 2040) { call.ret(0); return; } const uint64_t forwarded[] = {call[0], call[1], page(), uint64_t(capacity)}; const auto length = load_string(forwarded); if (!length) { call.ret(0); return; } const auto text = call.wide(page(), size_t(capacity)); auto narrow = to_ansi(text, 1252); call.put_narrow(call[2], narrow, size_t(capacity)); call.ret(std::min(narrow.size(), size_t(capacity) - 1)); }); }); registry.add("LoadMenuA", 2, [=, this](ApiCall &call) { guarded(call, 0, [&] { if (call[1] >= 0x10000) throw uint32_t(120); call.ret(menu_resource(call[0], call[1])); }); }); registry.add("CopyImage", 5, [=, this](ApiCall &call) { guarded(call, 0, [&] { const uint32_t type = call.u32(1); if (type == 0) { const auto *source = objects.bitmap(call[0]); if (!source) throw uint32_t(1409); const uint32_t width = call.u32(2) ? call.u32(2) : source->width, height = call.u32(3) ? call.u32(3) : source->height; const auto handle = allocate_bitmap(width, height, source->depth, false, true); if (!handle) throw uint32_t(8); auto *copy = objects.bitmap(handle); if (source->depth == 32) { const auto from = source->pixels(); auto to = copy->pixels(); for (uint32_t y = 0; y < height; ++y) for (uint32_t x = 0; x < width; ++x) { const size_t sy = source->top_down ? size_t(y) * source->height / height : source->height - 1 - size_t(y) * source->height / height; const size_t sx = size_t(x) * source->width / width; std::memcpy(&to[(size_t(y) * width + x) * 4], &from[(sy * source->width + sx) * 4], 4); } } else if (width == source->width && height == source->height) { std::copy(source->pixels().begin(), source->pixels().end(), copy->pixels().begin()); } call.ret(handle); } else { const auto *icon = objects.icon(call[0]); if (!icon && !(call[0] >= desktop_stock_cursor && call[0] < desktop_stock_cursor + desktop_stock_cursor_count)) throw uint32_t(1402); if (!icon) { call.ret(call[0]); return; } const auto handle = objects.add(*icon); if (!handle) throw uint32_t(8); call.ret(handle); } }); }); // CreateIcon(instance, width, height, planes, bits per pixel, AND bits, XOR bits): a monochrome mask whose rows are padded to words, // and the color image in the same top-down rows, at 32 bits per pixel or as a second monochrome plane. registry.add("CreateIcon", 7, [=, this](ApiCall &call) { guarded(call, 0, [&] { const uint32_t width = call.u32(1), height = call.u32(2), depth = uint32_t(uint8_t(call.u32(3))) * uint32_t(uint8_t(call.u32(4))); if (!width || !height || width > 1024 || height > 1024 || !call[5] || !call[6] || (depth != 1 && depth != 32)) throw uint32_t(87); const size_t mask_stride = ((size_t(width) + 15) / 16) * 2; const auto and_bits = call.read(call[5], mask_stride * height); const auto xor_bits = call.read(call[6], depth == 32 ? size_t(width) * height * 4 : mask_stride * height); DesktopIcon icon{}; icon.monochrome = depth == 1; icon.shared = false; auto &image = icon.image; image.color.width = width; image.color.height = height; image.color.rgba.assign(size_t(width) * height * 4, 0); image.and_mask.assign(size_t(width) * height, 0); bool any_alpha = false; for (uint32_t y = 0; y < height; ++y) for (uint32_t x = 0; x < width; ++x) { const bool transparent = (and_bits[y * mask_stride + x / 8] >> (7 - x % 8)) & 1; const size_t at = (size_t(y) * width + x) * 4; if (depth == 32) { const auto *p = &xor_bits[at]; image.color.rgba[at] = p[2]; image.color.rgba[at + 1] = p[1]; image.color.rgba[at + 2] = p[0]; image.color.rgba[at + 3] = p[3]; any_alpha = any_alpha || p[3]; } else { const uint8_t level = ((xor_bits[y * mask_stride + x / 8] >> (7 - x % 8)) & 1) ? 255 : 0; image.color.rgba[at] = image.color.rgba[at + 1] = image.color.rgba[at + 2] = level; } image.and_mask[size_t(y) * width + x] = transparent; } image.has_alpha = any_alpha; if (!any_alpha) for (size_t i = 0; i < image.and_mask.size(); ++i) image.color.rgba[i * 4 + 3] = image.and_mask[i] ? 0 : 255; const auto handle = objects.add(std::move(icon)); if (!handle) throw uint32_t(8); call.ret(handle); }); }); registry.add("CreateIconIndirect", 1, [=, this](ApiCall &call) { guarded(call, 0, [&] { // ICONINFO: fIcon, the cursor hotspot, then the mask and color bitmaps at the program's pointer width. const auto is_icon = call.load(call[0]); const size_t width = call.pointer_width(); const auto mask_handle = call.load_pointer(call[0] + (width == 8 ? 16 : 12)), color_handle = call.load_pointer(call[0] + (width == 8 ? 24 : 16)); const auto *mask = objects.bitmap(mask_handle); const auto *color = objects.bitmap(color_handle); if (!mask || !color || color->depth != 32) throw uint32_t(120); DesktopIcon icon{}; icon.monochrome = false; icon.shared = false; icon.cursor = !is_icon; icon.hotspot_x = int32_t(call.load(call[0] + 4)); icon.hotspot_y = int32_t(call.load(call[0] + 8)); auto &image = icon.image; image.color.width = color->width; image.color.height = color->height; image.color.rgba.assign(size_t(color->width) * color->height * 4, 0); image.and_mask.assign(size_t(color->width) * color->height, 0); image.xor_mask.clear(); const auto from = color->pixels(); const auto bits = mask->pixels(); const size_t stride = ((size_t(mask->width) + 15) / 16) * 2; bool any_alpha = false; for (uint32_t y = 0; y < color->height; ++y) for (uint32_t x = 0; x < color->width; ++x) { const size_t row = color->top_down ? y : color->height - 1 - y; const auto *p = &from[(row * color->width + x) * 4]; const size_t mrow = mask->top_down ? y : mask->height - 1 - y; const bool transparent = x < mask->width && y < mask->height && ((bits[mrow * stride + x / 8] >> (7 - x % 8)) & 1); const size_t at = (size_t(y) * color->width + x) * 4; image.color.rgba[at] = p[2]; image.color.rgba[at + 1] = p[1]; image.color.rgba[at + 2] = p[0]; image.color.rgba[at + 3] = p[3]; any_alpha = any_alpha || p[3]; image.and_mask[size_t(y) * color->width + x] = transparent; } image.has_alpha = any_alpha; if (!any_alpha) for (size_t i = 0; i < image.and_mask.size(); ++i) image.color.rgba[i * 4 + 3] = image.and_mask[i] ? 0 : 255; const auto handle = objects.add(std::move(icon)); if (!handle) throw uint32_t(8); call.ret(handle); }); }); } }