吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 382|回复: 6
收起左侧

[原创] 破解 QCAD for windows

[复制链接]
1550872070 发表于 2026-8-2 14:23
本帖最后由 1550872070 于 2026-8-3 13:39 编辑

之前想用轻量级cad软件,搜索到了qcad,参考论坛
破解 QCAD for Mac
https://www.52pojie.cn/thread-1849172-1-1.html
(出处: 吾爱破解论坛)
思路  处理了26年版本3.32.2  qt5 和qt6版本。特来反馈分享。所有分析参考论坛,由deepseek v4 flash 0731  +ida9.1进行分析。
目前脚本分享在github,需要的自取

https://github.com/binbinsan/Qcad.git

下方是思路分享

QCAD 3.32.9 逆向分析与补丁实战

免责声明: 本文仅用于技术研究和学习目的。所有分析均基于自行购买的正版软件。请支持正版软件,在购买授权后使用。


  1. 前言
  2. 环境准备
  3. QCAD 插件架构分析
  4. 许可验证机制解剖
  5. Qt5 vs Qt6 的关键差异
  6. isTrial 函数定位
  7. 补丁策略设计
  8. checkLicense 函数定位与补丁
  9. checkPluginLicense 函数定位与补丁
  10. 工具开发历程
  11. 性能优化:从 16 秒到 174 毫秒
  12. 常见问题与排错
  13. 总结与致谢

1. 前言

1.1 背景

QCAD 是一款开源的 2D CAD 制图软件,采用 GPL 协议发布。其商业版本 QCAD Professional 和 QCAD/CAM 提供了额外的功能模块,这些模块以 DLL 插件的形式加载,并带有试用期限制。

1.2 版本演进

版本 日期 Qt 版本 插件数量 目标插件数
3.32.2 2026-Q1 Qt 5.13.2 15 9
3.32.3 2026-Q2 Qt 5.13.2 15 9
3.32.9 2026-Q3 Qt 6.11.0 22 13

1.3 分析范围

本文涵盖从 QCAD 3.32.2 到 3.32.9 的完整逆向分析过程,包括:

  • Qt5 和 Qt6 版本的二进制差异分析
  • 插件许可验证机制的逆向工程
  • 跨版本补丁工具的开发与演化
  • 性能优化:从 16 秒到 174 毫秒

2. 环境准备

2.1 工具链

OS:           Windows 11 24H2
Python:       3.14.5
IDA Pro:      9.1 (MCP 远程连接)
QCAD/CAM:    3.32.9 (Qt5 & Qt6 双版本分析)

2.2 分析目标

所有显示"试用版本:激活"的专有插件:

# 插件 ID 文件 说明 自版本
1 DWG qcaddwg.dll DWG 格式支持 3.32.2
2 PDF qcadpdf.dll PDF 导入 3.32.2
3 POLYGON qcadpolygon.dll 多边形布尔运算 3.32.2
4 PROJ qcadproj.dll 地图投影 3.32.2
5 PROTOOLS qcadproscripts.dll 专业版脚本工具 3.32.2
6 PROXIES qcadproxies.dll 代理图形引擎 3.32.2
7 SHP qcadshp.dll Shape 文件导入 3.32.2
8 SPATIALINDEXPRO qcadspatialindexpro.dll 空间索引 3.32.2
9 TRACE qcadtrace.dll 位图矢量化 3.32.2
10 CAM qcadcamscripts.dll CAM 加工脚本 3.32.3
11 NEST qcadnest.dll 排料优化 3.32.3
12 CAVALIER qcadcavalier.dll 多段线偏移 3.32.9 新增
13 TEX qcadtex.dll LaTeX 数学对象 3.32.9 新增

3. QCAD 插件架构分析

3.1 三层架构

QCAD 的插件系统分为三个层级:

