吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2171|回复: 0
收起左侧

[Windows] 【python】文件CRC32C多线程校验工具(v1.0)

[复制链接]
不谙世事的雨滴 发表于 2024-5-19 19:20
本帖最后由 不谙世事的雨滴 于 2024-7-5 22:04 编辑

这是一款由我用python编写,通过在分享前获取文件CRC32C校验值并记录在文件中,和在接受文件之后根据记录文件比对文件CRC32C的方式,方便文件分享者和接收者确认数据完整性、正确性的工具。


注意!!
软件为了利用CPU中SSE4.2指令集的CRC32指令提升计算效率,降低CPU占用,因此用到的python库crc32c及其使用的CRC32C标准,和常用的CRC32(B)标准相比,也就是大多数压缩软件和校验程序提供的CRC32,有很大的区别,两者不能通用,也就是不能用常规的计算CRC32的方法来校验本程序生成的CRC32C记录,所以建议无论是分享前记录CRC的过程,还是接收到文件之后的比对,都不要离开本软件


关于crc32的CPU指令的详细介绍,参见下方这篇文章:
【基于Intel SSE4.2指令集crc32指令进行快速crc32c校验(使用Delphi/Fpc内联汇编实现)】
https://www.52pojie.cn/thread-1584310-1-1.html
(出处: 吾爱破解论坛)


