加密解密软件VMProtect教程(八)许可制度之序列号生成器
VMProtect是新一代软件保护实用程序。VMProtect支持德尔菲、Borland C Builder、Visual C/C++、Visual Basic(本机)、Virtual Pascal和XCode编译器。
同时,VMProtect有一个内置的反汇编程序,可以与Windows和Mac OS X可执行文件一起使用,并且还可以链接编译器创建的MAP文件,以快速选择要保护的代码片段。
为了轻松实现应用程序保护任务的自动化,VMProtect实现了内置脚本语言。VMProtect完全支持Windows系列的32/64位操作系统(从Windows 2000开始)和Mac OSX(从版本10.6开始)。重要的是,无论目标平台如何,VMProtect都支持所有范围的可执行文件,即Windows版本可以处理Mac OS X版本的文件,反之亦然。
VMProtect是保护应用程序代码免遭分析和破解的可靠工具,但只有在正确构建应用程序内保护机制并且没有可能破坏整个保护的典型错误的情况下才能最有效地使用。
序列号生成器
他们有什么用?
除了 VMProtect,其他软件也可以生成序列号。这是自动发送序列号所必需的。客户购买产品,电子商务代理向供应商网站发送 HTTP 查询,生成器在服务器上运行并根据客户数据生成序列号。序列号被发送给客户和供应商。然后,供应商使用导入许可证对话框手动将序列号添加到 VMProtect。
怎么运行
VMProtect 的许可系统基于非对称算法,这就是为什么需要秘密产品密钥来生成序列号的原因。您可以在项目属性窗口中导出此密钥,并以任何合适的方式将其传递给生成器。
电子商务代理使用 HTTP 查询调用生成器。可以直接调用 PHP 生成器,基于 DLL 的生成器 - 间接调用,但原理是相同的:
- 从电子商务代理接收用户数据
- 添加供应商指定的所有必需信息
- 生成序列号
- 使用其中一种算法对其进行加密
- 将结果发送给电商代理
有现成的发电机吗?
许可系统带有三个随时可用的序列号生成器作为 DLL,用于 .Net 平台和PHP。
我可以制作自己的发电机吗?
是的你可以。序列号的格式在这里,序列号的加密算法在这里描述。
安全吗?
一般来说,是的,这是安全的。但是,您应该遵循以下建议:
- 使用 HTTPS——如果您的电子商务提供商可以发送 HTTPS 查询,并且您的网络托管提供商可以回答此类请求——您应该更喜欢这种变体而不是典型的 HTTP,因为在这种情况下,所有数据都以加密形式传输,生成的序列无法截取号码。
- “隐藏”你的发电机——确保没有人可以偶尔打开发电机。www.site.com/keygen.php 地址是个坏主意。虽然 www.site.com/abc123.php 要好得多。确保您没有放置任何指向密钥生成器的外部链接,它没有列在网站目录中,也没有将它放入任何服务文件(如 robot.txt)中。对发电机的位置了解得越少越好。或者,您甚至可以将生成器放在另一个网站上。
- 确保调用生成器的是电子商务代理——处理来自代理的查询的程序应该检查调用者的 IP 地址。电子商务提供商通常会发布用于查询序列号生成器的 IP 范围。在您的代理处找到该列表并在程序中添加支票。如果发送查询的 IP 地址超出指定的 IP 范围,则不要生成可理解的错误消息。要么不返回任何内容,要么生成一个简单的 404。不要提供任何有关查询失败原因的线索。
- 检查输入参数——电子商务代理控制面板中的产品设置通常允许您指定代理为接收许可证而应进行的查询字符串。例如,您想要接收用户名、电子邮件地址、购买日期和订单 ID。因此,请确保所有这些参数都已传递并且所有参数都具有正确的格式。不要对错误的查询做出任何响应。每当对生成器进行错误查询时,向您自己的电子邮件发送消息。这应该有助于调查问题。
- 添加“密码”指定电子商务代理发送的查询中的附加参数,即密码。它应该有一个不明显的名称和值。从接收方检查此参数。如果值错误或未指定参数 – 不生成序列号。
序号生成器
- Windows版本
- Net版本
- UNIX 版本
- 序列号格式
- 序列号加密算法
描述
Windows 密钥生成器是用于 x86 和 x64 平台的 DLL 文件、一个 C 语言头文件和一个 MSVC 兼容的库文件。因此,库既可以静态链接也可以动态加载。
生成器的所有文件都位于Keygen\DLL文件夹中。生成序列号的测试应用程序也在那里。
生成器 API
生成器仅导出两个函数:第一个函数生成一个序列号,而第二个函数释放第一个函数分配的内存。让我们从第一个也是主要的开始:
VMProtectErrors __stdcall VMProtectGenerateSerialNumber ( VMProtectProductInfo * pProductInfo, VMProtectSerialNumberInfo * pSerialInfo, char ** pSerialNumber );
第一个参数是指向VMProtectProductInfo结构的指针,其内容已上传到 VMProtect(请参阅导出产品参数)。该结构包含产品私钥、使用的算法和产品的标识符。有关填充此结构的更多详细信息如下。
第二个参数是指向VMProtectSerialNumberInfo结构的指针,其内容被移动到生成的序列号中。该结构包含序列号的所有字段和定义应将哪些字段写入序列号的位掩码。
struct VMProtectSerialNumberInfo {INT flags;wchar_t * pUserName;wchar_t * pEMail;DWORD dwExpDate;DWORD dwMaxBuildDate;BYTE nRunningTimeLimit;char * pHardwareID;size_t nUserDataLength;BYTE * pUserData; };
flags字段包VMProtectSerialNumberFlags中的位标志,该集合在结构之前进行了描述:
- HAS_USER_NAME – 将pUserName变量中的用户名放入序列号中。
- HAS_EMAIL – 将pEMail变量中的电子邮件放入序列号中。
- HAS_EXP_DATE – 序列号将在dwExpDate变量中指定的日期之后过期。
- HAS_MAX_BUILD_DATE – 序列号仅适用于在dwMaxBuildDate变量中指定的日期之前构建的产品版本 。
- HAS_TIME_LIMIT – 程序在nRunningTimeLimit变量指定的时间到期后停止工作(时间以分钟为单位指定,不应超过 255)。
- HAS_HARDWARE_ID – 该程序仅适用于具pHardwar变量中指定的 ID 的硬件。
- HAS_USER_DATA – 将nUserDataLength长度的自定义用户数据放在pUserData的地址到序列号。
第三个参数是指向指针的指针。生成的序列号的地址写在那里。生成序列号后,应该复制它,地址必须传递给生成器的第二个 API 函数,该函数将释放序列号占用的内存。
void __stdcall VMProtectFreeSerialNumberMemory ( char * pSerialNumber);
VMProtectGenerateSerialNumber函数返回一个VMProtectErrors值,如果成功生成序列号,则该值包含 0,或者包含一个错误代码。可能的错误代码是:
- ALL_RIGHT – 没有错误,序列号已生成。
- UNSUPPORTED_ALGORITHM – 在函数的第一个参数中传递了不正确的密钥加密算法。
- UNSUPPORTED_NUMBER_OF_BITS – 在函数的第一个参数中传递了不正确的位数。
- USER_NAME_IS_TOO_LONG – UTF-8 编码的用户名长度超过 255 字节。
- EMAIL_IS_TOO_LONG – UTF-8 编码的用户电子邮件的长度超过 255 字节。
- USER_DATA_IS_TOO_LONG – 用户数据的长度超过 255 字节。
- HWID_HAS_BAD_SIZE – 硬件标识符的大小不正确。
- PRODUCT_CODE_HAS_BAD_SIZE – 在函数的第一个参数中传递的产品标识符大小不正确。
- SERIAL_NUMBER_TOO_LONG – 序列号太长,无法满足算法中指定的位数。
- BAD_PRODUCT_INFO – 函数的第一个参数不正确或为 NULL。
- BAD_SERIAL_NUMBER_INFO – 函数的第二个参数不正确或为 NULL。
- BAD_SERIAL_NUMBER_CONTAINER – 该函数的第三个参数未指向要写入序列号地址的内存。
- NOT_EMPTY_SERIAL_NUMBER_CONTAINER – 函数的第三个参数不指向空内存单元,该单元必须为 NULL。
- BAD_PRIVATE_EXPONENT – 函数的第一个参数包含不正确的私有指数值。
- BAD_MODULUS – 函数的第一个参数包含不正确的模数值。
错误可以分为两类:由不正确的参数或第一个参数的不正确值引起的错误,以及其他所有错误。第一类错误很少见,它们表示结构配置不正确。您应该重新上传产品信息并检查结构是否填写正确。可以在下面找到正确填充结构的示例。
第二类错误是由于尝试向键中放入超过其大小所能容纳的更多数据而引起的。在这种情况下,我们建议向电子商务提供商发送一条消息,其中包含“密钥将在 24 小时内发送”之类的文本,而不是实际的序列号,并将所有必需的信息发送到您自己的电子邮箱。在这种情况下,密钥是在 VMProtect 中手动生成的,一些数据被截断以适应最大密钥大小的所有关键信息。
使用示例
下面是调用上述函数并生成序列号的代码示例。注意最开始的代码块。在您将其替换为从 VMProtect 为您的产品导出的示例之前,该示例将不起作用:
// // !!! this block should be generated by VMProtect !!! /// // VMProtectAlgorithms g_Algorithm = ALGORITHM_RSA; size_t g_nBits = 0; byte g_vModulus[1]; byte g_vPrivate[1]; byte g_vProductCode[1]; // // int _tmain(int argc, _TCHAR* argv[]) { VMProtectProductInfo pi; pi.algorithm = g_Algorithm; pi.nBits = g_nBits; pi.nModulusSize = sizeof(g_vModulus); pi.pModulus = g_vModulus; pi.nPrivateSize = sizeof(g_vPrivate); pi.pPrivate = g_vPrivate; pi.nProductCodeSize = sizeof(g_vProductCode); pi.pProductCode = g_vProductCode;VMProtectSerialNumberInfo si = {0}; si.flags = HAS_USER_NAME | HAS_EMAIL; si.pUserName = L"John Doe"; si.pEMail = L"john@doe.com"; char * pBuf = NULL; VMProtectErrors res = VMProtectGenerateSerialNumber(&pi, &si, &pBuf); if (res == ALL_RIGHT) { printf("Serial number:\n%s\n", pBuf); VMProtectFreeSerialNumberMemory(pBuf); } else { printf("Error: %d\n", res); } return 0;
这是来自Keygen\DLL\Example的 Microsoft Visual Studio 示例项目。下面是代码中最有趣的部分以及我们的评论。
main函数的第一行使用从 VMProtect 导出的数据填充VMProtectProductInfo结构。此代码是典型的,不应更改以避免错误。然后我们创建VMProtectSerialNumberInfo结构并将用户名和电子邮件的位组合插入标志字段。在下一行中,我们将用户名和密码放入结构中的相应字段。请注意,值在 UNICODE 编码中被接受。密钥生成器会将它们转换为 UTF-8。
然后,我们初始化一个指针变量,用于存储生成的密钥的地址,并调用VMProtectGenerateSerialNumber,然后分析返回码。如果没有错误,生成的密钥将输出到控制台,并调用免费序列号记忆功能。
VMprotectSerialNumberInfo 结构的其余字段
结构的某些字段可能需要一些额外的解释。例如,dwExpDate和dwMaxBuildDate字段包含特定格式的日期:0xYYYYMMDD,即年存储在高位字中,月和日分别存储在低位字的高低字节中。为了产生这样的数字,使用了以下宏:MAKEDATE(y, m, d)。您可以这样称呼它:MAKEDATE(2010, 05, 12)。
pHardwareID字段应包含指向许可 SDK的VMProtectGetCurrentHWID方法返回的字符串的指针。
- .Net版本
描述
密钥生成器的 .Net 版本是包含生成序列号所需的所有内容的构建。源代码在Keygen\Net中作为两个项目:KeyGen(密钥生成器本身)和 Usage(密钥生成器的使用示例)。
密钥生成器在源代码中提供,以便在给定版本的 .Net Framework 下快速构建,但是我们强烈建议不要对代码应用任何更改。在 VMProtect 的未来版本中,可能会向生成器添加一些新的可能性,这可能会导致重复修改代码。此外,这可能会导致非常难以定位的错误。如果您在生成器的原始代码中发现错误或想提出改进建议,请联系支持团队。
使用发电机
以Usage项目中的代码为基础,然后将指向 VMProtect.KeyGen.dll 构建的链接添加到您的项目中。之后,您将能够在您的应用程序中生成序列号。为了正常运行,生成器必须“知道”您为哪个产品生成序列号。为此,在 VMProtect 中打开“Project | 导出密钥对”对话框并选择“KeyGen.Net 的参数”选项。下面的文本区域将包含文本信息,您应该将其作为字符串常量复制并粘贴到您的应用程序中。
下面是调用生成器的示例代码:
try { string data = @""; // put the exported data here Generator g = new Generator(data); g.UserName = "John Doe"; g.EMail = "john@doe.com"; g.ExpirationDate = DateTime.Now.AddMonths(1); g.MaxBuildDate = DateTime.Now.AddYears(1); g.RunningTimeLimit = 15; g.HardwareID = "AQIDBAgHBgU="; g.UserData = new byte[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; string serial = g.Generate(); Console.WriteLine("Serial number:\n{0}\n", serial); } catch (Exception ex) { Console.WriteLine("Error: {0}", ex); }
您从 VMProtect 复制的字符串应该放在作为参数传递给序列号类构造函数的数据变量中。如果在解析产品数据时出现任何问题,构造函数将抛出包含问题描述的异常。如果构造函数成功完成其工作,则生成器已准备好生成序列号。
序列号可以包含使用生成器属性指定的各种信息。上面的示例显示了如何填写序列号的所有字段。某些领域有限制。例如,用户名和电子邮件不能接受超过 255 个字符的 UTF-8 编码字符串。如果提供的数据不正确,属性会抛出包含问题描述的异常。
生成器设置完成后,将调用Generate()方法。此方法生成一个序列号。在此步骤中,将序列号的所有数据合并,计算校验和并加密数据。如果数据量超过允许的长度,该方法将抛出异常。
如果需要生成多个序列号,可以连续多次使用生成器类,而不需要从头开始创建。要清除生成器的任何给定属性,只需为其分配一个空值即可
以上便是本篇文章的分享,如果您还有其他问题,请私信我~
相关文章:

加密解密软件VMProtect教程(八)许可制度之序列号生成器
VMProtect是新一代软件保护实用程序。VMProtect支持德尔菲、Borland C Builder、Visual C/C、Visual Basic(本机)、Virtual Pascal和XCode编译器。 同时,VMProtect有一个内置的反汇编程序,可以与Windows和Mac OS X可执行文件一起…...

单源最短路的建图
1.热浪 信息学奥赛一本通(C版)在线评测系统 (ssoier.cn)http://ybt.ssoier.cn:8088/problem_show.php?pid1379 很裸的单源最短路问题,n2500,可以用dijksta或者spfa都能过,下面展示spfa的做法 #include<bits/stdc.h> usi…...

MyBatis基本操作及SpringBoot单元测试
目录 一、什么是单元测试? 1.1 单元测试的好处 1.2 单元测试的实现步骤 1.2.1 生成单元测试类: 1.2.2 SpringBootTest注解 1.2.3 检验方法结果: 二、利用MyBatis实现查询操作 2.1单表查询 2.2 参数占位符 #{} 和 ${} 2.2.1 ${} 字符…...

Linux之创建进程、查看进程、进程的状态以及进程的优先级
文章目录 前言一、初识fork1.演示2.介绍3.将子进程与父进程执行的任务分离4.多进程并行 二、进程的状态1.进程的状态都有哪些?2.查看进程的状态2.运行(R)3.阻塞4.僵尸进程(Z)1.僵尸状态概念2.为什么要有僵尸状态&#…...

k8s部署rabbitmq
docker pull rabbitmq:3.9.28-management 1.部署模板 apiVersion: v1 kind: Service metadata:name: rabbitmq spec:ports:- name: amqpport: 5672targetPort: 5672- name: managementport: 15672targetPort: 15672selector:app: rabbitmq---apiVersion: apps/v1 kind: Statef…...

关于QGroundControl的软件架构的理解
首先QGC是基于QT平台开发,个人理解软件架构即为项目前后端结构,以及前后端数据交互的逻辑。下面是对QGroundControl源码的一些个人理解,写这个博客只是为了记录下来,防止时间久了忘记,过程中看了一些大佬的博客来帮助理…...
Android 文本识别:MLKIT + PreviewView
随着移动设备的普及和摄像头的高像素化,利用相机进行文本识别成为了一种流行的方式。MLKit 是 Google 提供的一款机器学习工具包,其中包含了丰富的图像和语言处理功能,包括文本识别。PreviewView 是 Android Jetpack 的一部分,它提…...

刮泥机的分类有哪些及组成部分
刮泥机的分类有哪些及组成部分 刮泥机的分类: 刮泥机主要包括:周边传动刮泥机、中心传动浓缩刮泥机。 1、中心传动浓缩刮泥机:主要由溢流装置、大梁及拦杆、进口管、传动装置、电器箱、稳流筒、主轴、浮渣耙板、刮集装置、水下轴承、小刮刀、…...

Qt编程基础 | 第六章-窗体 | 6.2、VS导入资源文件
一、VS导入资源文件 1.1、导入资源文件 步骤一: 将所有图片放到各自文件夹下,并将文件夹拷贝到资源文件(.qrc文件)的同级目录下,如下: 步骤二: 新建VS项目的时候,系统会自动建好一…...

NET框架程序设计-第4章类型基础
4.1 所有类型的基类型:System.Object CLR 要求每个类型最终都要继承自 System.Object 类型。 两种类型定义: 1)隐式继承 //隐式继承 Object class Employee{}2)显式继承 class Employee:System.Object{}System.Object 主要的公…...