GPL (开源免费)
├── DXF (qcaddxf.dll) — DXF 文件导入导出
├── JSAPI (qcadjsapi.dll) — QJSEngine 绑定 (3.32.9 新增)
└── SCRIPTS (qcadscripts.dll) — QCAD 脚本引擎

Professional (专有, 需授权)
├── DWG (qcaddwg.dll) — DWG 格式支持
├── PDF (qcadpdf.dll) — PDF 导入
├── POLYGON (qcadpolygon.dll) — 多边形布尔运算
├── PROJ (qcadproj.dll) — 地图投影
├── PROTOOLS (qcadproscripts.dll) — 专业版工具
├── PROXIES (qcadproxies.dll) — 代理图形引擎
├── SHP (qcadshp.dll) — Shape 文件导入
├── SPATIALINDEXPRO (qcadspatialindexpro.dll) — 空间索引
├── TRACE (qcadtrace.dll) — 位图矢量化
├── CAVALIER (qcadcavalier.dll) — 多段线偏移 (3.32.9 新增)
├── TEX (qcadtex.dll) — LaTeX 数学对象 (3.32.9 新增)
├── TRIANGULATION (qcadtriangulation.dll) — 多边形三角化 (3.32.9 移除)
└── 辅助 DLL: HELP, NESTJSAPI, QCADPROJSAPI, TEXJSAPI

CAM (专有, 需授权)
├── CAM (qcadcamscripts.dll) — CAM 加工脚本
├── GLES3D (qcadgles3d.dll) — OpenGL ES 3D 显示
├── NEST (qcadnest.dll) — 排料优化
└── 辅助 DLL: GLES3DJSAPI

3.2 插件加载流程

QCAD 启动
└─ main() 入口
   └─ RPluginLoader::loadPlugins()
      └─ 遍历 plugins/ 目录加载所有 DLL
         ├─ QPluginLoader::load() — 加载 DLL
         ├─ QPluginLoader::instance() — 获取插件实例
         ├─ qobject_cast<RPluginBase*>() — 转换为基类指针
         └─ RPluginLoader::checkPluginLicense()
            └─ 调用插件自身的 RPluginBase::checkLicense()
               ├─ RPluginBase::checkLicenseFile() — 检查许可证文件
               ├─ RPluginBase::checkLicenseKey() — 检查许可证密钥
               └─ 设置 RPluginBase::isTrial() 的返回值

4. 许可验证机制解剖

4.1 RPluginBase 基类结构

RPluginBase 是所有插件的基类,继承自 QObject。其关键成员变量布局如下:

class RPluginBase : public QObject {
    Q_OBJECT
public:
    // ... 虚函数表 ...
    virtual bool isTrial() const;        // 判断是否试用版
    virtual bool checkLicense() const;   // 检查许可证
    virtual QString checkLicenseFile() const;
    virtual QString checkLicenseKey() const;
    // ...
protected:
    // Qt5 偏移 0x28 处的一个字节标志位
    // Qt6 偏移 0x48 处的一个字节标志位
    bool _isTrial;  // 0 = 已授权, 1 = 试用版
};

4.2 许可证验证链

所有 13 个目标插件共享完全相同的 isTrial() 函数实现。这是通过编译器的虚函数表机制实现的——每个 DLL 编译时都包含了 RPluginBase 的虚函数实现。

4.3 验证流程

QCAD 启动
  │
  ▼
checkLicense() 被调用
  │
  ├─ checkLicenseFile() — 检查 %APPDATA%/QCAD/ 下的许可证文件
  │   ├─ 找到有效许可证文件 → _isTrial = 0 (已授权)
  │   └─ 未找到 → 继续下一步
  │
  ├─ checkLicenseKey() — 检查注册表或配置文件中的许可证密钥
  │   ├─ 密钥有效 → _isTrial = 0 (已授权)
  │   └─ 密钥无效/未找到 → _isTrial = 1 (试用版)
  │
  └─ 最终: isTrial() 返回 _isTrial 的值

5. Qt5 vs Qt6 的关键差异

5.1 核心发现

