函数介绍:
EnumFontFamiliesEx 是 Windows GDI (图形设备接口) 中的一个函数,用于枚举系统中可用的字体族。
其中带W的这个函数是 EnumFontFamiliesEx 函数的 Unicode 版本。
如果是带A的话是对应的 ANSI 版本。
下面是对这个函数的详细介绍:
函数源代码如下:
int EnumFontFamiliesExW(
HDC hdc,
LPLOGFONTW lpLogfont,
FONTENUMPROCW lpProc,
LPARAM lParam,
DWORD dwFlags
);
参数说明:
- hdc: 设备上下文的句柄。
- pLogfont: 指向 LOGFONTW 结构的指针,用于指定要枚举的字体的特征。可以设置为 NULL 以枚举所有字体。
- lpProc: 回调函数的指针,系统会为每个匹配的字体族调用这个函数。
- lParam: 传递给回调函数的应用程序定义的数据。
- dwFlags: 附加选项的标志。通常设置为 0。
功能:
– 这个函数会枚举系统中的字体族,对于每个匹配的字体族,它都会调用指定的回调函数。
– 可以通过 lpLogfont 参数来筛选特定的字体特征,如字体名称、字符集、字体粗细等。
– 回调函数可以收集字体信息,决定是否继续枚举,或者执行其他自定义操作。
使用场景:
- 获取系统中所有可用的字体列表。
- 查找具有特定特征的字体(如特定字符集的字体)。
- 验证某个特定字体是否在系统中可用。
- 在应用程序中创建字体选择器或字体管理功能。
示例代码片段:
BOOL CALLBACK EnumFontFamiliesExProc(
ENUMLOGFONTEXA *lpelfe,
NEWTEXTMETRICA *lpntme,
DWORD FontType,
LPARAM lParam)
{
// 处理字体信息
printf("Font Name: %s\n", lpelfe->elfLogFont.lfFaceName);
return TRUE; // 继续枚举
}
// 在主函数中
HDC hdc = GetDC(NULL);
LOGFONTA lf = {0};
lf.lfCharSet = DEFAULT_CHARSET;
EnumFontFamiliesExA(hdc, &lf, (FONTENUMPROCA)EnumFontFamiliesExProc, 0, 0);
ReleaseDC(NULL, hdc);
A和W的主要区别:
- 参数类型:
- lpLogfont 的类型是 LPLOGFONTA(而不是 LPLOGFONTW)
- lpProc 的类型是 FONTENUMPROCA(而不是 FONTENUMPROCW)
- 字符编码:
- EnumFontFamiliesExA 使用 ANSI 字符编码
- EnumFontFamiliesExW 使用 Unicode 字符编码
- 结构体:
- 使用 ENUMLOGFONTEXA 而不是 ENUMLOGFONTEXW
- 使用 NEWTEXTMETRICA 而不是 NEWTEXTMETRICW
注意事项 :
– 使用完 HDC 后,记得调用 ReleaseDC 释放它。
– 回调函数应该尽可能快地执行,以避免影响性能。
– W这个函数是 Unicode 版本,对应的 ANSI 版本是 EnumFontFamiliesExA。
通过使用 gdi32.EnumFontFamiliesExW,程序可以灵活地管理和使用系统中的字体资源,增强应用程序的字体处理能力。
实际运用:
在旮旯游戏程序里,大多数都是EnumFontFamiliesExA,这里我们需要让他枚举中文的字体,所以我们的关注的点在lf.lfCharSet这个参数。
这是 `LOGFONT` 结构中的一个重要成员,用于指定字体的字符集。
`lf.CharSet` 的定义:
在 `LOGFONT` 结构中,`lfCharSet` 被定义为 `BYTE` 类型(8位无符号整数)。它用于指定字体的字符集或代码页。
CharSet的主要用途 :
- 筛选字体:在调用 `EnumFontFamiliesEx` 函数时,可以使用 `lfCharSet` 来筛选特定字符集的字体。
- 创建字体:当使用 `CreateFont` 或 `CreateFontIndirect` 创建字体时,`lfCharSet` 用于指定所需的字符集。
常用的 `lfCharSet` 值:
- `ANSI_CHARSET` (0): 表示 ANSI 字符集,主要用于英语和西欧语言。
- `DEFAULT_CHARSET` (1): 默认字符集,让系统选择最合适的字符集。也就是你电脑是什么语言就选什么。(也有时候会根据脚本和游戏的默认,选择日语)
- `SYMBOL_CHARSET` (2): 用于符号字体。
- `SHIFTJIS_CHARSET` (128): 日语字符集。
- `HANGEUL_CHARSET` (129): 韩语字符集。
- `GB2312_CHARSET` (134): 简体中文字符集。
- `CHINESEBIG5_CHARSET` (136): 繁体中文字符集。
- -`OEM_CHARSET` (255): OEM 字符集,通常用于控制台应用程序。
在 `EnumFontFamiliesEx` 中的使用:
- 1. 枚举所有字符集的字体:LOGFONT lf = {0};lf.lfCharSet = DEFAULT_CHARSET;
- 2. 枚举特定字符集的字体(例如,简体中文):LOGFONT lf = {0};lf.lfCharSet = GB2312_CHARSET;
重要说明:
- 1. 灵活性:使用 `DEFAULT_CHARSET` 可以枚举所有可用的字体,不限于特定字符集。
- 2. 性能考虑:指定特定的字符集可以减少枚举的字体数量,提高性能。
- 3. 字体匹配:当创建字体时,如果指定的字符集在请求的字体中不可用,Windows 会尝试找到最接近的匹配。
- 4. Unicode 和字符集:即使在使用 Unicode 函数(如 `EnumFontFamiliesExW`)时,`lfCharSet` 仍然很有用,因为它可以帮助筛选支持特定语言或符号集的字体。(但是,不一定做决定)
- 5. 系统依赖:可用的字符集可能因 Windows 版本和安装的语言包而异。(如果你系统没有这个对应的语言族的字体,那么他就枚举不出来的噢)
- 6. 字体替换:在某些情况下,Windows 可能会使用字体替换来显示不在指定字符集中的字符。(典型的就是游戏在使用默认的MS GOTHIC[MSゴシック]来显示中文的时候会出现粗细不一致的情况。
HOOK例子:
#include <windows.h>
#include <detours.h>
#include <stdio.h>
// 原始 EnumFontFamiliesExA 函数指针
static int (WINAPI *TrueEnumFontFamiliesExA)(
HDC hdc,
LPLOGFONTA lpLogfont,
FONTENUMPROCA lpProc,
LPARAM lParam,
DWORD dwFlags
) = EnumFontFamiliesExA;
// 自定义的字体枚举回调函数
int CALLBACK CustomFontEnumProc(
const LOGFONTA *lpelfe,
const TEXTMETRICA *lpntme,
DWORD FontType,
LPARAM lParam
) {
// 创建一个修改后的 LOGFONTA 结构
LOGFONTA modifiedLogFont = *lpelfe;
// 将字体名称改为"宋体"
strcpy_s(modifiedLogFont.lfFaceName, LF_FACESIZE, "宋体");
// 设置字符集为 GB2312_CHARSET
modifiedLogFont.lfCharSet = GB2312_CHARSET;
// 调用原始的回调函数,但使用修改后的 LOGFONTA
return ((FONTENUMPROCA)lParam)(&modifiedLogFont, lpntme, FontType, lParam);
}
// Hook 后的 EnumFontFamiliesExA 函数
int WINAPI HookedEnumFontFamiliesExA(
HDC hdc,
LPLOGFONTA lpLogfont,
FONTENUMPROCA lpProc,
LPARAM lParam,
DWORD dwFlags
) {
// 调用原始函数,但使用我们的自定义回调函数
return TrueEnumFontFamiliesExA(hdc, lpLogfont, CustomFontEnumProc, (LPARAM)lpProc, dwFlags);
}
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
if (DetourIsHelperProcess()) {
return TRUE;
}
if (dwReason == DLL_PROCESS_ATTACH) {
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)TrueEnumFontFamiliesExA, HookedEnumFontFamiliesExA);
LONG error = DetourTransactionCommit();
if (error == NO_ERROR) {
OutputDebugStringA("EnumFontFamiliesExA successfully hooked");
} else {
OutputDebugStringA("Error hooking EnumFontFamiliesExA");
}
}
else if (dwReason == DLL_PROCESS_DETACH) {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueEnumFontFamiliesExA, HookedEnumFontFamiliesExA);
DetourTransactionCommit();
}
return TRUE;
}
详细例子:
这里我们以某个游戏为例子。我们打开游戏看一下字体选择选项。发现全是日文字体。
好,我们使用x32dbg进行调试,找到调用EnumFontFamiliesExW的地方。我们往上看,发现一个push 0x地址。
这个十分可疑,合理怀疑这是调用LOGFONTA结构的地方。
进去一看,很明显发现一个cmp byte ptr ds:[ecx+0x17], 0x80 这里就是lfCharSet的地方。我们修改成cmp byte ptr ds:[ecx+0x17], 0x86
再次进入游戏,发现成功调用中文字体了。
暂无评论内容