Java设计模式-备忘录模式
简介 在软件开发中,设计模式是为了解决常见问题而提出的一种经过验证的解决方案。备忘录模式(Memento Pattern)是一种行为型设计模式,它允许我们在不破坏封装性的前提下,捕获和恢复对象的内部状态。 备忘录模式是一种…...

Zookeeper集群 + Kafka集群
Zookeeper 概述 Zookeeper 定义 Zookeeper是一个开源的分布式的,为分布式框架提供协调服务的Apache项目。 Zookeeper 工作机制 Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数…...

“邮件营销新趋势,这个平台让你收获颇丰!
随着各媒体平台的迅速发展,2023年大家更专注于视频营销、网红营销、直播营销等营销方式。可以见得,数字媒介手段的发展,对于营销方式也产生了巨大的影响。但是,企业在拥抱新兴的营销方式的同时,也不要忽视传统的营销方…...

Python列表推导
列表推导式 列表推导式创建列表的方式更简洁。常见的用法为,对序列或可迭代对象中的每个元素应用某种操作,用生成的结果创建新的列表;或用满足特定条件的元素创建子序列。 例如,创建平方值的列表: squares [] for …...

git使用查看分支、创建分支、合并分支
一、查看分支 查看的git命令如下: git branch 列出本地已经存在的分支,并且当前分支会用*标记 git branch -r 查看远程版本库的分支列表 git branch -a 查看所有分支列表(包括本地和远程,remotes/开头的表示远程分支)…...

