当前位置: 首页 > article >正文

二十六.签名与脚本(1)--脚本介绍

1.区块链脚本介绍在之前的章节中我们了解了签名与验证相关但是btc的交易数据签名和验证不是单纯的还有脚本深度参与其中。我们从开始来bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx wtxNew)发送coin给别人scriptPubKey参数就不是简单的账户公钥数据是个CScript脚本类型带有公钥数据。2.scriptPubKey构造我们来看一下之前这个scriptPubKey是怎么构造的得到了接收方的地址后void CSendDialog::OnButtonSend(wxCommandEvent event) { CWalletTx wtx; string strAddress (string)m_textCtrlAddress-GetValue(); // Parse amount int64 nValue 0; if (!ParseMoney(m_textCtrlAmount-GetValue(), nValue) || nValue 0) { wxMessageBox(Error in amount ); return; } if (nValue GetBalance()) { wxMessageBox(Amount exceeds your balance ); return; } if (nValue nTransactionFee GetBalance()) { wxMessageBox(string(Total exceeds your balance when the ) FormatMoney(nTransactionFee) transaction fee is included ); return; } // Parse bitcoin address uint160 hash160; bool fBitcoinAddress AddressToHash160(strAddress, hash160); if (fBitcoinAddress) { // Send to bitcoin address CScript scriptPubKey; scriptPubKey OP_DUP OP_HASH160 hash160 OP_EQUALVERIFY OP_CHECKSIG; if (!SendMoney(scriptPubKey, nValue, wtx)) return; wxMessageBox(Payment sent , Sending...); }获取到地址后就是strAddress1开头的那种地址然后调用函数AddressToHash160转成hash160格式就是将strAddress base58解码得到25字节的数据后去掉前缀一个字节和去掉后四个字节的校验和得到20字节的数据就是hash160,其实就是公钥生成账号里的第二步骤里那个hash160数据把它提取出来AddressToHash160函数的功能就是这个。得到hash160后关键的代码CScript scriptPubKey; scriptPubKey OP_DUP OP_HASH160 hash160 OP_EQUALVERIFY OP_CHECKSIG;3.脚本锁定和解锁其中OP_开头的都是操作码指明进行什么操作这是一个锁定脚本。放在vout里如下成员class CTxOut { public: int64 nValue; // 金额单位聪 CScript scriptPubKey; // ← 这里就是你看到的 scriptPubKey // ... 其他成员和函数 };我们知道这是个脚本但是这个脚本只是一部分它还需要对应的脚本就是解锁脚本(即CTxIn里class CTxIn { public: COutPoint prevout; // 指向上一笔交易的输出哪个 UTXO CScript scriptSig; // ← 这里就是解锁脚本Unlocking Script uint32_t nSequence; // 序列号老版本常用 0xFFFFFFFF // ... 其他成员 };这两个脚本内容最终合成一个脚本执行后如果结果为true则解锁成功后续解锁人可以花费这笔vout)。好关于下面的操作码这里先不解释scriptPubKey OP_DUP OP_HASH160 hash160 OP_EQUALVERIFY OP_CHECKSIG;4.脚本代码解释因为我们要先理解一些概念什么叫合成一个脚本什么叫结果为true这些概念。我们先来看这个脚本代码2 3 OP_ADD 5 OP_EQUALOP_ADD这个操作码是两数相加OP_EQUAL是判断两数量相等。但是这里我们要搞清楚的是操作哪两个数相加结果又放在哪里。我们这里要引入栈顶的概念push和pop。第一个2就是将2这个数字压入栈顶即push操作。然后再压入3接着OP_ADD的意思是从栈顶开始将两个数字相加并将结果压入栈顶。那么如果你是2 3 8 OP_ADD那么是3和8相加。并且结果是2 11注意并不是2 3 8 11。此时栈中的状态。因为OP_ADD操作两上数字它会把这个两个数字pop出去相当于删除。再压入结果那么此时11被压入栈顶。所以我们再来看这个脚本2 3 OP_ADD 5 OP_EQUAL2 3相加得到5 再压入5然后调用OP_EQUAL判断两者是否相等。如果相等就把true压入栈顶即1的值。把这操作脚本转换成相关的类就是CScript script; script 2 3 OP_ADD 5 OP_EQUAL;5.运行脚本前言:可以看到我们脚本就是正常的脚本没有什么锁定脚本和解锁脚本这些都是自定义的你可以人为的把这完整的脚本分开比如锁定脚本就是5 OP_EQUAL,然后解锁脚本就是2 3 OP_ADD,然后验证的时候拼接在一起就行了。这就是锁定脚本和解释脚本的来源。当然比特币真正这部分脚本比较复杂不是简单的验证一个是否等于5就行了要验证签名的。所以这里的验证签名并不是外面单独调用一个“验证签名”函数而是通过脚本来完成签名验证的。当然脚本内部实现肯定也是调用了“验证签名” 函数。为什么要这样设计呢或者说为什么要引入脚本呢增加灵活性使其变成了一种可编程的货币关于涉及到哪些功能我们将在后续慢慢介绍。我们要运行脚本就必须解决CScript类相关问题即兼容性将script.h和script.cpp引用到我们的项目中并解决兼容性问题。6.CScript我们先来理解一下源码中的这个类class CScript : public vectorunsigned char它为什么要继承vectorunsigned char呢因为SCcript作为脚本容器用vector数组来存储脚本代码最好不过了。所以它要继承这个类。注意这个并不是栈的容器栈是另一个容器stack相关。好我们在自己的项目中新建script.h和script.cpp然后将代码复制进去。7.修改报错代码然后接下来是修改报错的代码了(可跳过后续可直接下载改好的文件)7.1script.cpp 928行(foreach改成for)foreach(const CScript script2, vTemplates)改成如下for(const CScript script2:vTemplates)7.2986行同样改法:foreach(PAIRTYPE(opcodetype, valtype) item, vSolution)然后上面的PAIRTYPE未定义标识符我们需要在util.h中添加如下代码// This is needed because the foreach macro cant get over the comma in pairt1, t2 #define PAIRTYPE(t1, t2) pairt1, t21045行同样改for语句:foreach(PAIRTYPE(opcodetype, valtype) item, vSolution)script.h中 303行:return HexNumStr(vch.begin(), vch.end());HexNumStr未定义标识符将此代码复制到util.h中:templatetypename T string HexNumStr(const T itbegin, const T itend, bool f0xtrue) { const unsigned char* pbegin (const unsigned char*)itbegin[0]; const unsigned char* pend pbegin (itend - itbegin) * sizeof(itbegin[0]); string str (f0x ? 0x : ); for (const unsigned char* p pend-1; p pbegin; p--) str strprintf(%02X, *p); return str; }310行改forforeach(const vectorunsigned charvch, vStack)555行:printf(CScript(%s)\n, HexStr(begin(), end()).c_str());HexStr未定义util.h中添加如下代码:templatetypename T string HexStr(const T itbegin, const T itend, bool fSpacestrue) { const unsigned char* pbegin (const unsigned char*)itbegin[0]; const unsigned char* pend pbegin (itend - itbegin) * sizeof(itbegin[0]); string str; for (const unsigned char* p pbegin; p ! pend; p) str strprintf((fSpaces p ! pend-1 ? %02x : %02x), *p); return str; }script.cpp 1078行改forforeach(PAIRTYPE(opcodetype, valtype) item, vSolution)然后我们来看这里报错(1099行处)uint256 hash SignatureHash(scriptPrereq txout.scriptPubKey, txTo, nIn, nHashType); if (!Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig)) return false; txin.scriptSig scriptPrereq txin.scriptSig;这个的运算符即scriptPrereq这个操作显示没有与这些操作数匹配的运行符。然后我看了CScript是有重载操作符的如下friend CScript operator(const CScript a, const CScript b) { CScript ret a; ret b; return (ret); }但是奇怪的是左右两边都是CScript类型而我的实际应用右边txout.scriptPubKey是string类型。源码中为什么没有string类型的重载或者说没写相关的转换功能中本聪应该不可能犯这种错。我想了一下恍然大悟原本txout.scriptPubKey就是CScript类型只不过我之前为了简便用string代替了。改回即可如下:class CTxOut { public: int64 nValue; CScript scriptPubKey;包括后面的txin.scriptSigCTxIn类里一样改回。接下来还有几个小报错也是跟CTxOut和CTxIn类相关因为之前这些类都是自己写的简写类。缺了一些成员和函数把报错的补上即可解决。CTxIn没有nSequence加上即可:unsigned int nSequence;CTxOut没有SetNull函数加上此函数即可:void SetNull() { nValue -1; scriptPubKey.clear(); }好没有显式的报错了接下来编译一下。不通过报没有匹配的号运算符。这是因为我们之前写的自定义函数CreateBlock和 GenerateTestBlock之类的将vin和vout里的签名用字符串代替导致现在我改回来CScript类型了但这里的函数还是给它们赋值字符串把这些函数删掉或把相应赋值注释掉就行txNew.vin[0].prevout.n -1; //txNew.vin[0].scriptSig pszTimestamp; //注释掉 否则类型不匹配 //vout赋值 txNew.vout[0].nValue 100; // txNew.vout[0].scriptPubKey zhengyong;这次编译也没问题了接下来我们来测试一下代码写一些脚本来调用。看能否正常执行。8.EvalScript我们先来看源码中验证签名脚本的逻辑bool VerifySignature(const CTransaction txFrom, const CTransaction txTo, unsigned int nIn, int nHashType) { assert(nIn txTo.vin.size()); const CTxIn txin txTo.vin[nIn]; if (txin.prevout.n txFrom.vout.size()) return false; const CTxOut txout txFrom.vout[txin.prevout.n]; if (txin.prevout.hash ! txFrom.GetHash()) return false; return EvalScript(txin.scriptSig CScript(OP_CODESEPARATOR) txout.scriptPubKey, txTo, nIn, nHashType); }最后是调用了EvalScript这个函数来执行脚本的在第一个参数里也可以看到将txin.scriptSig和txout.scriptPubKey是拼在了一起的。这就是锁定脚本和解锁脚本实现的原理。9.测试代码int main() { printf(Bitcoin v0.1.0 Simple Script Test\n); printf(Script: 2 3 OP_ADD 5 OP_EQUAL\n\n); // 1. 构造脚本 CScript script; script 2 3 OP_ADD 5 OP_EQUAL; // 2. 执行脚本 vectorvectorunsigned char stack; bool success EvalScript(script, CTransaction(), 0, 0, stack); // 3. 输出结果 if (success) { printf(EvalScript executed successfully!\n); if (!stack.empty()) { const std::vectorunsigned char top stack.back(); bool finalResult (top.size() 0 top[0] ! 0); printf(Final stack size : %zu\n, stack.size()); printf(Top of stack (bool): %s\n, finalResult ? TRUE : FALSE); if (finalResult) printf(Script result: PASSED (as expected)\n); else printf(Script result: FAILED\n); } else { printf(Stack is empty!\n); } } else { printf(EvalScript failed!\n); } // 可选打印栈中所有元素调试用 printf(\nStack content (bottom to top):\n); for (size_t i 0; i stack.size(); i) { // 简单打印前几个字节 printf([%zu] size%zu , i, stack[i].size()); for (size_t j 0; j stack[i].size() j 8; j) printf(%02x , stack[i][j]); printf(\n); } return 0; }运行结果:执行成功返回真。10.stack栈结构我们可以看到stack就是用来接收栈的内容的。其实栈的容器就是一个vector的unsigned char二维数组。我们在EvalScript中也能看到如下定义vectorvaltype stack;其中的valtype类型其实就是typedef vectorunsigned char valtype;好知道了stack栈是个二维数组那它是怎么压入变量的呢一个二维数组的元素算一个变量不是这样的。而是stack[0]算一个按行来压入元素为什么这样设计很明显如果你压入单个字节的变量到栈中那是没问题的如果你要压入一个很大的数字比如10000000或者压入公钥到栈中呢一个字节满足不了。所以它是按行来算的比如:script 2 3 OP_ADD 5 OP_EQUAL;压入2到stack[0]中然后压入3到stack[1]中此时我们调用vector.back()返回最后一个元素就是stack[1]为栈顶然后调用OP_ADD把两个数字相加即pop弹出两个元素(删除)然后再将结果压入栈顶。那么此时stack[0]就被压入5了然后是压入5到stack[1]中最后调用OP_EQUAL操作码判断是否相等把结果压入到栈顶中。注意上面压入单个字节的变量即stack[0][0],我要表达的意思是再压入不是stack[0][1],而是stack[1][0]这样好这些是一个字节如果压入一个大数字或者公钥那么二维数组此时的列数是根据数据大小来的。即如果这个元素是两个字节那么stack[0][0],stack[0][1]用来存储如果是32字节则stack[0][0]~stack[0][31]是这个范围。然后再压入一个元素会另起一行。11.EvalScript逻辑但是你会发现如果我们把上面的脚本5改成6即script 2 3 OP_ADD 6 OP_EQUAL;执行后你会发现栈是空的按理说就算结果为false它也应该把0这个数值存储到栈中栈顶不会为空的。这是因为EvalScript就是为了锁定和解锁而设计的所以如果结果为假它就会清空栈里的结果(为真则会保留栈中的结果)。为假后你并不能访问stack里的结果元素。所以我们只要判断一下stack[0].size()为空即为0也可知道结果为假如果我们需要一个纯粹的脚本执行器我们需要修改EvlaScript函数相关代码。这里留待以后。这里我们知道就行就像下面script 2 3 OP_ADD ;这个脚本没有判断相等但是栈的结果为5也为真EvalScript它的返回值也是为真的。最后元素输出为5:另注意这里代码const std::vectorunsigned char top stack.back(); bool finalResult (top.size() 0 top[0] ! 0);这里的top[0]并不是stack[0],而是stack[0][0],因为back返回最后一个元素即stack[0]赋给top那么top[0]当然就是stack[0][0]了。

相关文章:

二十六.签名与脚本(1)--脚本介绍

1.区块链脚本介绍在之前的章节中,我们了解了签名与验证相关,但是btc的交易数据,签名和验证,不是单纯的,还有脚本深度参与其中。我们从开始来:bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletT…...

高精度光照检测

光线检测仪,kotlin开发,调用手机感光模块检测室内外光照强度,用途多多,我主要用途孩子写作业检测光照保护视力。 食用方法∶打开即测,速度快,无广告,手机平视即可,无须直视光线。 买…...

独立开发者如何利用Taotoken Token Plan,以更低成本启动AI项目

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 独立开发者如何利用Taotoken Token Plan,以更低成本启动AI项目 对于独立开发者或小型团队而言,启动一个集成…...

Taotoken的审计日志功能为企业API安全与合规管理提供支持

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 Taotoken的审计日志功能为企业API安全与合规管理提供支持 当企业决定将大模型能力集成到内部业务流程中时,IT管理员和安…...

为你的Hermes Agent自定义Provider,接入Taotoken多模型池

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 为你的Hermes Agent自定义Provider,接入Taotoken多模型池 在构建复杂的AI应用时,开发者常常面临一个核心挑…...

艾尔登法环存档迁移终极指南:3分钟解决角色转移难题

艾尔登法环存档迁移终极指南:3分钟解决角色转移难题 【免费下载链接】EldenRingSaveCopier 项目地址: https://gitcode.com/gh_mirrors/el/EldenRingSaveCopier 还在为《艾尔登法环》存档版本不兼容而烦恼吗?EldenRingSaveCopier 是你的终极解决…...

3分钟开启PC游戏分屏派对:NucleusCoop让单机游戏秒变多人同屏神器

3分钟开启PC游戏分屏派对:NucleusCoop让单机游戏秒变多人同屏神器 【免费下载链接】nucleuscoop Starts multiple instances of a game for split-screen multiplayer gaming! 项目地址: https://gitcode.com/gh_mirrors/nu/nucleuscoop 还在为热门PC游戏不支…...

GIS工程应用记录(AI辅助编程)

问题的问题:语境坍缩“从各个角度提出问题,AI做出对应积极答复和修改,结果没有什么变化。”这,就是元问题最核心的症状。你尝试了所有你已知的“高级”协作手段,但就像重拳打在棉花上,AI永远在积极回应&…...

脉冲神经网络加速器设计与边缘计算优化

1. 脉冲神经网络加速器的设计挑战与突破在边缘计算领域,脉冲神经网络(SNN)正以其独特的生物启发特性引发新一轮技术变革。与传统人工神经网络(ANN)相比,SNN通过离散的脉冲信号传递信息,模拟生物神经元的工作机制,理论上可实现超低…...

OpenIPC开源固件:5分钟解锁网络摄像头的终极控制权

OpenIPC开源固件:5分钟解锁网络摄像头的终极控制权 【免费下载链接】firmware Alternative IP Camera firmware from an open community 项目地址: https://gitcode.com/gh_mirrors/fir/firmware 还在为网络摄像头的封闭系统而烦恼吗?想要完全掌控…...

DS4Windows终极指南:3步让PS手柄在PC上完美运行游戏

DS4Windows终极指南:3步让PS手柄在PC上完美运行游戏 【免费下载链接】DS4Windows Like those other ds4tools, but sexier 项目地址: https://gitcode.com/gh_mirrors/ds/DS4Windows 还在为PS手柄连接Windows电脑后无法识别而烦恼吗?&#x1f3ae…...

如何在3分钟内为任何活动搭建专业级滚动抽奖系统?Magpie-LuckyDraw全平台开源方案深度解析

如何在3分钟内为任何活动搭建专业级滚动抽奖系统?Magpie-LuckyDraw全平台开源方案深度解析 【免费下载链接】Magpie-LuckyDraw 🏅A fancy lucky-draw tool supporting multiple platforms💻(Mac/Linux/Windows/Web/Docker) 项目地址: https…...

Ubuntu经常安装软件

1、垃圾清理工具stacer sudo apt updatesudo apt install stacer apt cleanapt autocleanapt autoremove 2、类似与everything的工具Fsearcch 1sudo add-apt-repository ppa:christian-boxdoerfer/fsearch-stable 2sudo apt update 3sudo apt install fsearch (注&#xf…...

ZMJS,把 JavaScript 解释器放进 SAP ABAP 应用服务器之后,很多扩展思路会变得不一样

我今天看这个 oisee/zmjs 仓库时,最吸引人的不是它把 JavaScript 语法做进了 ABAP,而是它选择了一条非常 SAP 的路线,纯 ABAP、无外部依赖、无 Kernel Module、以类和接口的形式运行在 SAP 应用服务器内部。仓库自己的定位很直接,ZMJS 是一个面向 SAP ABAP 的 Mini JavaScr…...

航空发动机叶片三维扫描-诺斯顿

航空发动机叶片作为发动机的核心动力部件,其精度与性能直接决定发动机的推力、燃油效率及运行安全性,三维扫描技术作为航空制造领域的核心数字化手段,已广泛应用于叶片全生命周期的多个关键环节。其应用涵盖叶片研发设计阶段的逆向工程&#…...

LaTeX公式一键转Word:3步告别数学公式编辑烦恼

LaTeX公式一键转Word:3步告别数学公式编辑烦恼 【免费下载链接】LaTeX2Word-Equation Copy LaTeX Equations as Word Equations, a Chrome Extension 项目地址: https://gitcode.com/gh_mirrors/la/LaTeX2Word-Equation 还在为Word文档中的数学公式编辑而抓狂…...

打造XBEE封装BLE112蓝牙模块:硬件设计、射频布局与调试全攻略

1. 项目概述:为什么我们需要一个“XBEE格式”的蓝牙模块?在嵌入式开发和物联网项目中,无线通信模块的选择往往决定了项目的成败。对于很多工程师和创客来说,Silicon Labs(芯科科技)的BLE112/113模块是蓝牙4…...

Codex使用API Key授权无法使用插件?

小伙伴们,大家好,我是小溪,见字如面。对于没有ChatGPT账号的小伙伴来说,虽然可以通过API Key授权的方式使用Codex桌面端,但是会有一些限制。比如无法使用插件功能,无法使用Codex移动端进行远程控制等。为了…...

LVGL多页面开发避坑:用内部Timer替代轮询,解决页面切换时的内存踩踏问题

LVGL多页面开发中的内存安全实践:用Timer机制替代轮询的工程解决方案 在嵌入式UI开发中,LVGL因其轻量级和跨平台特性成为热门选择。但当项目复杂度提升到多页面交互时,开发者往往会遇到一个棘手问题:如何在频繁切换页面的同时保证…...

1688运营培训/询盘成本从500元降到63.9!1688运营培训还原1688真实玩法

1688运营培训/询盘成本从500元降到63.9!1688运营培训还原1688真实玩法500块钱一个询盘,你敢信?做1688运营培训这么多年,这个数字我都觉得离谱。前阵子遇到一个老板,一上来就开始吐槽1688,说1688就是个垃圾平…...

告别Postman!用APIfox搞定接口测试+自动化,这份保姆级教程带你从环境配置到报告生成

从Postman到APIfox:接口测试自动化的高效迁移指南如果你还在为接口测试中的重复劳动和多环境切换头疼,是时候考虑从Postman迁移到APIfox了。作为一名经历过这个转型过程的开发者,我想分享一些实战经验,帮助你平滑过渡并最大化利用…...

用Azure Kinect DK和Body Tracking SDK,5分钟实现一个实时人体骨骼点检测Demo(C++版)

5分钟实战:用Azure Kinect DK实现实时人体骨骼点追踪(C版) 当你第一次拿到Azure Kinect DK时,最令人兴奋的莫过于它强大的人体追踪能力。这款深度相机不仅能捕捉高清彩色图像,更能通过AI算法实时重建人体骨骼关节点。本…...

【python】ImportError: DLL load failed while importing QtWidgets: 找不到指定的程序。重新安装后搞定

文章目录前言一、PyQt6引用后报错二、使用步骤总结前言 想做个好看的界面,引用了PyQt6,却产生了新问题。 pip install pyqt6-tools,优先做这个动作进行修复。 一、PyQt6引用后报错 python里引用: from PyQt6.QtWidgets import…...

榨干Codex!OpenAI工程师亲授Codex真正用法

你可能把 Codex 当编程助手用,改改代码,跑跑测试。但它的能力远不止于此。OpenAI 的客户支持工程师 Jason(jxnlco)告诉你,Codex 其实是一套完整的电脑工作系统,从语音输入到自动化,从浏览器操控…...

真可用!美团数字人模型开源,MV、电商等统统拿下

美团开源的数字人视频生成框架 LongCat-Video-Avatar 刚刚更新到 1.5 版本。是真能用。这版更新把音频编码器换了,推理步数砍到8步,在770人、13240条主观评分的大规模评测里,雷达图面积全面领先。音频编码器换血,8步出图LongCat-V…...

yolo视频识别 车辆速度估计识别 yolo11视频实时速度测量与测速估计

文章目录YOLOv11:视频实时速度测量与测速估计一、YOLOv11概述二、速度测量原理三、距离测量方法四、应用场景五、实践案例以下是关于使用YOLOv11进行视频实时速度测量与测速估计的介绍: YOLOv11:视频实时速度测量与测速估计 随着计算机视觉…...

十年以上经验的建站公司推荐|策划强、落地稳的网站制作公司盘点

互联网时代,企业官网已从单纯的信息展示窗口升级为集品牌价值传递、用户体验连接与业务高效转化于一体的核心数字阵地。行业报告显示,优质官网可帮助企业线上转化率提升35%-60%,而低效官网则可能导致潜在客户大量流失。面对市场上众多的网站建…...

179个核心职位,50个公司分类,中国大模型产业全栈

最后 对于正在迷茫择业、想转行提升,或是刚入门的程序员、编程小白来说,有一个问题几乎人人都在问:未来10年,什么领域的职业发展潜力最大? 答案只有一个:人工智能(尤其是大模型方向)…...

解决方法:庐山派K230接串口没识别到端口问题

一、插入usb转串口工具之前二、插入usb转串口工具之后三、解决方法说明:🔍 核心原因:USB Serial 设备,没有被识别为 COM 口你现在看到的 USB Serial,说明开发板已经正常启动了,USB 也被电脑识别到了&#x…...

告别DLL缺失烦恼!Visual C++运行库合集一键搞定Windows应用依赖问题

告别DLL缺失烦恼!Visual C运行库合集一键搞定Windows应用依赖问题 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经在打开某个软件或游戏时…...