软件(和源码)链接:
①百度云:(拥有完整资料,包括软件自身、说明文本、操作演示视频背景音乐欣赏,以及视频中出现的音乐文件分享https://pan.baidu.com/s/1iBlKi5hV_kdkkzCgVjblYQ?pwd=0000
②蓝奏云:(因文件大小100M限制,只有软件自身、源码和一个说明用的txt文本文件,以及导航用的、存有百度云链接的文本文件)https://wwm.lanzouq.com/b0ny5lvuf
没有百度云VIP账号,想要看操作演示的(或者只是想随便听听音乐打发时间的),请访问下面这个链接,到B站收看:https://www.bilibili.com/video/BV18rupeWEt7


软件github地址:https://github.com/ssh-buanshish ... 2c_file_verify_tool


工具基于python的wmi模块获取文件路径所在磁盘的数字编号,然后基于磁盘SMART工具“smartctl.exe"(pySMART模块调用的exe程序)根据前面获取到的磁盘数字编号,获取对应磁盘的旋转速度、接口类型、是否为SSD的SMART属性,以此判断磁盘硬件类型(固态/机械),并合理分配同时读取的文件数(线程数)。
①在机械硬盘(HDD)上以单线程读取文件(逐个读取文件),降低机械盘寻道压力的同时,读取、计算校验的速度也能赶上机械盘自身的连续读取大文件的速度,这种情况下和7-zip读取校验CRC的速度不相上下;
②而在nvme和SSD上以4线程读取文件(同时读取4个文件),在获取一个文件夹下的多个文件(递归)CRC值的场景下,相较于7-zip和已有的大部分校验软件,速度上应该会有显著提升,可以加快校验速度,节省时间。在我自己电脑的nvme固态盘上测试,读取速度相较原来翻了一倍。4个线程也是我多次测试所得最优线程数。
③在不能读取识别明确硬件类型的磁盘上(U盘、虚拟磁盘……),以2线程读取文件(同时读取2个文件);
④识别磁盘硬件类型出错的情况下,以单线程运行。




软件运行截图:

运行截图

运行截图





OK,重要的交代完了。接下来,愿意听我继续哔哔的请留下
===================================================


故事要从一周前开始说起,那天我心情好,决定限一天内,分享些懂的都懂的资源。资源的大小很大,超过100G,我用了7-zip分卷并加密,然后上传网盘。为了预防下载时数据出错,我特意把每个分卷压缩包的CRC32都跑了一遍,并记录在一个txt里面。果然有两三人解压失败了,不过当我让他们跑一遍CRC32比对一下时,出乎意料的是他们都对这个基本操作一无所知,无奈只能亲自给他们发操作步骤。想不到都2024年了,还有人不会校验文件,那就给他们弄个傻瓜式的自动化校验工具吧,这样只要运行一个exe就能自动校验文件夹下(包含递归)的所有文件,并显示出有问题的那几个。于是脑子一热,说干就干。 本以为用python写个计算CRC32的函数会很容易,毕竟之前搞的“Peazip文件密钥加密模式 - 转 - 明文密码(v1.0)”就用到了sha512的计算,谁知hashlib里面居然没有专门计算crc32的函数,还得跑到zlib这个库那边去。一搜zlib使用crc32函数的资料,当场崩溃,搜到的几乎全是类似“crc32(f.read())”这样一股脑把文件整个吞进去的,丝毫没考虑到电脑内存的大小,如果直接copy下来的话,遇到大文件那不得分分钟报错?还好之后我加了“特大文件”这个关键词,搜出一个靠谱的分批缓冲读取的,计算出的CRC32和7-zip一样,总算是能用了。搜到的原本的代码中的循环好像记得是用“for line in file:”写的,其中file似乎是以“rb”模式打开的文件的句柄,也有可能我记错了,当时不知道这个line有什么特别的意思,网上搜了搜,只找到一个说能自适应读取单元和缓冲什么的,搜到的结果最多两三条,要么是文不对题,要么和这条解释相似,个人感觉似乎不太行,于是按照自己的理解改了下,顺便作为模板放在下面,也好让后面的人少踩点坑。


[Python] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# -*- coding: UTF-8 -*-
import os,sys
from zlib import crc32
 
# 传入文件路径,返回CRC32字符串
def calc(task_file: str) -> str:
    # 初始化结果变量为0
    result = 0
    try:
        file_handle = open(task_file, "rb")
        # 只要文件没读完,就不断迭代结果自身。
        #   一次read的字节大小理论上可以随便选,因为大部分文件的大小不是一次读取的大小的整数倍,总会多出或少掉几个字节,
        #   而且看网上资料说,这个函数还可以传入字符串,字符串的长度就更随意了,所以可以放心一次读取的长度应该不会影响到最终结果。
        #  
        #   取“1048576(1 MiB)”是出于性能考虑,我使用ATTO测试过,我的nvme盘在传输大小(块大小)为1 MiB到2 MiB时,
        #   未勾选“直接传输”的读取速率差不多达到最大,估计其他固态和机械盘应该也差不多
        #   “直接传输”据我估计是带上系统自身调度和缓冲特性的操作,和勾上后用到的底层操作,也就是硬件层面类似磁盘碎片整理软件里看到的“directwrite”操作正好相对。
        #   python读取文件的函数到不了底层,所以得去掉这个勾。
        #   恰好这个数字和1024关系很直接,感觉非常不错,所以就定为了“1048576(1 MiB)”
        #  
        #   突然想到固态盘里,闪存单元的块大小是不是也是这么大?不过感觉应该要比1 MiB小吧。
        #
        while buf := file_handle.read(1048576): # 1024 × 1024 = 1048576(1 MiB)
            result = crc32(buf,result)
        file_handle.close()
    except:
        try:
            file_handle.close()
        except:
            pass
 
    # “&”这部分的意思是:取结果低位的4个byte(0xFF)转换成hex字符串,以8个“0-F”的方式显示(“:08X”那部分代表的意思)
    result = f'{result & 0xFFFFFFFF:08X}'
     
    return result
 
# 将要计算CRC32值的文件路径替换掉下面这个样本
file = "[drive]:/path/to/file"
print(f"{file}的CRC32:{calc(file)}\n")
os.system("@pause>nul")
sys.exit(0)



解决完了怎么计算的问题,我又想到现在的校验软件差不多都是逐个读取和校验文件的,要不我试试多线程?要是能加快总体读取速度就好了。果然这一步走对了,在我的nvme固态上实现了整体读取速度翻番的效果,这下能省差不多一半的时间,有成就感了。不过多线程带来好处的同时,CPU的占用和发热多少有点大了,而且也为后面制作进度条埋了雷。


我原本的设想是多个文件的进度条同时显示出来,不过借鉴了大半天,结果测试时显示的进度条全混在一起分都分不清,而且还报错,大概意思是不能同时什么什么的……罢了,还是自己写一个显示进度条的函数吧,就用基础的print()和清屏的循环,好歹能自己有点控制能力,人家的库虽然美观、显示效果不错,不过一到了我这个场景下就歇菜了,当然如果深挖的话问题还是有可能解决的,不过我已经累了。总之,换成自己写的进度条后,总算能正常显示了,不过有个小问题,就是经常不定期闪烁,本来我想忽略不管的,但我一个程序员大佬朋友觉得这太晃眼了,受不了,于是我又去研究了下。

其实在问题出现的时候,我已经猜到了“os.system("cls")”这条里面多少有点猫腻,在我的理解里,这个过程的耗时会比Python自己的命令长,毕竟涉及到调用系统命令,而且自己以前运行批处理时也似乎记得cls再echo会出现闪烁,不过似乎较难察觉,又或许是我记错了。总之得想办法找到效果相同的方法替换掉它。首先搜到的是打印ANSI控制字符“\r”,不过只能回退一行,清掉整个屏幕的控制字符就难找了,毕竟这些控制字符估计都属于DOS时代的东西了(比如让蜂鸣器响的控制字符),除了换行什么的一直在用,其他特殊的找起来比较难。好在搜了半天,总算找到一个用“\033c”的:https://blog.csdn.net/Qiuml0703/article/details/131778995,果然一用上这个“\033c”,问题迎刃而解,甚至不用单独写一行清屏,只要把这个字符放在所有字符的前面,上一轮打印的字符就会自动清掉。在找资料的过程中,还认识了“sys.stdout.flush()”和“sys.stdout.write()”,前者用来刷新输出缓冲区,把还暂时停留在控制台缓冲区里的东西立刻打印出来,后者则可以说完全可以取代print(),据网上资料所说,print()就是调用的“sys.stdout.write()”顺便加了点自己的特性进去,比如自动换行。凭我的感觉和以前学C语言的记忆,觉得sys.stdout.write()是更底层的东西,而且刚好和“sys.stdout.flush()”是一对的,于是把打印进度条函数里的print()改成了这个,这样一来应该能提高一点“强迫症”性能吧,哈哈。现在看来,虽然90%以上的情况下,人们都是用的“os.system("cls")”来清屏,不过在某些特定场景下,比如我这个,就得做出调整了,“\033c”无疑就是个很好的方法和例子

又经过一段时间的优化,进度条的显示也让我满意了,想来应该可以分享出去了。不过我又冷不丁想到,万一是在机械硬盘上运行,多线程还能提高速度吗?搜索之前我心里已经有答案了。果然搜过之后和我想的一样,因为只有一个磁头,多线程在机械硬盘上完全是拖后腿的操作,不仅如此,我觉得还是给机械硬盘折寿的存在,毕竟迅雷的多线程写入毁掉多少盘(当然大部分是叠瓦的,不过垂直的也危险,只有企业级的可能还比较抗造点)是有目共睹的,虽然我这个还只是读取,不过脑海里已经能预想到我的多线程读取程序在机械盘上启动,然后不停地“炒豆子”,用户心中顿时跑过一万匹xxx,想要把我削了的场景了

于是又马不停蹄地投入了磁盘硬件类型识别的研究中。我的思路是:首先要通过文件路径获得所在的分区的盘符,然后通过盘符确定物理磁盘的编号(系统磁盘管理里面显示的磁盘号),最后根据物理磁盘的编号,通过某种识别软件或库识别出硬盘的硬件类型。第一步“通过文件路径获得所在的分区的盘符”简单,直接现成的“section= os.path.splitdrive(os.getcwd())[0]”一步到位;第二步难度就直接快升天了,费了老半天才找到用wmi库获得电脑磁盘号和分区列表的代码,总算接近了些,接下来就是在idle里各种print(),找出自己想要的,可算是完成了第二步的从分区到磁盘号的映射;到了第三步,能找到的可用的方法就快几乎没有了,一开始不管怎么搜都搜不到怎么用python判断是否为固态或者机械,只找到一个用powershell的方法,命令是“Get-PhysicalDisk| Select-Object FriendlyName, MediaType”,不过当我把“FriendlyName”改为单独用“Get-PhysicalDisk”命令显示出的第一列的“Number”,也就是我想要的内容,具体命令是“Get-PhysicalDisk| Select-Object Number, MediaType”,再敲回车时,Number那一列居然诡异地消失了:

powershell截图

powershell截图



我心里顿时“Oh GodPlease No! No!! No!!……”,怎会出现如此离谱的bug,算了,还是弃用powershell吧,powershell光是启动完成就要老半天,虽然Python有popen()和subprocess()以及各种传输管道的支持,理论上是能捕获到,不过看上面这样子,变数太大了,传输管道什么的还得查资料,自己根本不会,到最后可能还是一场空。可没有了powershell,其他的方法似乎更离谱了,打开磁盘碎片整理程序查看这个虽然有道理,可是程序运行过程中还要跳一个磁盘碎片整理的窗口出来捕获文本,多少有点更离谱了……

直接的办法不太行,试试间接的如何?不少磁盘都支持SMART……诶,我怎么没想到用获取SMART这个方法,这个灵感来得太及时了!换了方向之后,一切就豁然开朗了,找到了一个pySMART的python模块,虽然说明文件少得可怜,而且还依赖软件“smartmontools”以及解压后环境变量里设置的smartctl.exe的路径,不过好歹没之前用powershell测试时那么迷茫了,经过一顿寻找和尝试,找到了“handle.rotation_rate, handle.interface , handle.is_ssd”这三个能间接判断磁盘硬件类型的关键方法,于是就得到了上述三步一并实现的函数的代码,如下:
[Python] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import os,sys,time,wmi
from pySMART import Device
 
# 线程数:同时最多有几个文件在读取和计算CRC32
#  固态
thread_num_for_ssd = 4
#  机械
thread_num_for_hdd = 1
#  其他未知(可能为U盘)
thread_num_for_other = 2
#  获取失败时的线程数
thread_num_for_error = 1
 
def allocate_thread_num_by_disk_hardware_type() -> tuple:
    # 获取硬盘分区和磁盘号的关联
    c = wmi.WMI()
    section_to_physical_disk_dict = {}
    for physical_disk in c.Win32_DiskDrive():
        for partition in physical_disk.associators("Win32_DiskDriveToDiskPartition"):
            for logical_disk in partition.associators("Win32_LogicalDiskToPartition"):
                #print("|".join([physical_disk.Caption, partition.Caption, logical_disk.Caption]))
                section_to_physical_disk_dict[logical_disk.DeviceID] = physical_disk.Index
     
    # 获取程序所在目录的盘符,寻找对应的磁盘号
    section = os.path.splitdrive(os.getcwd())[0]
    print(f"所在盘符为:【{section}】")
 
    # 获取所在的磁盘号
    disk_index = section_to_physical_disk_dict.get(section)
    print(f"磁盘号为:【{disk_index}】")
    print("\n")
 
    if disk_index != None:
        handle = Device(f'/dev/pd{disk_index}')
        spin , slot , is_SSD = handle.rotation_rate , handle.interface , handle.is_ssd
        print(f"旋转速度:{spin}")
        print(f"接口:【{slot}】")
        print(f"是否为SSD:{is_SSD}")
        print("\n")
        if spin:
            print("硬件类型:机械硬盘")
            disk_type = "HDD"
            allocated_thread_num = thread_num_for_hdd
        elif (slot == "nvme") or is_SSD :
            print("硬件类型:固态硬盘")
            disk_type = "SSD"
            allocated_thread_num = thread_num_for_ssd
        else:
            print("硬件类型:未知,无旋转速度,可能为U盘")
            disk_type = "other"
            allocated_thread_num = thread_num_for_other
    else:
        print("获取磁盘号失败")
        disk_type = "error"
        allocated_thread_num = thread_num_for_error
     
    return (allocated_thread_num , disk_type)
 
thread_num , disk_hardware_type = allocate_thread_num_by_disk_hardware_type()
os.system("pause")
sys.exit(0)



本以为有了代码之后,接下来只要运行就能得出结果了,不过我还是疏忽了。就在我朋友的电脑上测试的时候,发现原本应该得出的是机械盘的结果,但“handle.rotation_rate, handle.interface , handle.is_ssd”这3个值都是None或者False,一开始我以为是软件也有没囊括到的硬盘型号,但无意间想到“smartctl.exe”的工具说明里提到了要获得管理员权限,才能保证获得准确的信息。于是我让朋友又用右键的“管理员权限”运行了一下,果然该获取到的信息又都获取到了,也被正确识别为机械硬盘了。于是赶紧做了个新的C语言启动器替换了上去,代码如下:

[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#pragma comment(linker, "/subsystem:windows /entry:mainCRTStartup")
//编译不弹黑窗的选项
 
#include <string.h>
#include <direct.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winuser.h>
#include <tchar.h>
 
 
int main(void)
{
    //进入到当前路径下的“bin”文件夹
        _chdir("bin");//一定要先进入到文件夹(设定程序的工作文件夹)再启动exe,不然窗口左上角的图标显示不出来,还有可能会有其他意想不到的问题
     
        SHELLEXECUTEINFO sei;
    ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO));//使用前最好清空
    sei.cbSize = sizeof(SHELLEXECUTEINFO);//管理员权限执行,最基本的使用与 ShellExecute 类似
    sei.lpVerb = _T("runas");
    sei.lpFile = _T("校验CRC32.exe");
    sei.nShow = SW_SHOWNORMAL;
    sei.fMask = SEE_MASK_FLAG_NO_UI;//出现错误,不在函数执行中显示错误消息框,比如不会弹出找不到文件之类的窗口,直接返回失败。防止在后面重复弹出错误消息。
     
        if (!ShellExecuteEx(&sei))
    {
        DWORD dwStatus = GetLastError();
         
                if (dwStatus == ERROR_CANCELLED)
        {
            //printf("提升权限被用户拒绝\n");
            MessageBox(
                                NULL,
                                (LPCTSTR)"提升权限被用户拒绝",//文本
                                (LPCTSTR)"starter",//标题
                                MB_OK | MB_ICONERROR | MB_TOPMOST | MB_SETFOREGROUND //“确定”按钮、“错误”图标、置顶、前台
                        );
        }
        else if (dwStatus == ERROR_FILE_NOT_FOUND)
        {
            //printf("所要执行的文件没有找到\n");
            MessageBox(
                                NULL,
                                (LPCTSTR)"所要执行的文件没有找到",//文本
                                (LPCTSTR)"starter",//标题
                                MB_OK | MB_ICONERROR | MB_TOPMOST | MB_SETFOREGROUND //“确定”按钮、“错误”图标、置顶、前台
                        );
        }
         
    }
 
    return 0;
}