vue3.0与vue2.0
一、生命周期的变化 1.vue2.响应式架构 2.vue3.0 响应式架构图 Vue3.0响应式框架在设计上,将视图渲染和数据响应式完全分离开来。将响应式核心方法effect从原有的Watcher中抽离。这样,当我们只需要监听数据响应某种逻辑回调(例如监听某个text属性的变化…...

HTML 中的常用标签用法
HTML是构建Web页面的基础语言,其中包含许多不同类型的标签。这些标签由尖括号包围,以指示浏览器如何呈现文本。下面是HTML中的一些常用标签以及它们的使用方法: 标题标签(h1-h6) 标题标签用于标识页面内容的标题&…...

【C++】指针 - 定义和使用,所占内存空间,空指针,野指针,const 修饰指针,指针和数组,指针和函数
文章目录 1. 定义和使用2. 所占内存空间3. 空指针4. 野指针5. const 修饰指针6. 指针和数组7. 指针和函数 1. 定义和使用 数据类型 * 变量名; 指针的作用是,可以通过指针间接访问内存。 内存编号是从 0 开始记录的,一般用十六进制数字表示。可以利用指…...

新规之下产业园区如何合理收费水电费用
一、政策背景 2018年3月30日,国家发改委发布《国家发展改革委关于降低一般工商业电价有关事项的通知》。明确提出进一步规范和降低电网环节收费,一是提高两部制电价的灵活性;二是全面清理规范电网企业在输配电价之外的收费项目,重…...

1011. 在 D 天内送达包裹的能力
传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。 传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。 返回能在 days 天内将…...

基于SpringBoot养老院管理系统
目录 一、项目介绍 二. 运行环境 三、项目技术 四、部署项目 五、项目运行 六、项目展示 五、项目下载 一、项目介绍 基于springboot的养老院管理系统拥有多种角色账号:管理员和用户 管理员:管理员管理、用户管理、健康管理、病例方案管理、药品…...

1.3 eBPF的工作原理初探
写在前面 上一节提到过,eBPF程序是面向BPF体系结构指令集编写的,它并不直接运行在Linux内核中,我们可以理解为它是运行在eBPF虚拟机,由eBPF虚拟机来执行eBPF字节码,就像java运行在jvm一样。 我们用一张原理图来看下eBPF程序的编译,加载,验证,钩子,映射等结点。 如上是…...

【CH32】| 02——常用外设 | GPIO
系列文章目录 【CH32】| 00——开发环境搭建 【CH32】| 01——新建工程 | 下载 | 运行 |调试 【CH32】| 02——常用外设 | GPIO 失败了也挺可爱,成功了就超帅。 文章目录 前言1. GPIO简介2. IO口的内部结构框图保护二极管上下拉电阻施密特触发器两个MOS管输出寄存器…...

第四章 测试用例编
本科程目标 1.什么是测试用例 2.测试用例的重要性 3.测试用例的八大要素(重点) 4.测试用例的评审 一、什么叫软件测试用例 测试用例(TestCase)是为项目需求而编制的一组测试输入、执行条件以及预期结果,以便测试…...

解决dpdk reserve的内存返回的虚拟地址和iova地址一样的问题
1. 背景: 在ubuntu20.04上用dpdk API: rte_memzone_reserve_aligned("L1L2_PCIE_MEMORY", 1.5*1024*1024*1024, rte_socket_id(), RTE_MEMZONE_1GB|RTE_MEMZONE_IOVA_CONTIG, RTE_CACHE_LINE_SIZE); 分配1.5…...

JQuery实现小项目
博主简介:想进大厂的打工人博主主页:xyk:所属专栏: JavaEE初阶 目录 文章目录 一、JQuery是什么 二、JQuery项目 2.1 猜数字 2.2 表白墙 2.3 聚合搜索 2.4 计算器 一、JQuery是什么 jQuery是一个快速、简洁的JavaScript框架,是继Prototype之…...

【C++/嵌入式笔试面试八股】一、23.结构体指针 | 指针和引用 | 万能指针 | 野指针
结构体指针 28.将结构体作为参数向函数中传递 传递方式有两种: 值传递地址传递,利用操作符 -> 可以通过结构体指针访问结构体属性//学生结构体定义 struct student {//成员列表string name; //姓名int age; //年龄int score; //分数 };//值传递...

【C++初阶】类和对象(下)构造函数(初始化列表) + explicit关键字 +static成员
👦个人主页:Weraphael ✍🏻作者简介:目前学习C和算法 ✈️专栏:C航路 🐋 希望大家多多支持,咱一起进步!😁 如果文章对你有帮助的话 欢迎 评论💬 点赞…...

chatgpt赋能python:Python代码怎么用?一个10年编程经验工程师的实践总结
Python代码怎么用?一个10年编程经验工程师的实践总结 如果你正在学习Python或已经是一名Python开发者,你需要知道如何正确地使用Python代码以实现项目需求。在本文中,我将分享我的10年Python编程经验,并介绍一些关于如何使用Pyth…...

【Android定制】修改BUILD_AGO_GMS = no 和 BUILD_GMS=no属性
文章目录 概要名词解释细节小结 概要 在安卓底层源码中,有这样的两个属性,这两个第一眼看上去都像是带不带谷歌,BUILD_AGO_GMS no和BUILD_GMSno有什么区别?? 如果带了谷歌,那么这个设备就差不多是国外定…...