这是本文最重要的技术发现。QCAD 3.32.9 同时发布了 Qt5 和 Qt6 两个版本,它们的 RPluginBase 对象布局不同:

版本 isTrial 偏移 十六进制特征码 补丁策略
Qt5 3.32.2 [rcx+0x28] 80 79 28 00 0F 94 C0 C3 mov al, 0; nop; ret
Qt5 3.32.9 [rcx+0x28] 80 79 28 00 0F 94 C0 C3 mov al, 0; nop; ret
Qt6 3.32.9 [rcx+0x48] 80 79 48 00 0F 94 C0 C3 xor al, al; ret; nop×5

5.2 原因分析

Qt5 和 Qt6 的 QObject 基类大小不同。Qt6 的 QObject 比 Qt5 增加了约 32 字节,原因包括:

  1. QProperty 系统: Qt6 引入了新的 QProperty 绑定系统
  2. QObjectPrivate 扩展: Qt6 的 QObjectPrivate 增加了新字段
  3. QML 集成改进: 与 QML 引擎的集成更深

这导致 RPluginBase_isTrial 标志位的偏移从 0x28 变为 0x48

5.3 验证方法

通过 IDA Pro 分析 qcadproj.dll 的导出函数 ?isTrial@RPluginBase@@QEAA_NXZ

Qt5 版本 (3.32.2 / 3.32.9):

.text:000000018000B200 80 79 28 00      cmp    byte ptr [rcx+0x28], 0
.text:000000018000B204 0F 94 C0         setz   al        ; al=1 → 试用版
.text:000000018000B207 C3               ret

Qt6 版本 (3.32.9):

.text:000000018000BE00 80 79 48 00      cmp    byte ptr [rcx+0x48], 0
.text:000000018000BE04 0F 94 C0         setz   al        ; al=1 → 试用版
.text:000000018000BE07 C3               ret

5.4 Qt 版本检测方法

工具自动检测 Qt 版本的逻辑非常简单可靠:

def detect_qt(install_dir: Path) -> str:
    if (install_dir / "Qt6Core.dll").exists():
        return "Qt6"
    if (install_dir / "Qt5Core.dll").exists():
        return "Qt5"
    return "?"

6. isTrial 函数定位

6.1 方法一:通过导出表定位

3 个插件将 isTrialcheckLicense 作为 C++ 修饰导出函数:

插件 isTrial 导出 RVA Qt5 偏移 Qt6 偏移
qcadproj.dll 0xBE00 0x28 0x48
qcadspatialindexpro.dll 0x35E0 0x28 0x48
qcadtrace.dll 0x3570 0x28 0x48

导出函数名示例:

?isTrial@RPluginBase@@QEAA_NXZ
?checkLicense@RPluginBase@@QEAA_NXZ

6.2 方法二:通过特征码扫描

所有 13 个目标插件的 isTrial 函数具有完全相同的二进制模式。这意味着我们可以通过特征码扫描一次性定位所有文件,无需逐个分析。

6.3 方法三:IDA Pro MCP 批量分析

使用 IDA Pro 9.1 的 MCP 接口,可以远程批量分析 DLL:

# 通过 MCP 远程分析所有 DLL
for dll in target_dlls:
    # 打开文件到 IDA
    ida.open_file(dll_path)
    # 等待自动分析完成
    # 查找 isTrial 函数
    # 导出反汇编结果
    # 关闭数据库

7. 补丁策略设计

7.1 补丁原理

isTrial() 函数的返回值强制改为 0(非试用),使其始终返回"已授权"状态。

7.2 等长替换原则

这是最重要的工程原则。修改 PE 文件必须保持等长,否则会破坏:

  • 所有后续代码的 RVA 地址偏移
  • 导出表地址指向错误的偏移量
  • 依赖该 DLL 的其他模块(如 qcadprojsapi.dll)加载失败

7.3 Qt5 版补丁 (偏移 0x28)