之所以单独做个启动器,不仅是因为要提升为管理员权限运行以确保获取准确的硬件信息,而且也是为了用户能少进一层文件夹,毕竟“smartctl.exe”等“smartmontools”的软件组件文件,需要和打包好的python程序放在同一个文件夹下面,才能确保程序启动时pySMART能调用到,才能获取到磁盘SMART信息。


能做到上述几点已是不易,该想到的问题差不多都想到了,故事照理来说应该就快结束了吧?不过后面又多出了一段相当重要的部分,而且是在我录好演示视频,准备传到论坛上去的时候。在我准备这条帖子,搜索有没有类似的帖子时,看到了下面这条帖子:
【基于Intel SSE4.2指令集crc32指令进行快速crc32c校验(使用Delphi/Fpc内联汇编实现)】
https://www.52pojie.cn/thread-1584310-1-1.html
(出处: 吾爱破解论坛)


当时看完后心想,这也太符合我现在的需要了吧,要是能套上汇编或者C的话,计算的效率不得蹭蹭地往上去啊!可惜自己只学过51单片机的汇编,C语言也才入门,而且也不懂怎么在python里嵌入汇编和C,虽说Python理论上好像确实有这个功能,但是如果嵌入的话,以自己现在的水平,怕是一行汇编也看不懂,更别提上手了,毕竟多少得做些修改,自己又不懂,这套操作还是太高端了。当时时间不早了,于是就去睡了。 第二天醒来时又想到这个,看来自己还真是念念不忘啊,那就去pypi看看有没有现成的“宝藏”库吧。不得不说运气真就是好,找到个叫“crc32c”的库:https://pypi.org/project/crc32c/。一看里面的介绍,就是用这个处理器指令加速的,如果处理器不支持指令加速的话,库还自动换为软件算法打底。那还等什么,赶紧安排一波测试。测试结果让人喜出望外,在nvme上以设计的4线程并行读取计算的速度比原来似乎稍快的同时,CPU的占用也从原来软媒雷达显示的40%左右,任务管理器显示的75%直接降到了——现在软媒雷达显示的20%左右,和任务管理器显示的37%~40%左右(i7-9750H,6核心12线程),不过就是输出的CRC32C和通用的CRC32B不一样罢了,反正都是在软件里“闭环”操作,软件自身直接双击启动自动记录 + 自动校验的设计也很人性化,应该不会有人傻到再用别的软件根据文本记录一个个自己校验吧?所以也就不考虑输出的CRC32的兼容性了,毕竟用上硬件加速后,得到的提升非常诱人。

