2 #include "api_override.h"
3 #include "gdipp_client/gdi_painter.h"
4 #include "gdipp_client/global.h"
5 #include "gdipp_client/helper.h"
6 #include "gdipp_lib/scoped_rw_lock.h"
7 #include "gdipp_rpc/gdipp_rpc.h"
12 std::set
<HDC
> hdc_in_path
;
14 bool is_valid_dc(HDC hdc
)
20 if (GetDeviceCaps(hdc
, TECHNOLOGY
) != DT_RASDISPLAY
)
23 // the DC use another std::map mode, which transform the GDI coordination space
24 // we tried to implement MM_ANISOTROPIC, and found that the text looks worse than the native API
25 if (GetMapMode(hdc
) != MM_TEXT
)
29 if ExtTextOut is called within an open path bracket, different draw function is required
30 because GDI renders the path outline pretty good, and path is rarely used (one example is Google Earth)
31 gdipp does not render HDC with path
33 if (hdc_in_path
.find(hdc
) != hdc_in_path
.end())
39 bool is_target_text(HDC hdc
, bool is_glyph_index
, LPCWSTR lpString
, size_t c
, const wchar_t *target_text
, int start_index
)
42 return true if the current string is the target text (string or glyph index array)
43 otherwise return false
44 always return true if the target is invalid
49 if (target_text
== NULL
)
56 const size_t cmp_len
= min(c
, wcslen(target_text
));
62 WORD
*gi
= new WORD
[cmp_len
];
63 GetGlyphIndicesW(hdc
, target_text
, static_cast<int>(cmp_len
), gi
, 0);
65 is_target
= (wmemcmp(lpString
+ start_index
, reinterpret_cast<const wchar_t *>(gi
), cmp_len
) == 0);
70 is_target
= (wcsncmp(lpString
+ start_index
, target_text
, cmp_len
) == 0);
75 bool fetch_glyph_run (bool is_glyph_index,
80 const dc_context &context,
81 FT_Render_Mode render_mode,
82 glyph_run &a_glyph_run,
83 GLYPH_CACHE_LEVEL &cache_level)
88 switch (context.setting_cache->renderer)
90 case RENDERER_CLEARTYPE:
92 case RENDERER_GETGLYPHOUTLINE:
93 renderer = new ggo_renderer;
94 cache_level = SINGLE_GLYPH;
96 case RENDERER_DIRECTWRITE:
97 renderer = new dw_renderer;
98 cache_level = GLYPH_RUN;
101 renderer = new wic_renderer;
105 renderer = new ft_renderer;
106 cache_level = SINGLE_GLYPH;
110 b_ret = renderer->begin(&context, render_mode);
113 if (cache_level >= GLYPH_RUN)
114 b_ret = renderer->fetch_glyph_run(is_glyph_index, is_pdy, lpString, c, lpDx, a_glyph_run);
125 void adjust_glyph_bbox(bool is_pdy
, CONST INT
*lpDx
, int char_extra
, gdipp_rpc_bitmap_glyph_run
*glyph_run
, INT
*ctrl_right
, INT
*black_right
)
128 adjust the glyph boxes from distance array
130 the dx array stores the distance from the left border of the current glyph to the next glyph
131 the count of such array should be no less than the count of glyphs
132 the last element indicates the distance between the right border of the control box of the glyph run and the left border of the last glyph
133 if pdy flag is set, every 2 elements specifies the distance of the glyph in both X and Y axis
135 gdipp matches the metrics of the glyph control box against the dx array, then adjusts black box accordingly
136 dx array is application specific, therefore the boxes after adjustment are not cached
139 assert(lpDx
!= NULL
|| char_extra
!= 0);
141 const BYTE dx_skip
= (is_pdy
? 2 : 1);
145 for (UINT i
= 0; i
< glyph_run
->count
; ++i
)
149 // distance to shift right
150 const int distance_shift
= *ctrl_right
- glyph_run
->ctrl_boxes
[i
].left
+ char_extra
;
152 glyph_run
->ctrl_boxes
[i
].left
+= distance_shift
;
153 glyph_run
->ctrl_boxes
[i
].right
+= distance_shift
;
154 glyph_run
->black_boxes
[i
].left
+= distance_shift
;
155 glyph_run
->black_boxes
[i
].right
+= distance_shift
;
159 *ctrl_right
= glyph_run
->ctrl_boxes
[i
].right
;
161 *ctrl_right
+= lpDx
[i
* dx_skip
];
162 *black_right
= glyph_run
->black_boxes
[i
].right
;
165 *black_right
= max(*black_right
, *ctrl_right
);
168 BOOL WINAPI
ExtTextOutW_hook(HDC hdc
, int x
, int y
, UINT options
, CONST RECT
* lprect
, LPCWSTR lpString
, UINT c
, CONST INT
*lpDx
)
172 gdipp_rpc_bitmap_glyph_run glyph_run
= {};
174 // all GDI text painting functions calls ExtTextOutW eventually
177 if (lpString
== NULL
|| c
== 0)
178 goto fail_safe_text_out
;
180 // rectangle is required but not specified
182 if (((options
& ETO_OPAQUE
) || (options
& ETO_CLIPPED
)) && (lprect
== NULL
))
183 goto fail_safe_text_out
;
185 // completely clipped
186 if ((options
& ETO_CLIPPED
) && IsRectEmpty(lprect
))
187 goto fail_safe_text_out
;
189 if (!is_valid_dc(hdc
))
190 goto fail_safe_text_out
;
192 const bool is_glyph_index
= !!(options
& ETO_GLYPH_INDEX
);
193 const bool is_pdy
= !!(options
& ETO_PDY
);
196 bool is_target_spec
= false;
197 //is_target_spec |= (x > 0);
198 //is_target_spec |= (y > 0);
199 //is_target_spec |= !!(options & ETO_GLYPH_INDEX));
200 //is_target_spec |= !(options & ETO_GLYPH_INDEX);
201 //is_target_spec |= (options == 4102);
202 //is_target_spec |= (c == 17);
206 ;//goto fail_safe_text_out;
208 const wchar_t *debug_text
;
210 if (!is_target_text(hdc
, is_glyph_index
, lpString
, c
, debug_text
, 0))
211 goto fail_safe_text_out
;
215 // uncomment this lock to make rendering single-threaded
216 //gdipp::lock("debug");
218 // initialize the context of the current DC
219 if (!context
.init(hdc
))
220 goto fail_safe_text_out
;
222 // create painter and paint the glyph run
224 GDIPP_RPC_SESSION_HANDLE h_session
= NULL
;
225 e
= gdipp_rpc_begin_session(h_gdipp_rpc
, reinterpret_cast<const byte
*>(&context
.log_font
), sizeof(context
.log_font
), context
.bmp_header
.biBitCount
, &h_session
);
226 if (e
!= 0 || h_session
== NULL
)
227 goto fail_safe_text_out
;
229 e
= gdipp_rpc_make_bitmap_glyph_run(h_gdipp_rpc
, h_session
, lpString
, c
, is_glyph_index
, &glyph_run
);
230 gdipp_rpc_end_session(h_gdipp_rpc
, &h_session
);
232 goto fail_safe_text_out
;
234 const int char_extra
= GetTextCharacterExtra(hdc
);
235 if (char_extra
== 0x8000000)
236 goto fail_safe_text_out
;
238 INT ctrl_right
, black_right
;
239 if (lpDx
== NULL
&& char_extra
== 0)
241 ctrl_right
= glyph_run
.ctrl_boxes
[glyph_run
.count
- 1].right
;
242 black_right
= glyph_run
.black_boxes
[glyph_run
.count
- 1].right
;
246 adjust_glyph_bbox(!!(options
& ETO_PDY
), lpDx
, char_extra
, &glyph_run
, &ctrl_right
, &black_right
);
250 switch (client_config_instance
.painter
)
252 case client_config::PAINTER_D2D
:
253 //painter = new gdimm_wic_painter;
256 painter
= new gdi_painter
;
260 b_ret
= painter
->begin(&context
);
263 b_ret
= painter
->paint(x
, y
, options
, lprect
, glyph_run
, ctrl_right
, black_right
);
268 for (UINT i
= 0; i
< glyph_run
.count
; ++i
)
270 if (glyph_run
.glyphs
[i
].buffer
!= NULL
)
271 MIDL_user_free(glyph_run
.glyphs
[i
].buffer
);
273 MIDL_user_free(glyph_run
.glyphs
);
274 MIDL_user_free(glyph_run
.ctrl_boxes
);
275 MIDL_user_free(glyph_run
.black_boxes
);
281 return ExtTextOutW(hdc
, x
, y
, options
, lprect
, lpString
, c
, lpDx
);
284 int WINAPI
DrawTextExA_hook(HDC hdc
, LPSTR lpchText
, int cchText
, LPRECT lprc
, UINT format
, LPDRAWTEXTPARAMS lpdtp
)
286 // DrawTextA calls DrawTextExA eventually
288 const int i_ret
= DrawTextExA(hdc
, lpchText
, cchText
, lprc
, format
, lpdtp
);
293 int WINAPI
DrawTextExW_hook(HDC hdc
, LPWSTR lpchText
, int cchText
, LPRECT lprc
, UINT format
, LPDRAWTEXTPARAMS lpdtp
)
295 // DrawTextW calls DrawTextExW eventually
297 const int i_ret
= DrawTextExW(hdc
, lpchText
, cchText
, lprc
, format
, lpdtp
);
302 bool get_text_extent(HDC hdc
, LPCWSTR lpString
, int count
, LPSIZE lpSize
, bool is_glyph_index
, int nMaxExtent
, LPINT lpnFit
, LPINT lpnDx
)
306 if (lpString == NULL || lpSize == NULL || count == 0)
309 if (!is_valid_dc(hdc))
313 if (!is_target_text(hdc, is_glyph_index, lpString, 0, debug_text, 0))
318 if (!context.init(hdc))
321 FT_Render_Mode render_mode;
322 if (!get_render_mode(context.setting_cache, context.bmp_header.biBitCount, context.log_font.lfQuality, render_mode))
324 if (!get_render_mode(context.setting_cache, GetDeviceCaps(hdc, BITSPIXEL), context.log_font.lfQuality, render_mode))
328 glyph_run a_glyph_run;
329 GLYPH_CACHE_LEVEL cache_level;
330 b_ret = fetch_glyph_run(is_glyph_index, false, lpString, count, NULL, context, render_mode, a_glyph_run, cache_level);
334 if (cache_level < GLYPH_RUN)
339 lpSize->cx = get_glyph_run_width(&a_glyph_run, true);
340 lpSize->cy = context.outline_metrics->otmTextMetrics.tmHeight;
343 // for GetTextExtentExPoint series
344 if (lpnFit != NULL || lpnDx != NULL)
346 std::list<RECT>::const_iterator box_iter;
348 for (box_iter = a_glyph_run.ctrl_boxes.begin(), curr_index = 0; box_iter != a_glyph_run.ctrl_boxes.end(); ++box_iter, ++curr_index)
350 if (lpnFit != NULL && box_iter->right <= nMaxExtent)
351 *lpnFit = curr_index + 1;
354 lpnDx[curr_index] = box_iter->right - a_glyph_run.ctrl_boxes.front().left;
361 BOOL APIENTRY
GetTextExtentPoint32A_hook(HDC hdc
, LPCSTR lpString
, int c
, LPSIZE lpSize
)
363 std::wstring wide_char_str
;
364 if (mb_to_wc(lpString
, c
, wide_char_str
))
366 if (get_text_extent(hdc
, wide_char_str
.c_str(), static_cast<int>(wide_char_str
.size()), lpSize
, false))
370 return GetTextExtentPoint32A(hdc
, lpString
, c
, lpSize
);
373 BOOL APIENTRY
GetTextExtentPoint32W_hook(HDC hdc
, LPCWSTR lpString
, int c
, LPSIZE lpSize
)
375 if (get_text_extent(hdc
, lpString
, c
, lpSize
, false))
378 return GetTextExtentPoint32W(hdc
, lpString
, c
, lpSize
);
381 BOOL WINAPI
GetTextExtentPointI_hook(HDC hdc
, LPWORD pgiIn
, int cgi
, LPSIZE lpSize
)
383 if (get_text_extent(hdc
, reinterpret_cast<LPCWSTR
>(pgiIn
), cgi
, lpSize
, true))
386 return GetTextExtentPointI(hdc
, pgiIn
, cgi
, lpSize
);
389 BOOL APIENTRY
GetTextExtentExPointA_hook(HDC hdc
, LPCSTR lpszString
, int cchString
, int nMaxExtent
, LPINT lpnFit
, LPINT lpnDx
, LPSIZE lpSize
)
391 std::wstring wide_char_str
;
392 if (mb_to_wc(lpszString
, cchString
, wide_char_str
))
394 if (get_text_extent(hdc
, wide_char_str
.c_str(), static_cast<int>(wide_char_str
.size()), lpSize
, false, nMaxExtent
, lpnFit
, lpnDx
))
398 return GetTextExtentExPointA(hdc
, lpszString
, cchString
, nMaxExtent
, lpnFit
, lpnDx
, lpSize
);
401 BOOL APIENTRY
GetTextExtentExPointW_hook(HDC hdc
, LPCWSTR lpszString
, int cchString
, int nMaxExtent
, LPINT lpnFit
, LPINT lpnDx
, LPSIZE lpSize
)
403 if (get_text_extent(hdc
, lpszString
, cchString
, lpSize
, false, nMaxExtent
, lpnFit
, lpnDx
))
406 return GetTextExtentExPointW(hdc
, lpszString
, cchString
, nMaxExtent
, lpnFit
, lpnDx
, lpSize
);
409 BOOL WINAPI
GetTextExtentExPointI_hook(HDC hdc
, LPWORD lpwszString
, int cwchString
, int nMaxExtent
, LPINT lpnFit
, LPINT lpnDx
, LPSIZE lpSize
)
411 if (get_text_extent(hdc
, reinterpret_cast<LPCWSTR
>(lpwszString
), cwchString
, lpSize
, true, nMaxExtent
, lpnFit
, lpnDx
))
414 return GetTextExtentExPointI(hdc
, lpwszString
, cwchString
, nMaxExtent
, lpnFit
, lpnDx
, lpSize
);
417 void adjust_ggo_metrics(const dc_context
*context
, UINT uChar
, UINT fuFormat
, LPGLYPHMETRICS lpgm
, CONST MAT2
*lpmat2
)
421 const MAT2 identity_mat = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};
422 if (memcmp(lpmat2, &identity_mat, sizeof(MAT2)) != 0)
424 // current not support transformation matrices other than identity
428 ft_renderer ft_renderer;
429 FT_OutlineGlyph target_glyph = NULL;
431 b_ret = ft_renderer.begin(context, FT_RENDER_MODE_NORMAL);
435 target_glyph = ft_renderer.get_outline_glyph(uChar, !!(fuFormat & GGO_GLYPH_INDEX));
438 if (target_glyph == NULL)
442 FT_Outline_Get_CBox(&target_glyph->outline, &glyph_bbox);
444 lpgm->gmBlackBoxX = int_from_26dot6(glyph_bbox.xMax - glyph_bbox.xMin);
445 lpgm->gmBlackBoxY = int_from_26dot6(glyph_bbox.yMax - glyph_bbox.yMin);
446 lpgm->gmptGlyphOrigin.x = int_from_26dot6(glyph_bbox.xMin);
447 lpgm->gmptGlyphOrigin.y = int_from_26dot6(glyph_bbox.yMax);
448 lpgm->gmCellIncX = static_cast<short>(int_from_16dot16(target_glyph->root.advance.x));
449 lpgm->gmCellIncY = static_cast<short>(int_from_16dot16(target_glyph->root.advance.y));*/
452 DWORD WINAPI
GetGlyphOutlineA_hook(HDC hdc
, UINT uChar
, UINT fuFormat
, LPGLYPHMETRICS lpgm
, DWORD cjBuffer
, LPVOID pvBuffer
, CONST MAT2
*lpmat2
)
454 /*const DWORD ggo_ret = GetGlyphOutlineA(hdc, uChar, fuFormat, lpgm, cjBuffer, pvBuffer, lpmat2);
455 if (ggo_ret == GDI_ERROR)
458 if (!!(fuFormat & (GGO_BITMAP | GGO_NATIVE)))
460 // do not adjust metrics if not only the glyph metrics are requested
464 if (!is_valid_dc(hdc))
468 if (!context.init(hdc))
471 if (context.setting_cache->renderer != RENDERER_FREETYPE)
474 adjust_ggo_metrics(&context, uChar, fuFormat, lpgm, lpmat2);
480 DWORD WINAPI
GetGlyphOutlineW_hook(HDC hdc
, UINT uChar
, UINT fuFormat
, LPGLYPHMETRICS lpgm
, DWORD cjBuffer
, LPVOID pvBuffer
, CONST MAT2
*lpmat2
)
482 /*const DWORD ggo_ret = GetGlyphOutlineW(hdc, uChar, fuFormat, lpgm, cjBuffer, pvBuffer, lpmat2);
483 if (ggo_ret == GDI_ERROR)
487 if (!is_target_text(hdc, !!(fuFormat & GGO_GLYPH_INDEX), reinterpret_cast<wchar_t *>(&uChar), 1, debug_text, 0))
491 if (!!(fuFormat & (GGO_BITMAP | GGO_NATIVE)))
494 const MAT2 identity_mat = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};
495 if (memcmp(lpmat2, &identity_mat, sizeof(MAT2)) != 0)
497 // do not adjust if the transformation matrix is not identical
501 if (!is_valid_dc(hdc))
505 if (!context.init(hdc))
508 if (context.setting_cache->renderer != RENDERER_FREETYPE)
511 adjust_ggo_metrics(&context, uChar, fuFormat, lpgm, lpmat2);
517 BOOL WINAPI
AbortPath_hook(HDC hdc
)
519 BOOL b_ret
= AbortPath(hdc
);
521 hdc_in_path
.erase(hdc
);
526 BOOL WINAPI
BeginPath_hook(HDC hdc
)
528 BOOL b_ret
= BeginPath(hdc
);
530 hdc_in_path
.insert(hdc
);
535 BOOL WINAPI
EndPath_hook(HDC hdc
)
537 BOOL b_ret
= EndPath(hdc
);
539 hdc_in_path
.erase(hdc
);
544 HRESULT WINAPI
ScriptPlace_hook(
545 HDC hdc
, // In Optional (see under caching)
546 SCRIPT_CACHE
*psc
, // InOut Cache handle
547 const WORD
*pwGlyphs
, // In Glyph buffer from prior ScriptShape call
548 int cGlyphs
, // In Number of glyphs
549 const SCRIPT_VISATTR
*psva
, // In Visual glyph attributes
550 SCRIPT_ANALYSIS
*psa
, // InOut Result of ScriptItemize (may have fNoGlyphIndex set)
551 int *piAdvance
, // Out Advance wdiths
552 GOFFSET
*pGoffset
, // Out x,y offset for combining glyph
553 ABC
*pABC
) // Out Composite ABC for the whole run (Optional)
556 if (!get_text_extent(hdc
, reinterpret_cast<LPCWSTR
>(pwGlyphs
), cGlyphs
, (pABC
== NULL
? NULL
: &text_extent
), !psa
->fNoGlyphIndex
, NULL
, NULL
, piAdvance
))
557 return ScriptPlace(hdc
, psc
, pwGlyphs
, cGlyphs
, psva
, psa
, piAdvance
, pGoffset
, pABC
);
559 int width_adjust
= 0;
560 if (piAdvance
!= NULL
)
562 for (int i
= cGlyphs
- 1; i
>= 0; i
--)
565 piAdvance
[i
] -= piAdvance
[i
- 1];
567 if (psva
[i
].fZeroWidth
)
569 width_adjust
+= -piAdvance
[i
];
575 // not support the offset for combining glyphs
576 ZeroMemory(pGoffset
, sizeof(GOFFSET
) * cGlyphs
);
581 pABC
->abcB
= text_extent
.cx
+ width_adjust
;
588 #if defined GDIPP_INJECT_SANDBOX && !defined _M_X64
590 void inject_at_eip(LPPROCESS_INFORMATION lpProcessInformation
)
595 // alloc buffer for the injection data
596 // the minimum allocation unit is page
597 SYSTEM_INFO sys_info
;
598 GetSystemInfo(&sys_info
);
599 BYTE
*inject_buffer
= new BYTE
[sys_info
.dwPageSize
];
600 memset(inject_buffer
, 0xcc, sys_info
.dwPageSize
);
602 // put gdimm path at the end of the buffer, leave space at the beginning for code
603 const DWORD path_offset
= sys_info
.dwPageSize
- MAX_PATH
* sizeof(wchar_t);
604 dw_ret
= GetModuleFileNameW(h_self
, reinterpret_cast<wchar_t *>(inject_buffer
+ path_offset
), MAX_PATH
);
607 // get eip of the spawned thread
609 ctx
.ContextFlags
= CONTEXT_CONTROL
;
610 b_ret
= GetThreadContext(lpProcessInformation
->hThread
, &ctx
);
613 LPVOID inject_base
= VirtualAllocEx(lpProcessInformation
->hProcess
, NULL
, sys_info
.dwPageSize
, MEM_COMMIT
, PAGE_EXECUTE_READWRITE
);
614 assert(inject_base
!= NULL
);
616 register BYTE
*p
= inject_buffer
;
618 #define emit_(t, x) *reinterpret_cast<t* UNALIGNED>(p) = (t)(x); p += sizeof(t)
619 #define emit_db(b) emit_(BYTE, b)
620 #define emit_dw(w) emit_(WORD, w)
621 #define emit_dd(d) emit_(DWORD, d)
623 emit_db(0x50); // push eax
625 emit_db(0x68); // push gdimm_path
626 emit_dd((DWORD
) inject_base
+ path_offset
);
627 emit_db(0xB8); // mov eax, LoadLibraryW
628 emit_dd(LoadLibraryW
);
629 emit_dw(0xD0FF); // call eax
631 emit_db(0x58); // pop eax -> LoadLibraryW has return value
633 emit_db(0x68); // push original_eip
635 emit_db(0xC3); // retn -> serve as an absolute jmp
637 // write injection data to target process space
638 b_ret
= WriteProcessMemory(lpProcessInformation
->hProcess
, inject_base
, inject_buffer
, sys_info
.dwPageSize
, NULL
);
641 delete[] inject_buffer
;
643 // notify code change
644 b_ret
= FlushInstructionCache(lpProcessInformation
->hProcess
, inject_base
, sys_info
.dwPageSize
);
647 // set eip to the entry point of the injection code
648 ctx
.Eip
= reinterpret_cast<DWORD
>(inject_base
);
649 b_ret
= SetThreadContext(lpProcessInformation
->hThread
, &ctx
);
655 CreateProcessAsUserW_hook(
657 LPCWSTR lpApplicationName
,
658 LPWSTR lpCommandLine
,
659 LPSECURITY_ATTRIBUTES lpProcessAttributes
,
660 LPSECURITY_ATTRIBUTES lpThreadAttributes
,
661 BOOL bInheritHandles
,
662 DWORD dwCreationFlags
,
663 LPVOID lpEnvironment
,
664 LPCWSTR lpCurrentDirectory
,
665 LPSTARTUPINFOW lpStartupInfo
,
666 LPPROCESS_INFORMATION lpProcessInformation
)
668 // if the token is not restricted, redirect the call to original API
669 // service can inject
670 if (!IsTokenRestricted(hToken
))
672 return CreateProcessAsUserW(
683 lpProcessInformation
);
686 // otherwise, the spawned process is restricted, and service cannot inject
688 // injection at EIP requires the process be suspended
689 // if CREATE_SUSPENDED is not specified in the creation flag, remember to resume process after injection
691 if (dwCreationFlags
& CREATE_SUSPENDED
)
695 is_suspended
= false;
696 dwCreationFlags
|= CREATE_SUSPENDED
;
699 if (!CreateProcessAsUserW(
710 lpProcessInformation
))
713 // since the spawned process can be restricted, EasyHook may not work
714 // we inject LoadLibrary call at the entry point of the spawned thread
715 inject_at_eip(lpProcessInformation
);
719 DWORD dw_ret
= ResumeThread(lpProcessInformation
->hThread
);
720 assert(dw_ret
!= -1);
726 #endif // GDIPP_INJECT_SANDBOX && !_M_X64
728 LPTOP_LEVEL_EXCEPTION_FILTER WINAPI
SetUnhandledExceptionFilter_hook(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
)