8 字节 → 8 字节 (等长替换):

原始: 80 79 28 00 0F 94 C0 C3
     cmp byte ptr [rcx+0x28], 0
     setz al              ; al=1 表示试用
     ret

替换: 80 79 28 00 B0 00 90 C3
     cmp byte ptr [rcx+0x28], 0  ; 保留比较指令(不影响结果)
     mov al, 0                    ; 强制返回 0 (非试用)
     nop                          ; 填充至等长
     ret

7.4 Qt6 版补丁 (偏移 0x48)

8 字节 → 8 字节 (等长替换):

原始: 80 79 48 00 0F 94 C0 C3
     cmp byte ptr [rcx+0x48], 0
     setz al              ; al=1 表示试用
     ret

替换: 32 C0 C3 90 90 90 90 90
     xor al, al           ; al = 0 (非试用)
     ret
     nop × 5              ; 填充至等长

7.5 为什么不能缩短字节

惨痛教训: 最初我犯了一个错误——将 8 字节替换为 3 字节 (32 C0 C3)。

后果:

文件缩小 5 字节
  └─ 所有后续代码的 RVA 地址偏移 -5
      └─ 导出表地址全部错位
          └─ qcadprojsapi.dll 加载失败
              └─ "无法加载库" 错误

修复需要:

  1. 从备份恢复所有 13 个文件
  2. 重新设计为等长替换
  3. 重新验证所有导出表完整性

教训: 修改 PE 文件时,永远保持等长替换,除非你完全理解 PE 文件结构并能修复所有重定位表。


7.6 isTrial 补丁的局限性

重要发现: 仅修补 isTrial() 函数不足以彻底解决试用提示问题。

修补 isTrial() 后,QCAD 启动时不会再显示"试用版本"标记,但 15-30 分钟后仍然会弹出试用到期提示。这是因为 QCAD 的许可验证机制有三层:

JavaScript 定时器 (15-30 分钟)
  └─ RPluginLoader::checkPluginLicenses()  (qcadcore.dll)
       └─ RPluginLoader::checkPluginLicense()  (qcadcore.dll)
            └─ RPluginBase::checkLicense()  (每个插件 DLL)
                 ├─ checkLicenseFile() — 检查许可证文件
                 ├─ checkLicenseKey()  — 检查许可证密钥
                 └─ 设置 _isTrial = 1  (重新激活试用模式)

即使 isTrial() 被修补为始终返回 0,checkLicense() 函数仍然会:

  1. 检查许可证文件是否存在且有效
  2. 检查许可证密钥是否有效
  3. 如果两者都无效,设置 _isTrial = 1
  4. 定时器触发时,JavaScript 代码调用 checkPluginLicenses()
  5. 该函数遍历所有插件,调用 checkLicense()
  6. 返回 false 的插件触发试用到期弹窗

因此,需要 三层补丁 才能彻底解决问题:

层级 目标 函数 作用
1 13 个插件 DLL isTrial() 返回 false (非试用)
2 13 个插件 DLL checkLicense() 始终返回 true (已授权)
3 qcadcore.dll checkPluginLicense() 阻止插件引擎检查许可证

8. checkLicense 函数定位与补丁

8.1 问题背景

在修补 isTrial() 后,虽然插件不再显示"试用版本"标记,但 QCAD 内置的 JavaScript 定时器仍然会在 15-30 分钟后触发试用到期弹窗。经过深入分析,发现弹窗的触发路径并不经过 isTrial(),而是通过 checkLicense() 函数。

8.2 验证过程

通过字符串搜索在所有 DLL 中发现了关键线索:

TrialExpiredReason    — 试用到期原因枚举
Trial                 — 试用状态标记
Disabled              — 禁用状态标记
1uninitSlot()         — Qt 定时器槽函数
LicenseFile           — 许可证文件属性
License/LicenseKey    — 许可证密钥属性

所有 13 个目标插件都包含 TrialExpired 字符串和 QTimer 引用,证实了定时器机制的存在。