理论上只要支持SSE4.2就能使用CRC32硬件加速,网上说AMD的3、5、7代Ryzen都支持SSE4.2,但因为库的介绍里只写了intel,我有点不确定amd的处理器是否也支持,于是找了身边的同学帮忙测试了下,发现R7 6800H也支持,看来兼容性应该不用太担心,只要不是老的处理器,估计都支持这个硬件加速。软件启动后,也会在标题那里显示是否开启了硬件加速。下面是现在使用的,CRC32_C的计算函数的代码模板:
[Python] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# -*- coding: UTF-8 -*-
import os,sys
import crc32c
 
# 传入文件路径,返回10位CRC32_C数字字符串
def calc(task_file: str) -> str:
    # 初始化结果变量为0
    result = 0
    try:
        file_handle = open(task_file, "rb")
        # 只要文件没读完,就不断迭代结果自身。
        #   一次read的字节大小理论上可以随便选,因为大部分文件的大小不是一次读取的大小的整数倍,总会多出或少掉几个字节,
        #   而且看网上资料说,这个函数还可以传入字符串,字符串的长度就更随意了,所以可以放心一次读取的长度应该不会影响到最终结果。
        #  
        #   取“1048576(1 MiB)”是出于性能考虑,我使用ATTO测试过,我的nvme盘在传输大小(块大小)为1 MiB到2 MiB时,
        #   未勾选“直接传输”的读取速率差不多达到最大,估计其他固态和机械盘应该也差不多
        #   “直接传输”据我估计是带上系统自身调度和缓冲特性的操作,和勾上后用到的底层操作,也就是硬件层面类似磁盘碎片整理软件里看到的“directwrite”操作正好相对。
        #   python读取文件的函数到不了底层,所以得去掉这个勾。
        #   恰好这个数字和1024关系很直接,感觉非常不错,所以就定为了“1048576(1 MiB)”
        #  
        #  
        #
        while buf := file_handle.read(1048576): # 1024 × 1024 = 1048576(1 MiB)
            result = crc32c.crc32c(buf,result)
        file_handle.close()
    except:
        try:
            file_handle.close()
        except:
            pass
 
     
    result = str(result).zfill(10)
     
    return result
 
# 将要计算CRC32_C值的文件路径替换掉下面这个样本
file = "[drive]:/path/to/file"
print(f"{file}的CRC32_C:{calc(file)}\n")
os.system("pause")
sys.exit(0)



由于这个新的计算函数的改动之前根本没预料到,所以演示视频里用来演示的还是之前那一版通用CRC32(B)的版本,截图也是从那个演示视频里截来的,不过操作上没什么差别,我也不打算改视频和截图了。


现在回过头来,发现自己长篇大论似乎有近千字了,感谢各位能看到现在


也感谢自己的坚持,完成了这个成就感满满的工具软件。 软件的源码就包括在网盘链接里,后面有建议、发现bug的欢迎随时来找我

免费评分

参与人数 3吾爱币 +5 热心值 +2 收起 理由
ZengHugh + 1 + 1 我很赞同!
gknight + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
onlyclxy + 3 鼓励转贴优秀软件安全工具和文档!

查看全部评分

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

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2025-5-28 03:39

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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