diff --git a/patches/ddraw-GetPickRecords/0001-ddraw-Implement-Pick-and-GetPickRecords.patch b/patches/ddraw-GetPickRecords/0001-ddraw-Implement-Pick-and-GetPickRecords.patch new file mode 100644 index 00000000..4ca66216 --- /dev/null +++ b/patches/ddraw-GetPickRecords/0001-ddraw-Implement-Pick-and-GetPickRecords.patch @@ -0,0 +1,540 @@ +From f1dba65707a5a8ef5ec2d9c8213134b01cd735e2 Mon Sep 17 00:00:00 2001 +From: Matthew Wong +Date: Fri, 18 Sep 2020 00:47:13 +0000 +Subject: [PATCH] ddraw: Implement Pick() and GetPickRecords(). + +Implement functions used by some games (notably LEGO Island) for +determining which 3D object in a scene was clicked by the mouse cursor. +Fighting Steel also uses this function for mouse over. Previous stubs +would cause LEGO Island to crash upon any click and Fighting Steel +to crash on game start. A patch posted years ago on the bug thread +provided the minimum functionality to prevent crashes, but still +rendered large portions of the game inaccessible without them +implemented correctly. + +Picking has been implemented by adding a "pick mode" in +d3d_execute_buffer_execute() which skips any drawing functions +leaving just the vertex processing. Adds click tests for each triangle +when in pick mode for creating an array of D3DPICKRECORDs. + +Stress testing reveals this patch's Pick() implementation may have +slight inaccuracies to the original function; occasionally pixels right +on triangle edges result in successful picks when they don't with the +original function (and vice versa). It may be some sort of floating +point rounding error or other algorithm difference that would be +difficult to determine without seeing the original code. In practice, I +believe this inaccuracy is so negligible that it won't produce any +undesirable results for the user. + +Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=10729 +Signed-off-by: Matthew Wong +Signed-off-by: Myah Caron +--- + dlls/ddraw/ddraw_private.h | 7 +- + dlls/ddraw/device.c | 67 ++++++++++++-- + dlls/ddraw/executebuffer.c | 176 ++++++++++++++++++++++++++++++++++++- + dlls/ddraw/tests/ddraw1.c | 131 +++++++++++++++++++++++++++ + 4 files changed, 371 insertions(+), 10 deletions(-) + +diff --git a/dlls/ddraw/ddraw_private.h b/dlls/ddraw/ddraw_private.h +index 01a9579651c..889a64219e5 100644 +--- a/dlls/ddraw/ddraw_private.h ++++ b/dlls/ddraw/ddraw_private.h +@@ -336,6 +336,11 @@ struct d3d_device + struct d3d_viewport *current_viewport; + D3DVIEWPORT7 active_viewport; + ++ /* Pick data */ ++ D3DPICKRECORD *pick_records; ++ DWORD pick_record_count; ++ DWORD pick_record_size; ++ + /* Required to keep track which of two available texture blending modes in d3ddevice3 is used */ + BOOL legacyTextureBlending; + D3DTEXTUREBLEND texture_map_blend; +@@ -569,7 +574,7 @@ struct d3d_execute_buffer *unsafe_impl_from_IDirect3DExecuteBuffer(IDirect3DExec + + /* The execute function */ + HRESULT d3d_execute_buffer_execute(struct d3d_execute_buffer *execute_buffer, +- struct d3d_device *device); ++ struct d3d_device *device, D3DRECT *pick_rect); + + /***************************************************************************** + * IDirect3DVertexBuffer +diff --git a/dlls/ddraw/device.c b/dlls/ddraw/device.c +index 80556e96787..b3b63d7b361 100644 +--- a/dlls/ddraw/device.c ++++ b/dlls/ddraw/device.c +@@ -349,6 +349,9 @@ static ULONG WINAPI d3d_device_inner_Release(IUnknown *iface) + IDirect3DDevice3_DeleteViewport(&This->IDirect3DDevice3_iface, &vp->IDirect3DViewport3_iface); + } + ++ if (This->pick_record_size > 0) ++ heap_free(This->pick_records); ++ + TRACE("Releasing render target %p.\n", This->rt_iface); + rt_iface = This->rt_iface; + This->rt_iface = NULL; +@@ -758,7 +761,7 @@ static HRESULT WINAPI d3d_device1_Execute(IDirect3DDevice *iface, + + /* Execute... */ + wined3d_mutex_lock(); +- hr = d3d_execute_buffer_execute(buffer, device); ++ hr = d3d_execute_buffer_execute(buffer, device, NULL); + wined3d_mutex_unlock(); + + return hr; +@@ -1025,16 +1028,44 @@ static HRESULT WINAPI d3d_device1_NextViewport(IDirect3DDevice *iface, + * x2 and y2 are ignored. + * + * Returns: +- * D3D_OK because it's a stub ++ * D3D_OK on success ++ * DDERR_INVALIDPARAMS if any of the parameters == NULL + * + *****************************************************************************/ + static HRESULT WINAPI d3d_device1_Pick(IDirect3DDevice *iface, IDirect3DExecuteBuffer *buffer, + IDirect3DViewport *viewport, DWORD flags, D3DRECT *rect) + { +- FIXME("iface %p, buffer %p, viewport %p, flags %#lx, rect %s stub!\n", +- iface, buffer, viewport, flags, wine_dbgstr_rect((RECT *)rect)); ++ struct d3d_device *device = impl_from_IDirect3DDevice(iface); ++ struct d3d_execute_buffer *buffer_impl = unsafe_impl_from_IDirect3DExecuteBuffer(buffer); ++ struct d3d_viewport *viewport_impl = unsafe_impl_from_IDirect3DViewport(viewport); ++ HRESULT hr; + +- return D3D_OK; ++ TRACE("iface %p, buffer %p, viewport %p, flags %#lx, rect %s.\n", ++ iface, buffer, viewport, flags, wine_dbgstr_rect((RECT *)rect)); ++ ++ /* Sanity checks */ ++ if (!buffer) ++ { ++ WARN("NULL buffer, returning DDERR_INVALIDPARAMS\n"); ++ return DDERR_INVALIDPARAMS; ++ } ++ ++ if (!viewport) ++ { ++ WARN("NULL viewport, returning DDERR_INVALIDPARAMS\n"); ++ return DDERR_INVALIDPARAMS; ++ } ++ ++ if (FAILED(hr = IDirect3DDevice3_SetCurrentViewport ++ (&device->IDirect3DDevice3_iface, &viewport_impl->IDirect3DViewport3_iface))) ++ return hr; ++ ++ /* Execute the pick */ ++ wined3d_mutex_lock(); ++ hr = d3d_execute_buffer_execute(buffer_impl, device, rect); ++ wined3d_mutex_unlock(); ++ ++ return hr; + } + + /***************************************************************************** +@@ -1050,13 +1081,35 @@ static HRESULT WINAPI d3d_device1_Pick(IDirect3DDevice *iface, IDirect3DExecuteB + * D3DPickRec: Address to store the resulting D3DPICKRECORD array. + * + * Returns: +- * D3D_OK, because it's a stub ++ * D3D_OK always + * + *****************************************************************************/ + static HRESULT WINAPI d3d_device1_GetPickRecords(IDirect3DDevice *iface, + DWORD *count, D3DPICKRECORD *records) + { +- FIXME("iface %p, count %p, records %p stub!\n", iface, count, records); ++ struct d3d_device *device; ++ ++ TRACE("iface %p, count %p, records %p.\n", iface, count, records); ++ ++ /* Windows doesn't check if count is non-NULL */ ++ ++ wined3d_mutex_lock(); ++ ++ device = impl_from_IDirect3DDevice(iface); ++ ++ /* Set count to the number of pick records we have */ ++ *count = device->pick_record_count; ++ ++ /* It is correct usage according to documentation to call this function with records == NULL ++ to retrieve _just_ the record count, which the caller can then use to allocate an ++ appropriately sized array, then call this function again to fill that array with data. */ ++ if (records && count) ++ { ++ /* If we have a destination array and records to copy, copy them now */ ++ memcpy(records, device->pick_records, sizeof(*device->pick_records) * device->pick_record_count); ++ } ++ ++ wined3d_mutex_unlock(); + + return D3D_OK; + } +diff --git a/dlls/ddraw/executebuffer.c b/dlls/ddraw/executebuffer.c +index 13e639eda3f..bb050fe16b8 100644 +--- a/dlls/ddraw/executebuffer.c ++++ b/dlls/ddraw/executebuffer.c +@@ -45,15 +45,106 @@ static void _dump_D3DEXECUTEBUFFERDESC(const D3DEXECUTEBUFFERDESC *lpDesc) { + TRACE("lpData : %p\n", lpDesc->lpData); + } + +-HRESULT d3d_execute_buffer_execute(struct d3d_execute_buffer *buffer, struct d3d_device *device) ++#define TRIANGLE_SIZE 3 ++/***************************************************************************** ++ * d3d_execute_buffer_pick_test ++ * ++ * Determines whether a "point" is inside a "triangle". Mainly used when ++ * executing a "pick" from an execute buffer to determine whether a pixel ++ * coordinate (often a mouse coordinate) is inside a triangle (and ++ * therefore clicking or hovering over a 3D object in the scene). This ++ * function uses triangle rasterization algorithms to determine if the ++ * pixel falls inside (using the top-left rule, in accordance with ++ * documentation). ++ * ++ * Params: ++ * x: The X coordinate of the point to verify. ++ * y: The Y coordinate of the point to verify. ++ * verts: An array of vertices describing the screen coordinates of the ++ * triangle. This function expects 3 elements in this array. ++ * ++ * Returns: ++ * TRUE if the pixel coordinate is inside this triangle ++ * FALSE if not ++ * ++ *****************************************************************************/ ++static BOOL d3d_execute_buffer_pick_test(LONG x, LONG y, D3DTLVERTEX* verts) ++{ ++ UINT i; ++ ++ for (i = 0; i < TRIANGLE_SIZE; i++) ++ { ++ D3DTLVERTEX* v1 = &verts[(i) % TRIANGLE_SIZE]; ++ D3DTLVERTEX* v2 = &verts[(i + 1) % TRIANGLE_SIZE]; ++ D3DVALUE bias = 0.0f; ++ ++ /* Edge function - determines whether pixel is inside triangle */ ++ D3DVALUE w = (v2->sx - v1->sx) * (y - v1->sy) - (v2->sy - v1->sy) * (x - v1->sx); ++ ++ /* Force top-left rule */ ++ if ((v1->sy == v2->sy && v1->sx > v2->sx) || (v1->sy < v2->sy)) ++ bias = 1.0f; ++ ++ if (w < bias) ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++/***************************************************************************** ++ * d3d_execute_buffer_z_value_at_coords ++ * ++ * Returns the Z point of a triangle given an X, Y coordinate somewhere inside ++ * the triangle. Used as the `dvZ` parameter of D3DPICKRECORD. ++ * ++ * Params: ++ * x: The X coordinate of the point to verify. ++ * y: The Y coordinate of the point to verify. ++ * verts: An array of vertices describing the screen coordinates of the ++ * triangle. This function expects 3 elements in this array. ++ * ++ * Returns: ++ * A floating-point Z value that can be used directly as the dvZ member of a ++ * D3DPICKRECORD. ++ * ++ *****************************************************************************/ ++static D3DVALUE d3d_execute_buffer_z_value_at_coords(LONG x, LONG y, D3DTLVERTEX* verts) ++{ ++ UINT i; ++ ++ D3DVALUE z1 = 0; ++ D3DVALUE z2 = 0; ++ ++ for (i = 0; i < TRIANGLE_SIZE; i++) ++ { ++ D3DTLVERTEX* v1 = &verts[i]; ++ D3DTLVERTEX* v2 = &verts[(i + 1) % TRIANGLE_SIZE]; ++ D3DTLVERTEX* v3 = &verts[(i + 2) % TRIANGLE_SIZE]; ++ ++ z1 += v3->sz * (x - v1->sx) * (y - v2->sy) - v2->sz * (x - v1->sx) * (y - v3->sy); ++ z2 += (x - v1->sx) * (y - v2->sy) - (x - v1->sx) * (y - v3->sy); ++ } ++ ++ return z1 / z2; ++} ++ ++HRESULT d3d_execute_buffer_execute(struct d3d_execute_buffer *buffer, struct d3d_device *device, ++ D3DRECT* pick_rect) + { + DWORD is = buffer->data.dwInstructionOffset; + char *instr = (char *)buffer->desc.lpData + is; + unsigned int i, primitive_size; +- struct wined3d_map_desc map_desc; ++ struct wined3d_map_desc map_desc, vert_map_desc; + struct wined3d_box box = {0}; + HRESULT hr; + ++ /* Variables used for picking */ ++ const unsigned int vertex_size = get_flexible_vertex_size(D3DFVF_TLVERTEX); ++ D3DTLVERTEX verts[TRIANGLE_SIZE]; ++ ++ device->pick_record_count = 0; ++ + TRACE("ExecuteData :\n"); + if (TRACE_ON(ddraw)) + _dump_executedata(&(buffer->data)); +@@ -69,6 +160,26 @@ HRESULT d3d_execute_buffer_execute(struct d3d_execute_buffer *buffer, struct d3d + instr += sizeof(*current); + primitive_size = 0; + ++ if (pick_rect != NULL) ++ { ++ switch (current->bOpcode) ++ { ++ /* None of these opcodes seem to be necessary for picking */ ++ case D3DOP_POINT: ++ case D3DOP_LINE: ++ case D3DOP_STATETRANSFORM: ++ case D3DOP_STATELIGHT: ++ case D3DOP_STATERENDER: ++ case D3DOP_TEXTURELOAD: ++ case D3DOP_SPAN: ++ FIXME("ignoring opcode %d for picking\n", current->bOpcode); ++ instr += count * size; ++ continue; ++ default: ++ break; ++ } ++ } ++ + switch (current->bOpcode) + { + case D3DOP_POINT: +@@ -174,6 +285,66 @@ HRESULT d3d_execute_buffer_execute(struct d3d_execute_buffer *buffer, struct d3d + { + case 3: + indices[(i * primitive_size) + 2] = ci->v3; ++ ++ if (pick_rect != NULL) { ++ UINT j; ++ ++ /* Get D3DTLVERTEX objects for each triangle vertex */ ++ for (j = 0; j < TRIANGLE_SIZE; j++) { ++ ++ /* Get index of vertex from D3DTRIANGLE struct */ ++ switch (j) { ++ case 0: box.left = vertex_size * ci->v1; break; ++ case 1: box.left = vertex_size * ci->v2; break; ++ case 2: box.left = vertex_size * ci->v3; break; ++ } ++ ++ box.right = box.left + vertex_size; ++ if (FAILED(hr = wined3d_resource_map(wined3d_buffer_get_resource(buffer->dst_vertex_buffer), ++ 0, &vert_map_desc, &box, WINED3D_MAP_WRITE))) { ++ return hr; ++ } else { ++ /* Copy vert data into stack array */ ++ verts[j] = *((D3DTLVERTEX*)vert_map_desc.data); ++ ++ wined3d_resource_unmap(wined3d_buffer_get_resource(buffer->dst_vertex_buffer), 0); ++ } ++ } ++ ++ /* Use vertices acquired above to test for clicking */ ++ if (d3d_execute_buffer_pick_test(pick_rect->x1, pick_rect->y1, verts)) ++ { ++ D3DPICKRECORD* record; ++ ++ device->pick_record_count++; ++ ++ /* Grow the array if necessary */ ++ if (device->pick_record_count > device->pick_record_size) ++ { ++ if (device->pick_record_size == 0) device->pick_record_size = 1; ++ device->pick_record_size *= 2; ++ device->pick_records = heap_realloc(device->pick_records, ++ sizeof(*device->pick_records) * device->pick_record_size); ++ } ++ ++ /* Fill record parameters */ ++ record = &device->pick_records[device->pick_record_count - 1]; ++ ++ record->bOpcode = current->bOpcode; ++ record->bPad = 0; ++ ++ /* Write current instruction offset into file */ ++ record->dwOffset = (DWORD_PTR)instr - (DWORD_PTR)buffer->desc.lpData - is; ++ ++ /* Formula for returning the Z value at this X/Y */ ++ record->dvZ = d3d_execute_buffer_z_value_at_coords(pick_rect->x1, pick_rect->y1, verts); ++ ++ /* We have a successful pick so we can skip the rest of the triangles */ ++ instr += size * (count - i - 1); ++ count = i; ++ } ++ } ++ + /* Drop through. */ + case 2: + indices[(i * primitive_size) + 1] = ci->v2; +@@ -426,6 +597,7 @@ HRESULT d3d_execute_buffer_execute(struct d3d_execute_buffer *buffer, struct d3d + end_of_buffer: + return D3D_OK; + } ++#undef TRIANGLE_SIZE + + static inline struct d3d_execute_buffer *impl_from_IDirect3DExecuteBuffer(IDirect3DExecuteBuffer *iface) + { +diff --git a/dlls/ddraw/tests/ddraw1.c b/dlls/ddraw/tests/ddraw1.c +index f5fc7b04053..b6374e75632 100644 +--- a/dlls/ddraw/tests/ddraw1.c ++++ b/dlls/ddraw/tests/ddraw1.c +@@ -15429,6 +15429,136 @@ static void test_enum_devices(void) + ok(!refcount, "Device has %lu references left.\n", refcount); + } + ++static void test_pick(void) ++{ ++ static D3DTLVERTEX tquad[] = ++ { ++ {{320.0f}, {480.0f}, { 1.5f}, {1.0f}, {0xff00ff00}, {0x00000000}, {0.0f}, {0.0f}}, ++ {{ 0.0f}, {480.0f}, {-0.5f}, {1.0f}, {0xff00ff00}, {0x00000000}, {0.0f}, {0.0f}}, ++ {{320.0f}, { 0.0f}, { 1.5f}, {1.0f}, {0xff00ff00}, {0x00000000}, {0.0f}, {0.0f}}, ++ {{ 0.0f}, { 0.0f}, {-0.5f}, {1.0f}, {0xff00ff00}, {0x00000000}, {0.0f}, {0.0f}}, ++ }; ++ IDirect3DExecuteBuffer *execute_buffer; ++ D3DEXECUTEBUFFERDESC exec_desc; ++ IDirect3DViewport *viewport; ++ IDirect3DDevice *device; ++ IDirectDraw *ddraw; ++ UINT inst_length; ++ HWND window; ++ HRESULT hr; ++ void *ptr; ++ DWORD rec_count; ++ D3DRECT pick_rect; ++ UINT screen_width = 640; ++ UINT screen_height = 480; ++ UINT hits = 0; ++ UINT nohits = 0; ++ int i, j; ++ ++ window = create_window(); ++ ddraw = create_ddraw(); ++ ok(!!ddraw, "Failed to create a ddraw object.\n"); ++ if (!(device = create_device(ddraw, window, DDSCL_NORMAL))) ++ { ++ skip("Failed to create a 3D device, skipping test.\n"); ++ IDirectDraw_Release(ddraw); ++ DestroyWindow(window); ++ return; ++ } ++ ++ viewport = create_viewport(device, 0, 0, screen_width, screen_height); ++ ++ memset(&exec_desc, 0, sizeof(exec_desc)); ++ exec_desc.dwSize = sizeof(exec_desc); ++ exec_desc.dwFlags = D3DDEB_BUFSIZE | D3DDEB_CAPS; ++ exec_desc.dwBufferSize = 1024; ++ exec_desc.dwCaps = D3DDEBCAPS_SYSTEMMEMORY; ++ ++ hr = IDirect3DDevice_CreateExecuteBuffer(device, &exec_desc, &execute_buffer, NULL); ++ ok(SUCCEEDED(hr), "Failed to create execute buffer, hr %#lx.\n", hr); ++ hr = IDirect3DExecuteBuffer_Lock(execute_buffer, &exec_desc); ++ ok(SUCCEEDED(hr), "Failed to lock execute buffer, hr %#lx.\n", hr); ++ memcpy(exec_desc.lpData, tquad, sizeof(tquad)); ++ ptr = ((BYTE *)exec_desc.lpData) + sizeof(tquad); ++ emit_process_vertices(&ptr, D3DPROCESSVERTICES_COPY, 0, 4); ++ emit_tquad(&ptr, 0); ++ emit_end(&ptr); ++ inst_length = (BYTE *)ptr - (BYTE *)exec_desc.lpData; ++ inst_length -= sizeof(tquad); ++ hr = IDirect3DExecuteBuffer_Unlock(execute_buffer); ++ ok(SUCCEEDED(hr), "Failed to unlock execute buffer, hr %#lx.\n", hr); ++ ++ set_execute_data(execute_buffer, 4, sizeof(tquad), inst_length); ++ ++ /* Perform a number of picks, we should have a specific amount by the end */ ++ for (i = 0; i < screen_width; i += 80) ++ { ++ for (j = 0; j < screen_height; j += 60) ++ { ++ pick_rect.x1 = i; ++ pick_rect.y1 = j; ++ ++ hr = IDirect3DDevice_Pick(device, execute_buffer, viewport, 0, &pick_rect); ++ ok(SUCCEEDED(hr), "Failed to perform pick, hr %#lx.\n", hr); ++ hr = IDirect3DDevice_GetPickRecords(device, &rec_count, NULL); ++ ok(SUCCEEDED(hr), "Failed to get pick records, hr %#lx.\n", hr); ++ if (rec_count > 0) ++ hits++; ++ else ++ nohits++; ++ } ++ } ++ ++ /* ++ We should have gotten precisely equal numbers of hits and no hits since our quad ++ covers exactly half the screen ++ */ ++ ok(hits == nohits, "Got a non-equal amount of pick successes/failures: %i vs %i.\n", hits, nohits); ++ ++ /* Try some specific pixel picks */ ++ pick_rect.x1 = 480; ++ pick_rect.y1 = 360; ++ hr = IDirect3DDevice_Pick(device, execute_buffer, viewport, 0, &pick_rect); ++ ok(SUCCEEDED(hr), "Failed to perform pick, hr %#lx.\n", hr); ++ hr = IDirect3DDevice_GetPickRecords(device, &rec_count, NULL); ++ ok(SUCCEEDED(hr), "Failed to get pick records, hr %#lx.\n", hr); ++ ok(rec_count == 0, "Got incorrect number of pick records (expected 0): %lu.\n", rec_count); ++ ++ pick_rect.x1 = 240; ++ pick_rect.y1 = 120; ++ hr = IDirect3DDevice_Pick(device, execute_buffer, viewport, 0, &pick_rect); ++ ok(SUCCEEDED(hr), "Failed to perform pick, hr %#lx.\n", hr); ++ rec_count = 0; ++ hr = IDirect3DDevice_GetPickRecords(device, &rec_count, NULL); ++ ok(SUCCEEDED(hr), "Failed to get pick records, hr %#lx.\n", hr); ++ ok(rec_count == 1, "Got incorrect number of pick records (expected 1): %lu.\n", rec_count); ++ ++ if (rec_count == 1) ++ { ++ D3DPICKRECORD record; ++ ++ hr = IDirect3DDevice_GetPickRecords(device, &rec_count, &record); ++ ok(SUCCEEDED(hr), "Failed to get pick records, hr %#lx.\n", hr); ++ ok(rec_count == 1, "Got incorrect number of pick records (expected 1): %lu.\n", rec_count); ++ ++ hr = IDirect3DDevice_GetPickRecords(device, &rec_count, &record); ++ ok(SUCCEEDED(hr), "Failed to get pick records, hr %#lx.\n", hr); ++ ok(rec_count == 1, "Got incorrect number of pick records (expected 1): %lu.\n", rec_count); ++ ++ /* Tests D3DPICKRECORD for correct information */ ++ ok(record.bOpcode == 3, "Got incorrect bOpcode: %i.\n", record.bOpcode); ++ ok(record.bPad == 0, "Got incorrect bPad: %i.\n", record.bPad); ++ ok(record.dwOffset == 24, "Got incorrect dwOffset: %lu.\n", record.dwOffset); ++ ok(compare_float(record.dvZ, 1.0, 0.1), "Got incorrect dvZ: %f.\n", record.dvZ); ++ } ++ ++ destroy_viewport(device, viewport); ++ IDirect3DExecuteBuffer_Release(execute_buffer); ++ IDirect3DDevice_Release(device); ++ IDirectDraw_Release(ddraw); ++ DestroyWindow(window); ++} ++ + START_TEST(ddraw1) + { + DDDEVICEIDENTIFIER identifier; +@@ -15545,6 +15675,7 @@ START_TEST(ddraw1) + test_vtbl_protection(); + test_window_position(); + test_get_display_mode(); ++ test_pick(); + run_for_each_device_type(test_texture_wrong_caps); + test_filling_convention(); + test_enum_devices(); +-- +2.40.1 + diff --git a/patches/ddraw-GetPickRecords/definition b/patches/ddraw-GetPickRecords/definition new file mode 100644 index 00000000..b544d2d4 --- /dev/null +++ b/patches/ddraw-GetPickRecords/definition @@ -0,0 +1,2 @@ +Fixes: [10729] ddraw: Implement Pick() and GetPickRecords(). +