8.3 checkLicense 函数定位

通过 pefile 库解析 DLL 的导出表,在 3 个插件中找到了 checkLicense 的导出 RVA:

插件 checkLicense RVA checkLicenseFile RVA checkLicenseKey RVA isTrial RVA
qcadproj.dll 0xB0D0 0xB1E0 0xB7B0 0xBE00
qcadspatialindexpro.dll 0x28B0 0x29C0 0x2F90 0x35E0
qcadtrace.dll 0x2840 0x2950 0x2F20 0x3570

导出函数名(C++ 修饰名):

?checkLicense@RPluginBase@@UEAA_NXZ     — virtual bool checkLicense()
?checkLicenseFile@RPluginBase@@QEAA_NXZ  — bool checkLicenseFile()
?checkLicenseKey@RPluginBase@@QEAA_NXZ   — bool checkLicenseKey()
?isTrial@RPluginBase@@QEAA_NXZ           — bool isTrial()

注意:@UEAA 表示这是一个虚函数(U = virtual),@QEAA 表示非虚函数。

8.4 函数特征码分析

读取 qcadproj.dllcheckLicense 函数的代码(RVA 0xB0D0):

; checkLicense 函数前导 (12 字节)
48 89 5C 24 10    mov [rsp+0x10], rbx      ; 保存 rbx
57                push rdi                  ; 保存 rdi
48 81 EC A0 00 00 00  sub rsp, 0xA0         ; 分配 160 字节栈空间
48 8B 05 ...      mov rax, [rip+...]        ; 加载安全 Cookie
48 33 C4          xor rax, rsp              ; XOR 与栈指针
48 89 84 24 98 00 00 00  mov [rsp+0x98], rax ; 保存 Cookie
48 8B D9          mov rbx, rcx              ; 保存 this 指针
48 8D 15 ...      lea rdx, [rip+...]        ; 加载字符串引用
48 8D 4C 24 40    lea rcx, [rsp+0x40]       ; 结果字符串缓冲区
FF 15 ...         call [rip+...]            ; 调用 checkLicenseFile()

该函数具有以下特征:

  • 安全 Cookie (GS): 函数使用 /GS 编译保护,栈上保存了安全 Cookie
  • 虚函数调用: 通过 call [rip+...] 间接调用 checkLicenseFile()checkLicenseKey()
  • QString 返回值: 使用栈上缓冲区作为 QString 的返回参数
  • 函数较长: 约 150+ 字节,远长于 isTrial 的 8 字节

8.5 在所有 DLL 中定位 checkLicense

通过搜索函数前导特征码 48 89 5C 24 10 57 48 81 EC A0 00 00 00,在所有 13 个目标 DLL 中找到了 checkLicense 函数:

DLL 文件偏移 上下文
qcaddwg.dll 0x55A0 有安全 Cookie + 虚函数调用
qcadpdf.dll 0x1D80 同上
qcadpolygon.dll 0xCC0 同上
qcadproj.dll 0xA4D0 同上
qcadproscripts.dll 0x22E0 同上
qcadproxies.dll 0xFA0 同上
qcadshp.dll 0x1050 同上
qcadspatialindexpro.dll 0x1CB0 同上
qcadtrace.dll 0x1C40 同上
qcadcavalier.dll 0xCC0 同上
qcadtex.dll 0x34300 同上
qcadcamscripts.dll 0x1CC0 同上
qcadnest.dll 0x44A00 同上

所有 DLL 中的 checkLicense 函数代码完全相同,因为它们都编译了同一个 RPluginBase::checkLicense() 实现。

8.6 checkLicense 补丁方案

checkLicense 函数的前 8 字节替换为 B0 01 C3 90 90 90 90 90

原始: 48 89 5C 24 10 57 48 81    (8 字节)
      mov [rsp+0x10], rbx
      push rdi
      sub rsp, 0xA0  (开始)

