#CXX #Windows https://github.com/ysc3839/FontMod
早在几个月前开发被用于修复 Telegaram 在 Windows 上默认 SimSun 字体问题的 TgFont(现在已经改名 FontMod)
现在经过重构后,不仅功能更新,其代码质量也得到了一定的提升(唯一有选项的一个就是继续模块化拆分编译单元)
和某个朋友讨论的时候提到了这个项目,现在来说一下其实现的一些(大体的)概览
——
Mod 和新开发程序自然是不同的啦,一般来说都是『修改的补丁』,所以 FontMod 必然使用某种方式将自己的更改附加到 Telegram Desktop 上
Windows 上这一般使用 DLL 注入实现,不过注入的 DLL (动态链接库,可以包含
FontMod 注入之后是先解析配置文件,然后使用自己的
—- 项目管理部分
yaml-cpp/ 这是 git submodule 依赖,用于解析 YAML 配置文件
.gitmodules
.gitattributes — 保持 *.rc UTF16 BOM 和 CRLF(必要)
.gitignore
CMakeLists.txt
Resources.rc — 自动生成项目发布资源
FontMod.yaml — 默认配置文件
—
LICENCE
README.*.md
—- 头文件接口定义
resources.h — 自动生成
DefConfigFile.hpp — 默认配置文件字符串
winmm.hpp — Windows 的老音频设备设备接口
至今不清楚这个是为什么 include
—- 实现代码
FontMod.hpp
Utils.hpp
====
功能:接管系统的字体加载创建过程,利用 YAML 配置提供的信息按照用户要求进行不同的修改替换
.... 其实要这么说的话,应该挑重点,因为内容很无聊
1. 我是 DLL,利用普通的动态链接替换方法(实现一个动态链接库的所有接口,然后在 DllMain 里加自己的逻辑)
用户需要将 FontMod 替换版的 winmm.dll 替换原版 DLL,然后在 font/ 下建立字体资源,使用 FontMod.yaml 定义配置即可
2. 读取用户的配置文件使用
void loadSettings(hmod, fileMame, errMsg, fixGSOFont, userGSOFont, debug)
YAML::Load, FindKey, ->IsMap, ->IsScalar
可配置的部分包括 fonts: replace
同样的东西,GSOFont 再来一遍。
(bool)fixGSOFont: // USE_NCM_FONT
map 的话和 fonts: 的就没有区别了
3. 加载字体使用
上面的配置加载完(全局变量)之后,即可开始工作,不过配置是在 hook 替换的程序运行过程中使用的
1. 利用 VirtualProtect 修改程序分页权限,给目标地址加写入权限 (EXECUTE_READWRITE),改完后复原
2. 初始化一个代表 jmp 机器指令的 struct,算一下从 new 跳转到 [old 的偏移量 (new - old)加上 5],作为它的参数 IPoff
3. 同样的,saved 的偏移量也是 old+5
4. 复原地址空间(此时 Hook 已经写入),返回。
常量 5 是怎么来的,具体可汇编这个程序
它的意思是跳到程序逻辑起始开始执行,frame 的 winding 就不需要了(这三条指令是不需要子程序相关信息的)
修改的程序使用 inline assembly
早在几个月前开发被用于修复 Telegaram 在 Windows 上默认 SimSun 字体问题的 TgFont(现在已经改名 FontMod)
现在经过重构后,不仅功能更新,其代码质量也得到了一定的提升(唯一有选项的一个就是继续模块化拆分编译单元)
和某个朋友讨论的时候提到了这个项目,现在来说一下其实现的一些(大体的)概览
——
Mod 和新开发程序自然是不同的啦,一般来说都是『修改的补丁』,所以 FontMod 必然使用某种方式将自己的更改附加到 Telegram Desktop 上
Windows 上这一般使用 DLL 注入实现,不过注入的 DLL (动态链接库,可以包含
DLLMain
符号)要干什么呢? (hook win32 API)FontMod 注入之后是先解析配置文件,然后使用自己的
MyCreateFontIndirectW
代替 Windows 自己动态链接上的 pfnCreateFontIndirectW
HMODULE hGdi32 = GetModuleHandleW(L"gdi32.dll");现在我将从几个文件简要说明一下实现,以及一些实现细节...
auto pfnCreateFontIndirectW = GetProcAddress(hGdi32, "CreateFontIndirectW");
—- 项目管理部分
yaml-cpp/ 这是 git submodule 依赖,用于解析 YAML 配置文件
.gitmodules
.gitattributes — 保持 *.rc UTF16 BOM 和 CRLF(必要)
.gitignore
CMakeLists.txt
project(FontMod)FontMod. vcxproj (.filters)
link_external_msproject(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}.vcxproj)
add_subdirectory("yaml-cpp")
add_dependencies(${PROJECT_NAME} "yaml-cpp")
Resources.rc — 自动生成项目发布资源
FontMod.yaml — 默认配置文件
—
LICENCE
README.*.md
—- 头文件接口定义
resources.h — 自动生成
DefConfigFile.hpp — 默认配置文件字符串
winmm.hpp — Windows 的老音频设备设备接口
至今不清楚这个是为什么 include
—- 实现代码
FontMod.hpp
Utils.hpp
====
功能:接管系统的字体加载创建过程,利用 YAML 配置提供的信息按照用户要求进行不同的修改替换
.... 其实要这么说的话,应该挑重点,因为内容很无聊
1. 我是 DLL,利用普通的动态链接替换方法(实现一个动态链接库的所有接口,然后在 DllMain 里加自己的逻辑)
用户需要将 FontMod 替换版的 winmm.dll 替换原版 DLL,然后在 font/ 下建立字体资源,使用 FontMod.yaml 定义配置即可
2. 读取用户的配置文件使用
void loadSettings(hmod, fileMame, errMsg, fixGSOFont, userGSOFont, debug)
YAML::Load, FindKey, ->IsMap, ->IsScalar
可配置的部分包括 fonts: replace
fonts : { <replaced>: (replace | name): <replacement> poverride }poverrride = (ulongs) size, width, weight, (bools) italic, underLine, strikeOut, (ul2byte) charSet, outPrecision, clipPreision, quality, pitchAndFamily
同样的东西,GSOFont 再来一遍。
(bool)fixGSOFont: // USE_NCM_FONT
map 的话和 fonts: 的就没有区别了
3. 加载字体使用
上面的配置加载完(全局变量)之后,即可开始工作,不过配置是在 hook 替换的程序运行过程中使用的
auto pfnCreateFontIndirectW = GetProcAddress(hGdi32, "CreateFontIndirectW");4.
if (pfnCreateFontIndirectW) { InlineHook(pfnCreateFontIndirectW, MyCreateFontIndirectW, &addrCreateFontIndirectW); }
if (fixGSOFont != DISABLED) {
auto pfnGetStockObject = GetProcAddress(hGdi32, "GetStockObject");
if (pfnGetStockObject) { InlineHook(pfnGetStockObject, MyGetStockObject, &addrGetStockObject); }}
void InlineHook(ptr old, ptr new, ptr_mut_ptr save);
子程序是这么做的:1. 利用 VirtualProtect 修改程序分页权限,给目标地址加写入权限 (EXECUTE_READWRITE),改完后复原
2. 初始化一个代表 jmp 机器指令的 struct,算一下从 new 跳转到 [old 的偏移量 (new - old)加上 5],作为它的参数 IPoff
3. 同样的,saved 的偏移量也是 old+5
4. 复原地址空间(此时 Hook 已经写入),返回。
常量 5 是怎么来的,具体可汇编这个程序
mov edi, edi看看字节长度是多少
push ebp
mov ebp, esp
0x00000000 6689ff mov di, di
0x00000003 6655 push bp
0x00000005 6689e5 mov bp, sp
它的意思是跳到程序逻辑起始开始执行,frame 的 winding 就不需要了(这三条指令是不需要子程序相关信息的)
修改的程序使用 inline assembly
__declspec(naked) HGDIOBJ WINAPI CallOrigGetStockObject(int i)
也是通过此法调用GitHub
GitHub - ysc3839/FontMod: Simple hook tool to change Win32 program font.
Simple hook tool to change Win32 program font. Contribute to ysc3839/FontMod development by creating an account on GitHub.
a2i2a.cpp
1.3 KB
浪费了我很多时间,教会了我,其实有些东西不一定是你开始想的那样,然后是要懂得接受失败, 🙃 #CXX #Algorithm, 这个算法还是要写,不过明天要先纠正下理论的错误
duangsuse::Echo
a2i2a.cpp
那么这件事就告一段落啦 #Algorithm #Haskell #Math #CXX 🤔
module Atoitoa (atoi', itoa') where
import Data.Char (digitToInt)
type Radix = [Char]
type Base = Int
atoi' :: Base -> (String -> Int)
atoi' b = foldl (\ac -> (ac*b +) . digitToInt) 0
itoa' :: Radix -> Base -> (Int -> String)
itoa' rx b = reverse . (itoa'' rx b)
where
itoa'' _ _ 0 = []
itoa'' rx b n = let c = rx !! (n `mod` b) in
c : itoa'' rx b (n `div` b)
#cxx https://tttttt.me/Ralphonograph/3427 再表达一遍:
为什么第二个是 1?因为 signed 的溢出是 UB(undef. behavior) ,编译器数值区间优化,断言常量正正必得正。
摘自 cppreference : unsigned int 计算结果取 2**n_bits 的余(remainder),也就是 max+1=0; 0-1=max
有符数区间溢出据表达方式(如补码)可能回滚或截止(在许多DSP上),甚至可能被编译器无视;在 gcc/clang 上用 -ftrapv 也可引发中断。
int x=INT_MAX;
printf("%d, %d", (x*x), (x*x) >= 0);
//-xxx, 1
为什么第二个是 1?因为 signed 的溢出是 UB(undef. behavior) ,编译器数值区间优化,断言常量正正必得正。
摘自 cppreference : unsigned int 计算结果取 2**n_bits 的余(remainder),也就是 max+1=0; 0-1=max
有符数区间溢出据表达方式(如补码)可能回滚或截止(在许多DSP上),甚至可能被编译器无视;在 gcc/clang 上用 -ftrapv 也可引发中断。
Telegram
Phonograph
今日疑问:为什么输出的第二个值为1?
太长不看版解答:有符号整数溢出为ub,编译器开了优化。(无符号整数溢出不是ub)
以下摘自 cppreference:
无符号整数算术始终进行 modulo 2^n
,其中 n 是该整数的位数。例如对于 unsigned int,向 UINT_MAX 加一得到 0,而从 0 减一得到 UINT_MAX。
有符号整数算术运算溢出(结果类型无法容纳其结果)时,行为未定义:它可能按照表示法的规则(典型为补码)发生回绕,可能在某些平台上或由于编译器选项(例如 GCC…
太长不看版解答:有符号整数溢出为ub,编译器开了优化。(无符号整数溢出不是ub)
以下摘自 cppreference:
无符号整数算术始终进行 modulo 2^n
,其中 n 是该整数的位数。例如对于 unsigned int,向 UINT_MAX 加一得到 0,而从 0 减一得到 UINT_MAX。
有符号整数算术运算溢出(结果类型无法容纳其结果)时,行为未定义:它可能按照表示法的规则(典型为补码)发生回绕,可能在某些平台上或由于编译器选项(例如 GCC…
感觉 Tkinter 和 X 的术语好奇怪啊, (w+h+x+y) 表示 geometry, 一般说的 minimize 也说成 iconify 🤔
不过窗口管理的功能暴露的也不错,看起来怪简洁 C/S 架构允许网络连接Xorg
htop 感觉是挺可定制的,还能调色彩主题, curses 应该没有基本的 layout 计算功能,这能写出来也是牛
#cxx #linux #game SuperTuxKart 现在真的是好棒棒了,也支持联机游戏
看 About 页那两个大佬写了 physics/GUI/networking/AI, SPM file format, Windows/MacOS packaging 什么的好厉害
看到游戏里一个动场景饰物画的 ActionScript 脚本有这样的代码:
估计也只有 Python 和 ActionScript 把严格相等性设计为 (is) ,一般都是 (===)
想想在 #Kotlin 里可以把 op 作为局部 fun ,而且 null 返回就不必单独用 if
不过 {op();return;} 这样的冗余代码就不好解决,只好用 when 和 ?.let {} == true 这样
说到 ActionScript, 我以前以为 MS ASP 的 AS 还是它的缩写呢,其实是 Action Server Page……
不过窗口管理的功能暴露的也不错,看起来怪简洁 C/S 架构允许网络连接Xorg
htop 感觉是挺可定制的,还能调色彩主题, curses 应该没有基本的 layout 计算功能,这能写出来也是牛
#cxx #linux #game SuperTuxKart 现在真的是好棒棒了,也支持联机游戏
看 About 页那两个大佬写了 physics/GUI/networking/AI, SPM file format, Windows/MacOS packaging 什么的好厉害
看到游戏里一个动场景饰物画的 ActionScript 脚本有这样的代码:
void throwBanana(kart-id, lib_id, ) {
TrackObject obj = /**/;
if (obj is null) { op();return; }
MeshObject mesh = /**/;
if (mesh is null ||/**/) { op();return; }
/**/
}
估计也只有 Python 和 ActionScript 把严格相等性设计为 (is) ,一般都是 (===)
想想在 #Kotlin 里可以把 op 作为局部 fun ,而且 null 返回就不必单独用 if
不过 {op();return;} 这样的冗余代码就不好解决,只好用 when 和 ?.let {} == true 这样
说到 ActionScript, 我以前以为 MS ASP 的 AS 还是它的缩写呢,其实是 Action Server Page……