# 游戏深度保护插件

《游戏深度保护》是一款小游戏代码保护工具，对明文游戏代码提供加固保护，包含了代码混淆、代码水印、代码锁以及部分对抗外挂的能力，能够较全面的保护游戏的健康运营。此外，也为代码的权益归属提供了辅助证据。

《游戏深度保护》提供了两种接入方式：微信开发者工具插件接入以及CLI工具接入，微信开发和工具插件安装好插件后开箱即用，上手简单，CLI工具允许开发者将游戏加固能力集成到开发者自身的开发流水线中，实现自动化加固，这两种接入方式获得的加固能力是等价的，详细接入方式如下。

## 插件接入说明

### 运行环境要求

下载并安装 1.06.2401020 或更高版本的微信开发者工具。

### 安装插件

#### 方式一

打开微信开发者工具，在菜单栏选择 "工具-插件-编辑器扩展" 或 "设置-扩展设置-编辑器扩展"，找到 "游戏深度保护" 点击 "获取" 进行安装。安装成功后，即可使用该插件功能。

![](https://res8.wxqcloud.qq.com.cn/wxdoc/3de9a897-3eac-4942-89d8-1660b558c8a9.png)

#### 方式二

打开微信开发者工具，直接点击 "扩展" 图标，在搜索栏中搜索 "codeFortify" 并进行安装。安装成功后，可以在工具栏看到下图圈出的 "游戏深度保护" 插件图标，即可使用该功能。

![](https://res8.wxqcloud.qq.com.cn/wxdoc/09a508ef-2832-4971-8301-866fcb2b2c69.png)

注意：安装成功后每次进入开发者工具都能看到此插件图标，插件 loading 最长需1分钟左右。若超时没有显示插件图标，请尝试退出开发者工具再重进，或者点击[咨询小助手](https://work.weixin.qq.com/kfid/kfcca4feec277f91616)。

### 插件使用流程

#### 方式一：交互式操作

![](https://res8.wxqcloud.qq.com.cn/wxdoc/3ccfaa93-df78-4d24-bef9-85182393e078.png)

step1 在需要进行加固的 js 代码文件上，右键选择 "添加到加固文件列表"，此时会弹出插件面板。（注意：如果未弹出插件面板可退出开发者工具重试或者使用方式二）。

step2 选择 "代码混淆" 以及对应的加固程度「高」、「中」、「低」；可以选择性地开启 "文件水印"。

step3 配置好加固文件后，点击 "全部加固" 即可开始加固（已经加固过的文件会自动忽略）。

#### 方式二：配置文件式操作

![](https://res8.wxqcloud.qq.com.cn/wxdoc/ce95aefe-1d91-460f-9760-00f7f5e0786f.png)

step1 点击 "初始配置文件"，创建一份加固配置文件。

step2 配置文件加固参数：files 是字符串数组，每个字符串是相对于项目目录的文件路径，一个字符串是一个待加固的js文件；level: 1低、2中、3高。

step3 编译/上传代码时将自动加固。

无论是方式一还是方式二，加固完成后，在原 js 文件（以 a.js 为例）所在目录下，会生成加固后的 js 文件（a.js）和对应的 sourcemap 文件（a.js.map）。建议开发者一起提审（可参考后面“操作注意事项/sourcemap”章节）。

### 插件常见问题排查

#### 插件交互异常

如以下偶然发生的情况：

- 插件图标不出现
- 右键添加文件到加固文件列表无反应
- 点击 "还原全部", "加固全部" 无反应

如果使用者遇到此情况，可以等待1分钟，如果不能解决问题，选择重新启动开发者工具。

#### 插件加固报错

根据提示，点击报错的那个文件名，查看它的报错信息。

![](https://res8.wxqcloud.qq.com.cn/wxdoc/714eee0c-02cc-440f-9c28-2dad3be2e655.png)

以下是几种常见的错误，括号内是输入代码出错的行列号：
- "语法错误, 或不支持的语法, 请检查: check_syntax_not_pass(tdz_access:a(3:14))"，意思是 "a 变量在暂时性死区内访问"，可根据行列号自行解决语法报错。
- "语法错误, 或不支持的语法, 请检查: check_syntax_not_pass(assign_to_const:99(3:5))"：意思是"将 99 赋值给了一个 const 变量"，可根据行列号自行解决语法报错。
- "逻辑错误，请重试一次 ..."：未知错误，一般是由于代码过大(>10M)引发的加固超时或者内存溢出错误。考虑拆分代码或降低加固级别，也可联系 "[小游戏助手](https://work.weixin.qq.com/kfid/kfcca4feec277f91616)"。

#### 线上运行报错

当加固后的代码线上报错时，使用 sourcemap 映射定位问题，步骤如下：[We分析/性能质量/JS分析](https://wedata.weixin.qq.com/mp2/js-error-list) 查看错误堆栈，此时已经是根据 sourcemap 映射后的错误信息了，开发者可自行定位。如有需要可以点击[咨询小助手](https://work.weixin.qq.com/kfid/kfcca4feec277f91616)。

![](https://res8.wxqcloud.qq.com.cn/wxdoc/00fbac0a-f797-4a3c-833a-da3e78d73656.png)

### 插件使用限制

#### 适用范围

目前只针对于微信小游戏的 js 代码文件，其他类型的文件（如 ts, txt, wasm 等）暂不支持。

#### 语法限制

- 代码中不要使用 function.toString，function.name，class.name，或者 Object.getOwnPropertyDescriptor(function, 'name') 等获得函数/类的名称/代码，由于代码加固会对名字（函数名/类名/属性名/变量名 等，不包括属性名）重命名，而且代码会被重写（函数体/类体/块 等），因此无法与加固前的名字/代码保持一致。如果使用者的代码依赖类似的逻辑，请修改代码，或者考虑不加固这部分代码。
- 不支持 eval、with 语法（小游戏本身不支持eval，会导致运行错误）。插件会检测这些语法，如果包含，那么加固失败。这样处理是因为这些语法无法进行准确的变量依赖分析，无法加固。
- 不支持在变量的暂时性死区（TDZ）里访问此变量，本身这样的语法会在运行时抛出异常。而本插件会尽量地静态检测这样的代码：如果在 let/const 构成的 TDZ 中访问此变量，那么加固失败，这是为了最大限度保护代码正确性。得到的错误信息如下：{"error_code":10018,"error_msg":"语法错误, 或不支持的语法, 请检查: check_syntax_not_pass(tdz_access:a(3:14))"}，请根据给出的变量名，括号里的行号、列号 自行修改代码。

```js
// TDZ 示例
function func(){
    console.info(a);    // 访问 TDZ 里的 a 会抛异常：ReferenceError
    const a = 100;
}
```

- 不支持对 const 变量赋值，本身这样的语法会在运行时抛出异常。而本插件会静态检测这样的代码：如果对 const 变量赋值，那么加固失败，这是为了最大限度保护代码正确性。得到的错误信息如下：{"error_code":10018,"error_msg":"语法错误, 或不支持的语法, 请检查: check_syntax_not_pass(assign_to_const:99(3:5))"}，请根据给出的变量名，括号里的行号、列号 自行修改代码。

```js
// 对 const 变量赋值示例
const a = 10;
a = 99;    // 对 const 变量赋值会抛异常：TypeError
```

- 全局作用域里的变量名字不会被混淆，这是因为使用者可能对代码进行 "部分加固" ，即：一部分加固，一部分不加固。插件会尽量保证加固代码与未加固代码合并后能正常运行。如果使用者确实需要对全局作用域名字混淆，那么只需要简单地使用一对括号将原代码括起来即可。

### 插件操作注意事项

#### 备份和恢复

使用者务必做好原代码备份工作，插件提供的代码备份功能只用于插件本身 debug 目的，不保证原代码不丢失，请不要依赖插件提供的代码备份/还原功能。

#### 不可更改已加固的代码

已加固的代码中有特殊标记，请不要更改已加固的代码，这可能会破坏这个标记。因为插件的加固和备份功能会依赖读出这个标记，做了以下判断：

- 已加固的代码不会再重复加固。
- 已加固的代码不会被备份（只会备份原代码）。

因此，更改已加固代码（比如继续开发），会导致以下问题：

- 新增的代码不会被加固，将失去保护。
- 新增的代码不会被备份，将可能丢失。

#### 自动忽略

- 点击 "全部加固" 按钮发起加固，已加固的文件会自动跳过，不会重复加固。
- 体积过小的 js （ 研发工具箱 -> 密钥管理"添加公钥（需要有小游戏管理员权限）

在项目根目录下（即推荐项目结构中的`GAME_CLI_ROOT_DIR`），新建 `id_rsa.conf` 文件（**当CLI工具尝试进行加固时，若检测到无id_rsa.conf，则会自动生成示例文件**），填写`appid`与私钥`private_key`

示例配置文件id_rsa.conf如下：

```
[appid]
wx4bf6c4476e094ea2
[private_key]
-----BEGIN RSA PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCykx4jO+f/IaeI
7S2n0cKWBRIqHG9GW8CNmNGTw55MPhq4cbdmbXvhF5YOW2cCSpQN0wjTMnY834wS
C4bKf79G5Dce3h8pWZb11XtdZ3xIWcxGDXWk5yqU08A62GhV8eAHfixRdHnCwkmT
VDBAJMBuPIx07rjTdaNjlVQunc5/BBSqbDyWuWyrlwdioBtCFRgtlQ2+x+pcN8dc
CBnFgNaQSrgQR/2kgcTud3DLSrjpeQY0uxpTkH8HDirc/z170gzlFhzDDgMuaja4
THOIQFbYAdqQzk/UmBMunERI43bi1EhP42V8bK4xmYBg4ugvI2EFKy/CqQZh2KWo
M03b0vlXAgMBAAECggEAZrrnzZE7B7UOsmDvsOmlLbTBHGS7Rxilj0/o0T7qJCZe
LNmOjmhRqc5tztiaS+Enh0RugUE4cBCSfhcPdhNDsL69Tw6tkpYtmEM18ygDgYPX
Vj2L/k/WWUPwBGpWq4q4PNdDG6EMIxSuSwlVsLDz2vcOPn7O6Sd11WYkxaSXFM4F
6VaP91VPg6Dp3BQWWZZnQDgMV42abUEkRLKY5/o+DmOmHmfiXYgX+TVEbl2yHbyd
8PGNFZjNMfQ1yKQG27XGoTHIxVC1FdMdPElqbCYO2RVCBWFhiIJJ+eyJrECvg1ny
3Yr0+9yVdTzraSCetE36HScR1oHxo3Kr3MA5hYQDsQKBgQD01KHbMtgzSkjHLlq0
de6JaTa93r9dM7TNnVt7rGXHo5eSTaFVxsThZRlqitYvDvdLDdi2Tr/IXbDC9Wc7
bnWkSeCBbP4rBUvq26NLZZAG7x+BiZ5UvcfF2yrX5nTB9mtLoAiMdjYwuy4gOczZ
cDb/gpfc/zLDktgROeeWUzZI2wKBgQC6uK9cT9cg2RD+e6Kj4iG+npDhfAr7fcxP
0S2eT7sgruX8/gYGK8C8QP11TKY9GipppYlb3Odmj3+oKQm34Qfsi3Qd2QvZXZq5
blE/WNRjTttmmpq2/6vvTYAVNqmsjSmC030m5qtFS9DbG3mTBikN5x13vsc9FPcb
A61bSQTsNQKBgQDGrdLXLdCS9sJOmMhUz6sOaYpZ1GwYt0AO55Z4qzNBKflzaXn2
QMI+YquYcShUseFuT8NAVnF9qMIrw7fNW9stwiHlBczQnpC3+xrtsXKKnfxEQ6hv
/kRThW+3uzTSLcZ5DyCQw9FABHXvAtjAzzZibNjOJ25LlVuvxvyvWcGgpwKBgHXH
Zz3oUr+yjEipvgZ37mcp6CODNf469LRoMslIa7YK/KGfrjcYRCO7LuVdqwKhzZ0E
QOeEKSaFX7W35Rxqwq/Pqzfkn9fFdoJ+9prpslbN+BD5dpv2HAL/tMosx8xC4qtW
MfxmxiQY1a5P31kBKsZNQUY2PcDmx5Pvttr87TD9AoGBANTMyEim1jevPkkCGVGq
I/50APmoprG/Hh23pBV3IaIH0bKHEzvk/I7IZSa6GipmOM9gKiDjGdAZ/Gh2VWNm
kRik7210vN3cMYVfxL4wtyRUDj3Bw0wRzbSZ1Ke5xVQ5BvGvp2p759x2eTddeai3
qWlA6ua9jgqpvEu6WSfmuqZg
-----END RSA PRIVATE KEY-----
```

#### 3. 添加加固配置文件

在小游戏目录下（即推荐项目结构中的`GAME_CLI_ROOT_DIR/minigame`），新建`code.fortify.config.json`文件（**当CLI工具尝试进行加固时，若检测到无code.fortify.config.json，则会自动生成示例文件**），指定需要加固的文件，示例配置文件如下：

```json
{
  "watermark": {
    "name": "水印保护",
    "files": [
      "file1.js"
    ]
  },
  "obfuscation": {
    "name": "代码混淆",
    "tasks": [
      {
        "level": 1,
        "files": [
            "file1.js"
        ]
      },
      {
        "level": 2,
        "files": [
            "file2.js"
        ]
      },
      {
        "level": 3,
        "files": [
            "file3.js"
        ]
      }
    ]
  }
}
```

json文件中的配置项说明：
- `obfuscation` 代表代码混淆配置项
- `level` 代表不同加固等级，1最低，3最高，不同加固等级会影响代码体积与性能，若无特殊要求，建议选择最高等级3
- `files` 代表待加固的文件路径（相对于小游戏工程目录）

#### 4. 加固

##### 方式一：npm自动调用

示例 `package.json` 文件如下，添加 `minigame-code-fortify-cli` 项，并在script下增加一个 `code-fortify`，加固时运行 `npm run code-fortify` 即可（可集成到编译的流水线中）

```json
{
  "name": "testmynpm",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "node main.js",
    "code-fortify": "minigame-code-fortify-cli-package-run"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "minigame-code-fortify-cli": "^1.0.1",
    "node-forge": "^1.3.1",
    "ts-node": "^10.9.2",
    "typescript": "^5.4.5"
  },
  "minigame_code_fortify_cli": {
    "workDir": "./minigame",
    "outputDir": "./output",
    "timeout_minute": 30
  }
}
```

`minigame-code-fortify-cli` 项定义如下

- `workDir` \[必须\]需要保护的目录
- `outputDir` \[必须\]输出目录
- `secretConfig` \[可选\]秘钥配置文件所在的目录（默认当前运行目录）
- `protectConfig` \[可选\]加固配置文件所在的目录（默认`workDir`）
- `timeout_minute` \[可选\]超时时间，单位为分钟，默认超时时间为10分钟

##### 方式二： api调用

在项目根目录下（即推荐项目结构中的`GAME_CLI_ROOT_DIR`），新建 `run_fortify.js` (开发者可自行命名加固文件入口)，内容如下：

```js
const {protect} = require('minigame-code-fortify-cli');

async function main() {
    //......省略其他代码
    await protect("minigame", "./output")
    //......省略其他代码
}
main()
```

运行后，在output目录下，将生成加固后的代码与对应的sourcemap文件。

### CLI工具使用注意事项

1. 【重要】请不要将密钥配置文件（`id_rsa.conf`）放在小游戏项目下，这会导致密钥文件一同被上传。
2. 【重要】不建议 `outputDir` 设置为 `workDir`，这会导致原有的js文件被覆盖，造成丢失。
3. 【重要】不可更改已加固的代码，已加固的代码中有特殊标记，更改可能会破坏这个标记，导致出现 bug 后无法查询到对应的源码位置。

### CLI工具常见问题排查

#### 1. 错误码:-1100002 加固达到限频

加固的代码次数达到上限，请明日再试。

#### 2. 错误码:-10222002 签名时间戳过期，请校准本地时钟

请检查本地时间戳是否正确，如果时间戳不正确，请检查本地时间是否正确。

#### 3. 错误码:-10000404 MP平台未配置公钥

请在MP平台配置公钥，配置文档：[怎样生成公钥](https://docs.qq.com/doc/DWW9DbmxxSmhNaXZq)。

#### 4. 错误码:-10222003 appid有误

请检查id_rsa.conf文件中appid配置。

#### 5. 错误码:-10000401 密钥检验失败，请检查私钥是否正确

验签失败，请检查公钥私钥是否匹配。

#### 6. 错误码:-20214006 请求过快

#### 7. 错误码 10018 语法错误，或不支持的语法

代码语法有误，请根据报错信息排查

#### 8. 加固超时

超时复用：对于未压缩/混淆的 js，4M以下一般会在几分钟内完成。如果某个文件的加固超时了，后台服务器仍然在继续加固。只要这个文件没有更改，那么在 24h之内 再次发起加固，会自动复用前面加固的结果：当某个文件已加固完成时，再次发起加固会很快完成。

不复用：如果使用者不想复用已加固的结果，则可以通过更改文件（例如在代码某个地方加个空格），使文件 md5 变化，再发起加固此文件，就会重新加固。

### CLI工具使用限制

参考插件使用限制

### 如何生成公钥

分为如下两步，首先生成RSA密钥对，然后转化为PEM格式的公钥
#### 1. 生成RSA密钥对
要生成RSA密钥对，可以使用ssh-keygen命令，-t 指定RSA加密算法，-m指定私钥为PEM格式，并通过 -b 选项选择所需的密钥长度。以下命令将生成一个2048长度的RSA密钥对

```bash
ssh-keygen -t rsa -m pem -b 2048 -C "your_email@example.com"
 ```

默认存储位置
- Linux: /home/YOU/.ssh/
- Windows: c:\Users\YOU\.ssh\
- Mac: /Users/YOU/.ssh/

公钥文件: id_rsa.pub
私钥文件: id_rsa

使用时，需打开相应的文件，复制出私钥，并放置到相关的接口中使用。

#### 2. 转化为PEM格式公钥
ssh-keygen 命令默认生成的公钥格式是OpenSSH格式，你需要将其转化为PEM格式进行使用
```bash
ssh-keygen -f id_rsa.pub -e -m PKCS8
```

该命令执行完会生成相应的PEM格式的公钥，即可复制并放置到相关的接口中使用。
注意：复制公钥时，需要连同前后注释一起（BEGIN PUBLIC KEY/END PUBLIC KEY所在行）
公钥示例：

```
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAshczq5hJ21OWcBmhaUKR
FyYGIt+CoPk04qwiylC7DSgHsc7FCfwNE130l5Vxu1Mrq2cRQIXdMKu0iVUGzs5+
vOjjrCk/4017qed这只是个示例，不要复制这个Rhch0PV8Yvy9jk37M9
EsPMETbUZ4XhBg6XvnKOi6inNRLkng78d1KbmRouA/lcjV9xylXGY5MOAQ/SjHKe
SEca+oMmaN+RmYY0zJwImkUDSovPpiIwIhQnN0yPbPhDTppFQzdISJoNbkQi9K8A
9mV/GsL5BwE/8eR45xOc/h+UllWBL1RYXwrC5jol9Iury4kHoHUFwb2QNtL7ZFRp
8QIDAQAB
-----END PUBLIC KEY-----
```