替换: B0 01 C3 90 90 90 90 90    (8 字节, 等长)
      mov al, 1          ; 始终返回 true (已授权)
      ret                ; 立即返回
      nop x 5            ; 填充至等长

为什么可以安全替换? 函数在 sub rsp, 0xA0 之前就返回,栈指针尚未被修改,ret 指令能正确弹出返回地址。安全 Cookie 检查被跳过,但由于函数未修改栈,不存在栈溢出风险。


9. checkPluginLicense 函数定位与补丁

9.1 函数定位

checkPluginLicenseRPluginLoader 类的静态方法,位于 qcadcore.dll 中。它是插件引擎检查许可证的入口点,被 JavaScript 代码调用。

通过 pefile 解析导出表找到:

qcadcore.dll:
  ?checkPluginLicense@RPluginLoader@@SA_NPEAVQObject@@@Z  @ 0xF2C10
  ?checkPluginLicenses@RPluginLoader@@SA_NXZ              @ 0xF2C40
  ?loadPlugin@RPluginLoader@@SAXPEAVQObject@@_NAEBVQString@@2@Z
  ?loadPlugins@RPluginLoader@@SAX_N@Z

9.2 函数代码分析

读取 checkPluginLicense 函数的代码(RVA 0xF2C10):

48 83 EC 28       sub rsp, 0x28           ; 分配栈空间
48 85 C9          test rcx, rcx           ; 检查插件指针是否为空
74 20             jz short return_true    ; 为空则返回 true
48 8B 01          mov rax, [rcx]          ; 加载 vtable 指针
48 8D 15 ...      lea rdx, [rip+...]      ; 加载 RPluginBase 类名
FF 50 08          call [rax+0x08]         ; 调用 qobject_cast (vtable+0x08)
48 8B C8          mov rcx, rax            ; 转型结果
48 85 C0          test rax, rax           ; 检查转型是否成功
74 0B             jz short return_true    ; 失败则返回 true
48 8B 00          mov rax, [rax]          ; 加载 vtable
48 83 C4 28       add rsp, 0x28           ; 平衡栈
48 FF 60 38       jmp [rax+0x38]          ; 尾调用 checkLicense() (vtable+0x38)
return_true:
B0 01             mov al, 1               ; 返回 true
48 83 C4 28       add rsp, 0x28           ; 平衡栈
C3                ret

关键发现: checkLicense() 虚函数位于 vtable 偏移 0x38 处。

函数流程:

checkPluginLicense(QObject* plugin)
  ├─ plugin == null → return true
  ├─ qobject_cast<RPluginBase*>(plugin)
  │    ├─ 转型失败 → return true
  │    └─ 转型成功 → tail call checkLicense() via vtable[0x38]
  └─ checkLicense() 返回布尔值

9.3 checkPluginLicense 补丁方案

checkPluginLicense 函数的前 8 字节替换为 B0 01 C3 90 90 90 90 90

原始: 48 83 EC 28 48 85 C9 74 20    (8 字节)
      sub rsp, 0x28
      test rcx, rcx
      jz short +0x20

替换: B0 01 C3 90 90 90 90 90        (8 字节, 等长)
      mov al, 1       ; 始终返回 true
      ret             ; 立即返回
      nop x 5         ; 填充至等长

为什么可以安全替换? 函数在 sub rsp, 0x28 之前就返回,栈指针未被修改。ret 指令正确弹出返回地址。


10. 工具开发历程

13.1 版本时间线

