# 游戏深度保护插件

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

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

# 插件接入说明

# 运行环境要求

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

# 安装插件

# 方式一

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

# 方式二

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

注意:安装成功后每次进入开发者工具都能看到此插件图标,插件 loading 最长需1分钟左右。若超时没有显示插件图标,请尝试退出开发者工具再重进,或者点击咨询小助手

# 插件使用流程

# 方式一:交互式操作

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

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

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

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

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

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

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

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

# 插件常见问题排查

# 插件交互异常

如以下偶然发生的情况:

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

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

# 插件加固报错

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

以下是几种常见的错误,括号内是输入代码出错的行列号:

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

# 线上运行报错

当加固后的代码线上报错时,使用 sourcemap 映射定位问题,步骤如下:We分析/性能质量/JS分析 查看错误堆栈,此时已经是根据 sourcemap 映射后的错误信息了,开发者可自行定位。如有需要可以点击咨询小助手

# 插件使用限制

# 适用范围

目前只针对于微信小游戏的 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))"},请根据给出的变量名,括号里的行号、列号 自行修改代码。
// 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))"},请根据给出的变量名,括号里的行号、列号 自行修改代码。
// 对 const 变量赋值示例
const a = 10;
a = 99;    // 对 const 变量赋值会抛异常:TypeError
  • 全局作用域里的变量名字不会被混淆,这是因为使用者可能对代码进行 "部分加固" ,即:一部分加固,一部分不加固。插件会尽量保证加固代码与未加固代码合并后能正常运行。如果使用者确实需要对全局作用域名字混淆,那么只需要简单地使用一对括号将原代码括起来即可。

# 插件操作注意事项

# 备份和恢复

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

# 不可更改已加固的代码

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

  • 已加固的代码不会再重复加固。
  • 已加固的代码不会被备份(只会备份原代码)。

因此,更改已加固代码(比如继续开发),会导致以下问题:

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

# 自动忽略

  • 点击 "全部加固" 按钮发起加固,已加固的文件会自动跳过,不会重复加固。
  • 体积过小的 js (<20k)平台会忽略"文件水印"这个选项,不加水印。如使用者需要对小 js 加水印,可以将这个小 js 合并入其它 js 一起加固。如有疑问,可点击咨询小助手

# 加固超时与复用

  • 超时复用:对于未压缩/混淆的 js,4M以下一般会在几分钟内完成。如果某个文件的加固超时了,可以继续等待(可以退出插件或者退出开发者工具,均无影响),后台仍然在继续加固。只要这个文件没有更改,那么在 24h之内 再次发起加固,会自动复用前面加固的结果:当某个文件已加固完成时,再次发起加固会很快完成。
  • 不复用:如果使用者不想复用已加固的结果,则可以通过更改文件(例如在代码某个地方加个空格),使文件 md5 变化,再发起加固就会重新加固。

# sourcemap

  • sourcemap(后面简称为 map)用于当线上报错时,辅助使用者定位报错位置。可以在 we 分析里直观看到映射后的报错信息。
  • 加固完成后,会得到加固的 js 文件和对应的 js.map 文件,请保持将它们放在一起(相同目录下),一并提审。map 文件对于代码包大小的计算是豁免的,不会因为它过大而导致提审受阻;map 文件在线上运行时不会被下发到客户端上。
  • 发起加固时,如果 js 文件有对应的 map 文件(例如 a.js 和 a.js.map),本插件会吸收已有的 map 文件,加固后生成的 map 会包含原 map 文件的内容(也即会映射到最原始的 js 文件上去)。如果不想使用这个已有 map 可以删除它,再发起加固(如果已经加固过,那么可以更改一下 a.js 再重新加固)。
  • sourcemap 是敏感信息,使用者务必注意不要泄露。

# 提审上传

上传代码包时,插件会检查加固列表中的文件,是否全部完成了加固。全部完成时会静默上传,如果存在未完成加固的文件,插件会提醒,请使用者加固后再上传,避免漏掉保护某个文件。

# 发布建议

  • 加固完成后一定要进行充分的测试和真机云测,请确保游戏运行正常后再进行发布。
  • 初次加固,或者开发者的代码有较大变更后再次加固,建议走灰度发布。

# CLI接入说明

# 运行环境要求

NodeJs 版本不低于 14。

# 推荐项目结构

GAME_CLI_ROOT_DIR
    ├─minigame # 小游戏工程所在目录
    │ ├─......# 小游戏项目文件
    │ └─code.fortify.config.json # 加固配置,指定加固目标文件及等级信息
    ├─output # 加固后代码输出目录
    ├─run_fortify.js # 在该文件中 调用本cli提供的加固接口
    └─id_rsa.conf # cli配置文件,在其中填写密钥与appid

# 使用流程

# 1. 安装npm包

在开发者原有的CLI项目(非小游戏项目)中运行如下命令

npm i minigame-code-fortify-cli

# 2. 添加CLI配置文件

请参考 怎样生成公钥 生成专属公私钥对,再前往MP后台"研发工具箱 -> 密钥管理"添加公钥(需要有小游戏管理员权限)

在项目根目录下(即推荐项目结构中的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,则会自动生成示例文件),指定需要加固的文件,示例配置文件如下:

{
  "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 即可(可集成到编译的流水线中)

{
  "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 (开发者可自行命名加固文件入口),内容如下:

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. 错误码:-10000401 密钥检验失败,请检查密钥是否正确

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

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

加固的代码token数达到上限,请明日再试

# 3. 错误码 10018 语法错误,或不支持的语法

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

# 4. 加固超时

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

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

# 5. 没有需要加固的文件

请在code.fortify.config.json文件中,指定需要加固的文件。

# 6. appid is required

请在id_rsa.conf 中配置appid

# 7. private_key is required

请在id_rsa.conf 中配置private_key

# CLI工具使用限制

参考插件使用限制