v1.0 — 初版 (3.32.2)
  │ 基于 52pojie 的 macOS 分析思路
  │ 手动 IDA Pro 分析 Windows 3.32.2
  │ 找到 isTrial @ 0x28
  │
  ├─ v2.0 — 3.32.9 适配
  │  QCAD 升级到 3.32.9, Qt5→Qt6
  │  所有偏移量失效, 重新分析
  │  isTrial 从 0x28 变为 0x48
  │
  ├─ v3.0 — 灾难性错误
  │  8→3 字节替换, 导出表损坏
  │  qcadprojsapi.dll 加载失败
  │  13 个文件全部恢复备份
  │
  ├─ v4.0 — 等长替换
  │  修复为 8→8 字节等长替换
  │  导出表完整保留
  │  支持 3.32.2 / 3.32.9 双版本
  │
  ├─ v5.0 — Qt5/Qt6 双版本
  │  发现 Qt5/Qt6 偏移不同
  │  增加 Qt 版本自动检测
  │
  ├─ v5.2 — GUI 工具
  │  增加 tkinter 图形界面
  │  支持扫描、修补、恢复、校验
  │
  ├─ v5.3 — 性能优化
  │
  └─ v5.4 — 三层补丁 (当前)
     发现 isTrial 补丁不足
     新增 checkLicense + checkPluginLicense 补丁
     三步彻底解决试用提示
     扫描速度从 16 秒 → 174 毫秒
     移除 pefile 依赖
     92 倍性能提升

13.2 工具架构

patch_qcad.py (CLI)              qcad_patch_gui.py (GUI)
┌──────────────────────┐        ┌──────────────────────┐
│  argparse 命令行解析   │        │  tkinter 图形界面     │
│  ├─ info              │        │  ├─ 目录选择          │
│  ├─ scan              │        │  ├─ 文件列表          │
│  ├─ patch             │        │  ├─ 扫描/修补/恢复    │
│  └─ restore           │        │  └─ 详细状态显示      │
├──────────────────────┤        ├──────────────────────┤
│ 核心引擎 (共享)        │◄───────│ 核心引擎 (共享)        │
│  ├─ detect_qt()       │        │  ├─ detect_qt()       │
│  ├─ get_patterns()    │        │  ├─ get_patterns()    │
│  ├─ scan_file()       │        │  ├─ scan_directory()  │
│  └─ apply_patches()   │        │  └─ apply_patches()   │
└──────────────────────┘        └──────────────────────┘

13.3 核心代码 (CLI 版本)

# Qt5 特征码: isTrial @ [rcx+0x28]
P_QT5 = bytes.fromhex("80 79 28 00 0F 94 C0 C3")
R_QT5 = bytes.fromhex("80 79 28 00 B0 00 90 C3")

# Qt6 特征码: isTrial @ [rcx+0x48]
P_QT6 = bytes.fromhex("80 79 48 00 0F 94 C0 C3")
R_QT6 = bytes.fromhex("32 C0 C3 90 90 90 90 90")

def detect_qt(install_dir: Path) -> str:
    if (install_dir / "Qt6Core.dll").exists(): return "Qt6"
    if (install_dir / "Qt5Core.dll").exists(): return "Qt5"
    return "?"

def scan_file(data: bytes, qt_ver: str) -> list[tuple]:
    """快速扫描:只搜索与 Qt 版本匹配的模式"""
    results = []
    for find_pat, repl_pat, label in get_patterns(qt_ver):
        offs = []
        s = 0
        while True:
            o = data.find(find_pat, s)
            if o < 0: break
            offs.append(o)
            s = o + 1
        patched = data.find(repl_pat) >= 0
        if offs or patched:
            results.append((label, offs, patched, find_pat, repl_pat))
    return results

免费评分

参与人数 1吾爱币 +1 收起 理由
sk7523 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

doglove 发表于 2026-8-3 15:59
好帖子,支持,机械专业看不懂。
YooD 发表于 2026-8-4 08:53
lvgcom 发表于 2026-8-4 09:47
mhfymn 发表于 2026-8-4 09:54
方法很棒,运行成功。不知道是我电脑的原因还是感觉Qcad运行卡顿,没有CAD快速看图效果快,如果博主能做一个CAD快速看图就更完美了
afms 发表于 2026-8-5 15:26
感谢分享
o0dp0o 发表于 2026-8-5 15:41
感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-8-6 09:10

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表