某音直播弹幕web端js逆向分析----protobuf实战及工具介绍

查看 187|回复 11
作者:prince_cool   
某音直播弹幕web端js逆向分析----protobuf实战及工具介绍
声明
        本文章中所有内容仅供学习交流,相关链接做了脱敏处理,若有侵权,请联系我立即删除!
一、前言
       前面的文章应该可以让大家掌握好protobuf吧,只要抓住关键词就行了。前面文章我也提到了某音直播弹幕也是采用protobuf格式传输的,那我们这篇文章就来看看,实战一下能不能获取到信息。同时我也会放出我写好的ast工具,对基本的protobuf格式应该都能做到处理,大佬们也可以在我这个基础上修改,或者推出更适合的工具。
二、前情提要
       一般的直播弹幕都采用websocket协议传输数据,因为要实时传输,ws协议只用一次建立握手连接,就可持续实时通信。

       基于这点,我们可以很容易的就找到我们要的地址,我们开始分析吧!
三、目标网站
任意直播间
四、开始分析
       如果你不信我的前情提要,觉得太虚了,有没有可能走http协议呢,那我们可以打开f12,打开fiddler抓包工具(为后面分析ws连接做准备),然后输入我们的目标网站地址(只需https:// live.***.com/数字),抓包看看吧。
       获取到弹幕的一瞬间,我们的fiddler就可以关了。我们看看浏览器的f12内容,进度条一直在跑,因为数据很多嘛.

       弹幕数据肯定是那些零星的,蓝色分散的点,我们括一小段看看

       我们看到数据类型大多是图片,不是我们需要的啊,唯独websocket类型数据是一直贯穿的,即使暂停了直播,弹幕还是会有,webscoket传输的数据还是没停下来。所以我们要找的就是websocket协议传输的数据。

点到ws,然后看到唯一链接wss开头,这就是websocket协议的。
知识点:(绿色表示客户端消息发给服务器,红色表示服务器下发数据给客户端
我们可以从堆栈入手,找到发包点。

       其实我们从名字也大概可以猜到可能是这两个,我一开始是直接看这两个的也能找到点,我们还是从最后一个发包栈(m)看起吧。

我们去搜索一下,学习一下这个是怎么一回事,t参数是什么意思。
来到mdn网站,强烈推荐:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket
           WebSocket 对象提供了用于创建和管理 WebSocket 连接,以及可以通过该连接发送和接收数据的 API。
                              使用 WebSocket() 构造函数来构造一个 WebSocket。
                              WebSocket(url ,protocols(可选))
                              返回一个 `WebSocket` 对象。
       所以说这个t是一个url链接,应该就是我们看到的那个wss链接了,我们下个断点,来分析分析。

确实是。我们回到原来那个mdn网站,可以往下看,可以看到事件这个栏目。

       我们断点下方不正是有这几个事件关键词吗,我们如果刷新界面,它刚开始连接时,如果成功,应该会触发open事件,我们都可以下个断点看看,主要先看看open,这几个关键词要记住,解websocket协议的核心
       我们可以看到它先走了onmessage,我们在mdn查一下 addEventListener:addEventListener(type,listener)其中 listener是对象,相当于监听它。我们跟过去看看这个对象,函数。

       相当于每个都是监听里面这一堆东西,各种判断处理,先给创建的websocket对象绑定好了各种方法,再来处理数据。
       所以他绑定的方法我们要跟过去下断点,都绑定后后,我们发现,他来到了onopen,就是我们最开始提到的,连接成功要处理的事件。

核心是这个 this.ping(),其他都是一些赋值操作,我们跟进ping函数

       是不是看到了我们protobuf格式的老熟人关键词:serializeBinary,然后通过socket的send发给服务器。
代码分析:
const e = Math.max(1e4, Number(this.heartbeatDuration)); 得到e是10000。
if (this.client && 1 === this.client.socket.readyState) {   //readyState准备状态,一开始是1嘛,可以理解,成立
                    const e = new p.PushFrame;    //实例化proto对象
                    e.setPayloadType("hb"),       //设置属性值
                    this.client.socket.send(e.serializeBinary())    //序列化后发送
    }
this.pingTimer = window.setTimeout((()=>{
                    this.pingStarted && this.ping()
                }
), e)  //setTimeout:超时就执行ping,毫秒。相当于这里就是10秒ping一次嘛

       可能是太久了,他发了两次,对吧,所以说这里就是一个点,我们要构造ping值,然后连接成功时候发一下,然后就能得到服务器返回的值了。

我们回到这里,先搞定这里再分析onmessage的,构造这个proto文件吧。

搜索 proto.pushproto.PushFrame.deserializeBinary

当然可以一个个慢慢扣,这里比较少。
①js转proto文件工具分享(只可解标准protobuf):
       这里我采用我自己写的ast工具进行处理,把js尽可能转成proto文件,这里是我想分享的一点。
       因为之前不是用渔歌的ast脚本加个try,逻辑改一下就搞定某方了,我就想,我能不能自己写一个呢,然后小学了一下ast,用的最基础的语法,我直接放出来吧,供大家使用,改良;你们写的肯定比我好。
//babel库及文件模块导入
const fs = require('fs');
//babel库相关,解析,转换,构建,生产
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const types = require("@babel/types");
const generator = require("@babel/generator").default;
//读取文件
let encode_file = "./test.js", decode_file = "./decode_result.proto"; //自己可以调整路径
if (process.argv.length > 2) {
    encode_file = process.argv[2];
}
if (process.argv.length > 3) {
    decode_file = process.argv[3];
}
//some function code
//判断类型
function get_id_type(id_type) {
    switch (id_type) {
        case "readString":
            id_type = "string";
            break;
        case "readDouble":
            id_type = "double";
            break;
        case "readInt32":
            id_type = "int32";
            break;
        case "readInt64":
            id_type = "int64";
            break;
        case "readFloat":
            id_type = "float";
            break;
        case "readBool":
            id_type = "bool";
            break;
        case "readPackedInt32":
            id_type = "int32";
            break;
        case "readBytes":
            id_type = "bytes";
            break;
        case "readEnum":
            id_type = "readEnum";
            break;
        case "readPackedEnum":
            id_type = "readPackedEnum";
            break;
        case "readUint64String":
            id_type = "uint64";
            break;
        case "readInt64String":
            id_type = "int64";
            break;
        case "readUint32":
            id_type = "uint32";
            break;
        case "readUint64":
            id_type = "uint64";
            break;
    }
    return id_type
}
//首字母大写,处理
function titleCase(str) {
    newStr = str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase();
    return newStr;
}
//读取文件
let jscode = fs.readFileSync(encode_file, {encoding: "utf-8"});
//转换为ast树
let ast = parser.parse(jscode);
// console.log(ast)
text = 'syntax = "proto3";\n'
//处理message插件
const visitor =
    {
        //TODO  write your code here!
        AssignmentExpression(path, scope) {
            // 外面大模型是没问题了,主要是里面细节
            if (path.node.left.property) {
                if (path.node.left.type == 'MemberExpression' &&
                    path.node.left.property.type == 'Identifier' &&
                    path.node.left.property.name == 'deserializeBinaryFromReader') {
                    //处理message名字
                    object_name = path.node.left.object.property.name
                    left_node = path.node.left
                    codename_list = generator(left_node).code.split('.');
                    codename_list.shift()
                    codename_list.pop()
                    object_name = codename_list.join('_');
                    // console.log(object_name);
                    text = text + 'message ' + object_name + '{\n'
                    //处理switch语句
                    if (path.node.right.type == 'FunctionExpression') {
                        path.traverse({
                                SwitchStatement(path2) {
                                    //case语句
                                    cases_list = path2.node.cases
                                    // console.log(generator(path2.node).code);
                                    // try {
                                    for (i = 0; i  {});
traverse(ast, visitor);
console.log('message部分完成!!!')
// // fs.writeFile('douyin_test_message完成.proto', text, (err) => {});
//
//
// // console.log(text);
//
fs.writeFile(decode_file, text, (err) => {});
首先,为什么能写出这个插件,我基于的是之前文章讲过的:(不要编辑它

所以我就看了一下原始创建的js和某方和某音的结构,发现有相同点:

proto.SearchService.SearchResponse.deserializeBinaryFromReader = function(e, t) {
  for (; t.nextField() && !t.isEndGroup(); ) {
    switch (t.getFieldNumber()) {
      case 1:
        var r = t.readBool();
        e.setStatus(r);
        break;
      case 2:
        r = t.readString();
        e.setMessage(r);
        break;
      case 3:
        r = t.readInt64();
        e.setCount(r);
        break;
      case 4:
        r = new s.Resource;
        t.readMessage(r, s.Resource.deserializeBinaryFromReader),
          e.addResources(r);
        break;
      default:
        t.skipField()
    }
  }
  return e
}
proto.pushproto.PushHeader.deserializeBinaryFromReader = function(e, t) {
  for (; t.nextField() && !t.isEndGroup(); ) {
    switch (t.getFieldNumber()) {
      case 1:
        var i = t.readString();
        e.setKey(i);
        break;
      case 2:
        i = t.readString();
        e.setValue(i);
        break;
      default:
        t.skipField()
    }
  }
  return e
}
我们放到网站上看看他们的结构:https://astexplorer.net/

       发现都是 AssignmentExpression类型对象,然后有左:MemberExpression类型节点,它调用的方法名是deserializeBinaryFromReader
定位:我就是先以这个确定message对象:

处理:然后再处理细节,展开后:

       先把message名字搞定:
       用babel库的generator将左节点转成字符串源码,然后以"."分割去掉头(proto)尾(deserializeBinaryFromReader)
       然后用"_"拼接,就搞定了message对象名字。我拿text变量来连接这些数据,下面就是处理右边的function了。

       直接通过babel库的traverse定位到switch语句块,里面又有好几种情况,大家可以自行分析,我们可以通过慢慢调试,发现缺少的就慢慢补充起来,我基本上把可能的情况都写上去了,可是有个地方需要大家帮忙修改或者采用更好的方法优化一下吧。

       如果报错可以在pycharm,debugger下看到报错细节,然后再进行调整,我发的基本上解决了。
下面是一些特殊情况,因为没保存之前的截图,临时找了一下:

       其实下面也写了处理enum类型的模块,因为本身转化成js后是不可逆的,只能尽量还原,不可逆的点就是这个enum类型对象,你可以看回转化后的js,是没有一一对应关系的,所以无法关联到枚举类型变量,所以我就关了,没使用,想看枚举类型也能打开来看。

       对了,上面有封装了一个函数,是处理数据类型的,如果你遇到了我这工具没有的类型,你自己可以添加到case语句里面,一看就懂。

代码大概介绍了一下,我们就拿上面拿出来的某方,某音的列子来试试效果。

       一下子就处理完成,效果蛮好的,红色报错之前文章也说过原因,也教过怎么找这个对象,可以看回之前的文章。
②回到某音ping分析的地方:

       我们跟进了那个方法的js文件,我们直接复制下来,丢到工具里面:

发现我们要的其实就是我圈起来的这两个,我们可以单独拿出来。
syntax = "proto3";
message pushproto_PushHeader{
string key=1;
string value=2;
}
message pushproto_PushFrame{
uint64 seqid=1;
uint64 logid=2;
uint64 service=3;
uint64 method=4;
repeated pushproto_PushHeader headers=5;
string payloadencoding=6;
string payloadtype=7;
bytes payload=8;
}
然后试试能不能得到想要的结果。(python语句看回我补充篇)

       可以看到我们发过去后,服务器给我们发送了一个数据,猜测应该会在onMessage收到,然后处理,放行,果然来了。

我们跟过去看看吧:

       又看到了我们的老朋友:反序列化的函数(deserializeBinary),所以我们可以采取构造proto文件处理,也可以使用python库blackboxprotobuf处理。因为我写了工具,就直接使用第一种方式,构造proto文件,也更直观。
我们先大概分析一下代码,再用工具去构造吧:
先看第一个语句:
const i = p.PushFrame.deserializeBinary(e.data) //反序列化得到数组对象
    , o = r.Response.deserializeBinary(
  //自执行
  function(e) {
      for (const t of Object.values(e.getHeadersList()))
          //判断是否有compress_type和gzip值在i里面
          if ("compress_type" === t.getKey() && "gzip" === t.getValue())
              return !0; //返回真
      return !1   //返回假
  }(i)
//选择表达式,如果真就走第一个,假就走后面那个,得到的值再反序列化得到o,这里应该都是压缩过的,所以都会走第一个
? (0,n.ec)(i.getPayload()) : i.getPayload_asU8());
第二段:
//如果需要ack做的事情 自行百度什么是websocket的ack
if (o.getNeedAck()) {
     let e = o.getInternalExt()
     , t = o.getCursor();
     (i.getHeadersList() || []).forEach((i=>{
   "im-internal_ext" === (null == i ? void 0 : i.getKey()) && (e = null == i ? void 0 : i.getValue()),
   "im-cursor" === (null == i ? void 0 : i.getKey()) && (t = null == i ? void 0 : i.getValue())
                        }
                        )),
    this.internalExt = e,
    this.cursor = t;
    //下面才是核心
    const a = new p.PushFrame;
    a.setPayloadType("ack"),
    a.setPayload(
    //自执行:相当于把internalext传进去处理然后当成是payload的值
    function(e) {
           const t = [];
           for (const i of e) {
           const e = i.charCodeAt(0);
           e > 6)),
           t.push(128 + (63 & e))) : e > 12)),
           t.push(128 + (e >> 6 & 63)),
           t.push(128 + (63 & e)))
      }
       return Uint8Array.from(t)
    }(e)
    ),
    a.setLogid(i.getLogid()),
    this.client.socket.send(a.serializeBinary())
}
最后
if ("msg" === i.getPayloadType() && (this.info("fetchSocketServer socket response: ", (()=>o.toObject())),
        this.emit(o)),//这里就是把值展示出来了,里面处理值
        "close" === i.getPayloadType())
        return t(new Error("close by payloadtype"))
}
       大概理清楚了onmessage的思路,先反序列化得到我们要的数据给后面展示,o就是我们要的数据对象,然后如果需要ack就发,最后通过emit函数处理展示值。
我们看看实际情况:

所以先通过n.ec函数处理,然后再来反序列化。
我们复制刚刚返回的,也能得到这个序列。


我们进去n.ec函数,然后移到最上面,发现是webpack打包的,然后我们可以看到


刚刚好Qe就是我们进来的位置,也就是这个函数,其实名字已经暴露了是解压,我们也可以把代码扣下来使用。
去掉开头结尾就好了:
function i(t) {
    let e = t.length;
    for (; --e >= 0; )
        t[e] = 0
}
const n = 256
  , r = 286
  , s = 30
  , l = 15
  , o = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0])
  , h = new Uint8Array([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13])
  , d = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7])
  , _ = new Uint8Array([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15])
  , f = new Array(576);
i(f);
const c = new Array(60);
i(c);
const u = new Array(512);
i(u);
const w = new Array(256);
i(w);
const b = new Array(29);
i(b);
const g = new Array(s);
function m(t, e, a, i, n) {
    this.static_tree = t,
    this.extra_bits = e,
    this.extra_base = a,
    this.elems = i,
    this.max_length = n,
    this.has_stree = t && t.length
}
let p, k, v;
function y(t, e) {
    this.dyn_tree = t,
    this.max_code = 0,
    this.stat_desc = e
}
i(g);
const x = t=>t >> 7)]
  , z = (t,e)=>{
    t.pending_buf[t.pending++] = 255 & e,
    t.pending_buf[t.pending++] = e >>> 8 & 255
}
  , A = (t,e,a)=>{
    t.bi_valid > 16 - a ? (t.bi_buf |= e > 16 - t.bi_valid,
    t.bi_valid += a - 16) : (t.bi_buf |= e {
    A(t, a[2 * e], a[2 * e + 1])
}
  , R = (t,e)=>{
    let a = 0;
    do {
        a |= 1 & t,
        t >>>= 1,
        a  0);
    return a >>> 1
}
  , Z = (t,e,a)=>{
    const i = new Array(16);
    let n, r, s = 0;
    for (n = 1; n {
    let e;
    for (e = 0; e {
    t.bi_valid > 8 ? z(t, t.bi_buf) : t.bi_valid > 0 && (t.pending_buf[t.pending++] = t.bi_buf),
    t.bi_buf = 0,
    t.bi_valid = 0
}
  , D = (t,e,a,i)=>{
    const n = 2 * e
      , r = 2 * a;
    return t[n] {
    const i = t.heap[a];
    let n = a {
    let i, r, s, l, d = 0;
    if (0 !== t.last_lit)
        do {
            i = t.pending_buf[t.d_buf + 2 * d] {
    const a = e.dyn_tree
      , i = e.stat_desc.static_tree
      , n = e.stat_desc.has_stree
      , r = e.stat_desc.elems;
    let s, o, h, d = -1;
    for (t.heap_len = 0,
    t.heap_max = 573,
    s = 0; s > 1; s >= 1; s--)
        O(t, a, s);
    h = r;
    do {
        s = t.heap[1],
        t.heap[1] = t.heap[t.heap_len--],
        O(t, a, 1),
        o = t.heap[1],
        t.heap[--t.heap_max] = s,
        t.heap[--t.heap_max] = o,
        a[2 * h] = a[2 * s] + a[2 * o],
        t.depth[h] = (t.depth >= t.depth[o] ? t.depth : t.depth[o]) + 1,
        a[2 * s + 1] = a[2 * o + 1] = h,
        t.heap[1] = h++,
        O(t, a, 1)
    } while (t.heap_len >= 2);
    t.heap[--t.heap_max] = t.heap[1],
    ((t,e)=>{
        const a = e.dyn_tree
          , i = e.max_code
          , n = e.stat_desc.static_tree
          , r = e.stat_desc.has_stree
          , s = e.stat_desc.extra_bits
          , o = e.stat_desc.extra_base
          , h = e.stat_desc.max_length;
        let d, _, f, c, u, w, b = 0;
        for (c = 0; c  h && (c = h,
            b++),
            a[2 * _ + 1] = c,
            _ > i || (t.bl_count[c]++,
            u = 0,
            _ >= o && (u = s[_ - o]),
            w = a[2 * _],
            t.opt_len += w * (c + u),
            r && (t.static_len += w * (n[2 * _ + 1] + u)));
        if (0 !== b) {
            do {
                for (c = h - 1; 0 === t.bl_count[c]; )
                    c--;
                t.bl_count[c]--,
                t.bl_count[c + 1] += 2,
                t.bl_count[h]--,
                b -= 2
            } while (b > 0);
            for (c = h; 0 !== c; c--)
                for (_ = t.bl_count[c]; 0 !== _; )
                    f = t.heap[--d],
                    f > i || (a[2 * f + 1] !== c && (t.opt_len += (c - a[2 * f + 1]) * a[2 * f],
                    a[2 * f + 1] = c),
                    _--)
        }
    }
    )(t, e),
    Z(a, d, t.bl_count)
}
  , N = (t,e,a)=>{
    let i, n, r = -1, s = e[1], l = 0, o = 7, h = 4;
    for (0 === s && (o = 138,
    h = 3),
    e[2 * (a + 1) + 1] = 65535,
    i = 0; i {
    let i, n, r = -1, s = e[1], l = 0, o = 7, h = 4;
    for (0 === s && (o = 138,
    h = 3),
    i = 0; i {
    A(t, 0 + (i ? 1 : 0), 3),
    ((t,e,a,i)=>{
        S(t),
        i && (z(t, a),
        z(t, ~a)),
        t.pending_buf.set(t.window.subarray(e, e + a), t.pending),
        t.pending += a
    }
    )(t, e, a, !0)
}
;
var C = (t,e,a,i)=>{
    let r, s, l = 0;
    t.level > 0 ? (2 === t.strm.data_type && (t.strm.data_type = (t=>{
        let e, a = 4093624447;
        for (e = 0; e >>= 1)
            if (1 & a && 0 !== t.dyn_ltree[2 * e])
                return 0;
        if (0 !== t.dyn_ltree[18] || 0 !== t.dyn_ltree[20] || 0 !== t.dyn_ltree[26])
            return 1;
        for (e = 32; e {
        let e;
        for (N(t, t.dyn_ltree, t.l_desc.max_code),
        N(t, t.dyn_dtree, t.d_desc.max_code),
        L(t, t.bl_desc),
        e = 18; e >= 3 && 0 === t.bl_tree[2 * _[e] + 1]; e--)
            ;
        return t.opt_len += 3 * (e + 1) + 5 + 5 + 4,
        e
    }
    )(t),
    r = t.opt_len + 3 + 7 >>> 3,
    s = t.static_len + 3 + 7 >>> 3,
    s {
        let n;
        for (A(t, e - 257, 5),
        A(t, a - 1, 5),
        A(t, i - 4, 4),
        n = 0; n {
        B || ((()=>{
            let t, e, a, i, n;
            const _ = new Array(16);
            for (a = 0,
            i = 0; i >= 7; i (t.pending_buf[t.d_buf + 2 * t.last_lit] = e >>> 8 & 255,
    t.pending_buf[t.d_buf + 2 * t.last_lit + 1] = 255 & e,
    t.pending_buf[t.l_buf + t.last_lit] = 255 & a,
    t.last_lit++,
    0 === e ? t.dyn_ltree[2 * a]++ : (t.matches++,
    e--,
    t.dyn_ltree[2 * (w[a] + n + 1)]++,
    t.dyn_dtree[2 * x(e)]++),
    t.last_lit === t.lit_bufsize - 1),
    _tr_align: t=>{
        A(t, 2, 3),
        E(t, 256, f),
        (t=>{
            16 === t.bi_valid ? (z(t, t.bi_buf),
            t.bi_buf = 0,
            t.bi_valid = 0) : t.bi_valid >= 8 && (t.pending_buf[t.pending++] = 255 & t.bi_buf,
            t.bi_buf >>= 8,
            t.bi_valid -= 8)
        }
        )(t)
    }
};
var M = (t,e,a,i)=>{
    let n = 65535 & t | 0
      , r = t >>> 16 & 65535 | 0
      , s = 0;
    for (; 0 !== a; ) {
        s = a > 2e3 ? 2e3 : a,
        a -= s;
        do {
            n = n + e[i++] | 0,
            r = r + n | 0
        } while (--s);
        n %= 65521,
        r %= 65521
    }
    return n | r {
    let t, e = [];
    for (var a = 0; a >> 1 : t >>> 1;
        e[a] = t
    }
    return e
}
)());
var j = (t,e,a,i)=>{
    const n = K
      , r = i + a;
    t ^= -1;
    for (let s = i; s >> 8 ^ n[255 & (t ^ e)];
    return -1 ^ t
}
  , P = {
    2: "need dictionary",
    1: "stream end",
    0: "",
    "-1": "file error",
    "-2": "stream error",
    "-3": "data error",
    "-4": "insufficient memory",
    "-5": "buffer error",
    "-6": "incompatible version"
}
  , Y = {
    Z_NO_FLUSH: 0,
    Z_PARTIAL_FLUSH: 1,
    Z_SYNC_FLUSH: 2,
    Z_FULL_FLUSH: 3,
    Z_FINISH: 4,
    Z_BLOCK: 5,
    Z_TREES: 6,
    Z_OK: 0,
    Z_STREAM_END: 1,
    Z_NEED_DICT: 2,
    Z_ERRNO: -1,
    Z_STREAM_ERROR: -2,
    Z_DATA_ERROR: -3,
    Z_MEM_ERROR: -4,
    Z_BUF_ERROR: -5,
    Z_NO_COMPRESSION: 0,
    Z_BEST_SPEED: 1,
    Z_BEST_COMPRESSION: 9,
    Z_DEFAULT_COMPRESSION: -1,
    Z_FILTERED: 1,
    Z_HUFFMAN_ONLY: 2,
    Z_RLE: 3,
    Z_FIXED: 4,
    Z_DEFAULT_STRATEGY: 0,
    Z_BINARY: 0,
    Z_TEXT: 1,
    Z_UNKNOWN: 2,
    Z_DEFLATED: 8
};
const {_tr_init: G, _tr_stored_block: X, _tr_flush_block: W, _tr_tally: q, _tr_align: J} = H
  , {Z_NO_FLUSH: Q, Z_PARTIAL_FLUSH: V, Z_FULL_FLUSH: $, Z_FINISH: tt, Z_BLOCK: et, Z_OK: at, Z_STREAM_END: it, Z_STREAM_ERROR: nt, Z_DATA_ERROR: rt, Z_BUF_ERROR: st, Z_DEFAULT_COMPRESSION: lt, Z_FILTERED: ot, Z_HUFFMAN_ONLY: ht, Z_RLE: dt, Z_FIXED: _t, Z_DEFAULT_STRATEGY: ft, Z_UNKNOWN: ct, Z_DEFLATED: ut} = Y
  , wt = 258
  , bt = 262
  , gt = 103
  , mt = 113
  , pt = 666
  , kt = (t,e)=>(t.msg = P[e],
e)
  , vt = t=>(t  4 ? 9 : 0)
  , yt = t=>{
    let e = t.length;
    for (; --e >= 0; )
        t[e] = 0
}
;
let xt = (t,e,a)=>(e {
    const e = t.state;
    let a = e.pending;
    a > t.avail_out && (a = t.avail_out),
    0 !== a && (t.output.set(e.pending_buf.subarray(e.pending_out, e.pending_out + a), t.next_out),
    t.next_out += a,
    e.pending_out += a,
    t.total_out += a,
    t.avail_out -= a,
    e.pending -= a,
    0 === e.pending && (e.pending_out = 0))
}
  , At = (t,e)=>{
    W(t, t.block_start >= 0 ? t.block_start : -1, t.strstart - t.block_start, e),
    t.block_start = t.strstart,
    zt(t.strm)
}
  , Et = (t,e)=>{
    t.pending_buf[t.pending++] = e
}
  , Rt = (t,e)=>{
    t.pending_buf[t.pending++] = e >>> 8 & 255,
    t.pending_buf[t.pending++] = 255 & e
}
  , Zt = (t,e,a,i)=>{
    let n = t.avail_in;
    return n > i && (n = i),
    0 === n ? 0 : (t.avail_in -= n,
    e.set(t.input.subarray(t.next_in, t.next_in + n), a),
    1 === t.state.wrap ? t.adler = M(t.adler, e, n, a) : 2 === t.state.wrap && (t.adler = j(t.adler, e, n, a)),
    t.next_in += n,
    t.total_in += n,
    n)
}
  , Ut = (t,e)=>{
    let a, i, n = t.max_chain_length, r = t.strstart, s = t.prev_length, l = t.nice_match;
    const o = t.strstart > t.w_size - bt ? t.strstart - (t.w_size - bt) : 0
      , h = t.window
      , d = t.w_mask
      , _ = t.prev
      , f = t.strstart + wt;
    let c = h[r + s - 1]
      , u = h[r + s];
    t.prev_length >= t.good_match && (n >>= 2),
    l > t.lookahead && (l = t.lookahead);
    do {
        if (a = e,
        h[a + s] === u && h[a + s - 1] === c && h[a] === h[r] && h[++a] === h[r + 1]) {
            r += 2,
            a++;
            do {} while (h[++r] === h[++a] && h[++r] === h[++a] && h[++r] === h[++a] && h[++r] === h[++a] && h[++r] === h[++a] && h[++r] === h[++a] && h[++r] === h[++a] && h[++r] === h[++a] && r  s) {
                if (t.match_start = e,
                s = i,
                i >= l)
                    break;
                c = h[r + s - 1],
                u = h[r + s]
            }
        }
    } while ((e = _[e & d]) > o && 0 != --n);
    return s {
    const e = t.w_size;
    let a, i, n, r, s;
    do {
        if (r = t.window_size - t.lookahead - t.strstart,
        t.strstart >= e + (e - bt)) {
            t.window.set(t.window.subarray(e, e + e), 0),
            t.match_start -= e,
            t.strstart -= e,
            t.block_start -= e,
            i = t.hash_size,
            a = i;
            do {
                n = t.head[--a],
                t.head[a] = n >= e ? n - e : 0
            } while (--i);
            i = e,
            a = i;
            do {
                n = t.prev[--a],
                t.prev[a] = n >= e ? n - e : 0
            } while (--i);
            r += e
        }
        if (0 === t.strm.avail_in)
            break;
        if (i = Zt(t.strm, t.window, t.strstart + t.lookahead, r),
        t.lookahead += i,
        t.lookahead + t.insert >= 3)
            for (s = t.strstart - t.insert,
            t.ins_h = t.window,
            t.ins_h = xt(t, t.ins_h, t.window[s + 1]); t.insert && (t.ins_h = xt(t, t.ins_h, t.window[s + 3 - 1]),
            t.prev[s & t.w_mask] = t.head[t.ins_h],
            t.head[t.ins_h] = s,
            s++,
            t.insert--,
            !(t.lookahead + t.insert {
    let a, i;
    for (; ; ) {
        if (t.lookahead = 3 && (t.ins_h = xt(t, t.ins_h, t.window[t.strstart + 3 - 1]),
        a = t.prev[t.strstart & t.w_mask] = t.head[t.ins_h],
        t.head[t.ins_h] = t.strstart),
        0 !== a && t.strstart - a = 3)
            if (i = q(t, t.strstart - t.match_start, t.match_length - 3),
            t.lookahead -= t.match_length,
            t.match_length = 3) {
                t.match_length--;
                do {
                    t.strstart++,
                    t.ins_h = xt(t, t.ins_h, t.window[t.strstart + 3 - 1]),
                    a = t.prev[t.strstart & t.w_mask] = t.head[t.ins_h],
                    t.head[t.ins_h] = t.strstart
                } while (0 != --t.match_length);
                t.strstart++
            } else
                t.strstart += t.match_length,
                t.match_length = 0,
                t.ins_h = t.window[t.strstart],
                t.ins_h = xt(t, t.ins_h, t.window[t.strstart + 1]);
        else
            i = q(t, 0, t.window[t.strstart]),
            t.lookahead--,
            t.strstart++;
        if (i && (At(t, !1),
        0 === t.strm.avail_out))
            return 1
    }
    return t.insert = t.strstart {
    let a, i, n;
    for (; ; ) {
        if (t.lookahead = 3 && (t.ins_h = xt(t, t.ins_h, t.window[t.strstart + 3 - 1]),
        a = t.prev[t.strstart & t.w_mask] = t.head[t.ins_h],
        t.head[t.ins_h] = t.strstart),
        t.prev_length = t.match_length,
        t.prev_match = t.match_start,
        t.match_length = 2,
        0 !== a && t.prev_length  4096) && (t.match_length = 2)),
        t.prev_length >= 3 && t.match_length {
    let a = 65535;
    for (a > t.pending_buf_size - 5 && (a = t.pending_buf_size - 5); ; ) {
        if (t.lookahead = i) && (t.lookahead = t.strstart - i,
        t.strstart = i,
        At(t, !1),
        0 === t.strm.avail_out))
            return 1;
        if (t.strstart - t.block_start >= t.w_size - bt && (At(t, !1),
        0 === t.strm.avail_out))
            return 1
    }
    return t.insert = 0,
    e === tt ? (At(t, !0),
    0 === t.strm.avail_out ? 3 : 4) : (t.strstart > t.block_start && (At(t, !1),
    t.strm.avail_out),
    1)
}
)), new Tt(4,4,8,4,Dt), new Tt(4,5,16,8,Dt), new Tt(4,6,32,32,Dt), new Tt(4,4,16,16,Ot), new Tt(8,16,32,32,Ot), new Tt(8,16,128,128,Ot), new Tt(8,32,128,256,Ot), new Tt(32,128,258,1024,Ot), new Tt(32,258,258,4096,Ot)];
function Nt() {
    this.strm = null,
    this.status = 0,
    this.pending_buf = null,
    this.pending_buf_size = 0,
    this.pending_out = 0,
    this.pending = 0,
    this.wrap = 0,
    this.gzhead = null,
    this.gzindex = 0,
    this.method = ut,
    this.last_flush = -1,
    this.w_size = 0,
    this.w_bits = 0,
    this.w_mask = 0,
    this.window = null,
    this.window_size = 0,
    this.prev = null,
    this.head = null,
    this.ins_h = 0,
    this.hash_size = 0,
    this.hash_bits = 0,
    this.hash_mask = 0,
    this.hash_shift = 0,
    this.block_start = 0,
    this.match_length = 0,
    this.prev_match = 0,
    this.match_available = 0,
    this.strstart = 0,
    this.match_start = 0,
    this.lookahead = 0,
    this.prev_length = 0,
    this.max_chain_length = 0,
    this.max_lazy_match = 0,
    this.level = 0,
    this.strategy = 0,
    this.good_match = 0,
    this.nice_match = 0,
    this.dyn_ltree = new Uint16Array(1146),
    this.dyn_dtree = new Uint16Array(122),
    this.bl_tree = new Uint16Array(78),
    yt(this.dyn_ltree),
    yt(this.dyn_dtree),
    yt(this.bl_tree),
    this.l_desc = null,
    this.d_desc = null,
    this.bl_desc = null,
    this.bl_count = new Uint16Array(16),
    this.heap = new Uint16Array(573),
    yt(this.heap),
    this.heap_len = 0,
    this.heap_max = 0,
    this.depth = new Uint16Array(573),
    yt(this.depth),
    this.l_buf = 0,
    this.lit_bufsize = 0,
    this.last_lit = 0,
    this.d_buf = 0,
    this.opt_len = 0,
    this.static_len = 0,
    this.matches = 0,
    this.insert = 0,
    this.bi_buf = 0,
    this.bi_valid = 0
}
const Ft = t=>{
    if (!t || !t.state)
        return kt(t, nt);
    t.total_in = t.total_out = 0,
    t.data_type = ct;
    const e = t.state;
    return e.pending = 0,
    e.pending_out = 0,
    e.wrap {
    const e = Ft(t);
    var a;
    return e === at && ((a = t.state).window_size = 2 * a.w_size,
    yt(a.head),
    a.max_lazy_match = Lt[a.level].max_lazy,
    a.good_match = Lt[a.level].good_length,
    a.nice_match = Lt[a.level].nice_length,
    a.max_chain_length = Lt[a.level].max_chain,
    a.strstart = 0,
    a.block_start = 0,
    a.lookahead = 0,
    a.insert = 0,
    a.match_length = a.prev_length = 2,
    a.match_available = 0,
    a.ins_h = 0),
    e
}
  , It = (t,e,a,i,n,r)=>{
    if (!t)
        return nt;
    let s = 1;
    if (e === lt && (e = 6),
    i  15 && (s = 2,
    i -= 16),
    n  9 || a !== ut || i  15 || e  9 || r  _t)
        return kt(t, nt);
    8 === i && (i = 9);
    const l = new Nt;
    return t.state = l,
    l.strm = t,
    l.wrap = s,
    l.gzhead = null,
    l.w_bits = i,
    l.w_size = 1 It(t, e, ut, 15, 8, ft),
    deflateInit2: It,
    deflateReset: Bt,
    deflateResetKeep: Ft,
    deflateSetHeader: (t,e)=>t && t.state ? 2 !== t.state.wrap ? nt : (t.state.gzhead = e,
    at) : nt,
    deflate: (t,e)=>{
        let a, i;
        if (!t || !t.state || e > et || e > 8 & 255),
                Et(n, n.gzhead.time >> 16 & 255),
                Et(n, n.gzhead.time >> 24 & 255),
                Et(n, 9 === n.level ? 2 : n.strategy >= ht || n.level > 8 & 255)),
                n.gzhead.hcrc && (t.adler = j(t.adler, n.pending_buf, n.pending, 0)),
                n.gzindex = 0,
                n.status = 69) : (Et(n, 0),
                Et(n, 0),
                Et(n, 0),
                Et(n, 0),
                Et(n, 0),
                Et(n, 9 === n.level ? 2 : n.strategy >= ht || n.level = ht || n.level >> 16),
                Rt(n, 65535 & t.adler)),
                t.adler = 1
            }
        if (69 === n.status)
            if (n.gzhead.extra) {
                for (a = n.pending; n.gzindex  a && (t.adler = j(t.adler, n.pending_buf, n.pending - a, a)),
                zt(t),
                a = n.pending,
                n.pending !== n.pending_buf_size)); )
                    Et(n, 255 & n.gzhead.extra[n.gzindex]),
                    n.gzindex++;
                n.gzhead.hcrc && n.pending > a && (t.adler = j(t.adler, n.pending_buf, n.pending - a, a)),
                n.gzindex === n.gzhead.extra.length && (n.gzindex = 0,
                n.status = 73)
            } else
                n.status = 73;
        if (73 === n.status)
            if (n.gzhead.name) {
                a = n.pending;
                do {
                    if (n.pending === n.pending_buf_size && (n.gzhead.hcrc && n.pending > a && (t.adler = j(t.adler, n.pending_buf, n.pending - a, a)),
                    zt(t),
                    a = n.pending,
                    n.pending === n.pending_buf_size)) {
                        i = 1;
                        break
                    }
                    i = n.gzindex  a && (t.adler = j(t.adler, n.pending_buf, n.pending - a, a)),
                0 === i && (n.gzindex = 0,
                n.status = 91)
            } else
                n.status = 91;
        if (91 === n.status)
            if (n.gzhead.comment) {
                a = n.pending;
                do {
                    if (n.pending === n.pending_buf_size && (n.gzhead.hcrc && n.pending > a && (t.adler = j(t.adler, n.pending_buf, n.pending - a, a)),
                    zt(t),
                    a = n.pending,
                    n.pending === n.pending_buf_size)) {
                        i = 1;
                        break
                    }
                    i = n.gzindex  a && (t.adler = j(t.adler, n.pending_buf, n.pending - a, a)),
                0 === i && (n.status = gt)
            } else
                n.status = gt;
        if (n.status === gt && (n.gzhead.hcrc ? (n.pending + 2 > n.pending_buf_size && zt(t),
        n.pending + 2 > 8 & 255),
        t.adler = 0,
        n.status = mt)) : n.status = mt),
        0 !== n.pending) {
            if (zt(t),
            0 === t.avail_out)
                return n.last_flush = -1,
                at
        } else if (0 === t.avail_in && vt(e) {
                let a;
                for (; ; ) {
                    if (0 === t.lookahead && (St(t),
                    0 === t.lookahead)) {
                        if (e === Q)
                            return 1;
                        break
                    }
                    if (t.match_length = 0,
                    a = q(t, 0, t.window[t.strstart]),
                    t.lookahead--,
                    t.strstart++,
                    a && (At(t, !1),
                    0 === t.strm.avail_out))
                        return 1
                }
                return t.insert = 0,
                e === tt ? (At(t, !0),
                0 === t.strm.avail_out ? 3 : 4) : t.last_lit && (At(t, !1),
                0 === t.strm.avail_out) ? 1 : 2
            }
            )(n, e) : n.strategy === dt ? ((t,e)=>{
                let a, i, n, r;
                const s = t.window;
                for (; ; ) {
                    if (t.lookahead = 3 && t.strstart > 0 && (n = t.strstart - 1,
                    i = s[n],
                    i === s[++n] && i === s[++n] && i === s[++n])) {
                        r = t.strstart + wt;
                        do {} while (i === s[++n] && i === s[++n] && i === s[++n] && i === s[++n] && i === s[++n] && i === s[++n] && i === s[++n] && i === s[++n] && n  t.lookahead && (t.match_length = t.lookahead)
                    }
                    if (t.match_length >= 3 ? (a = q(t, 1, t.match_length - 3),
                    t.lookahead -= t.match_length,
                    t.strstart += t.match_length,
                    t.match_length = 0) : (a = q(t, 0, t.window[t.strstart]),
                    t.lookahead--,
                    t.strstart++),
                    a && (At(t, !1),
                    0 === t.strm.avail_out))
                        return 1
                }
                return t.insert = 0,
                e === tt ? (At(t, !0),
                0 === t.strm.avail_out ? 3 : 4) : t.last_lit && (At(t, !1),
                0 === t.strm.avail_out) ? 1 : 2
            }
            )(n, e) : Lt[n.level].func(n, e);
            if (3 !== a && 4 !== a || (n.status = pt),
            1 === a || 3 === a)
                return 0 === t.avail_out && (n.last_flush = -1),
                at;
            if (2 === a && (e === V ? J(n) : e !== et && (X(n, 0, 0, !1),
            e === $ && (yt(n.head),
            0 === n.lookahead && (n.strstart = 0,
            n.block_start = 0,
            n.insert = 0))),
            zt(t),
            0 === t.avail_out))
                return n.last_flush = -1,
                at
        }
        return e !== tt ? at : n.wrap > 8 & 255),
        Et(n, t.adler >> 16 & 255),
        Et(n, t.adler >> 24 & 255),
        Et(n, 255 & t.total_in),
        Et(n, t.total_in >> 8 & 255),
        Et(n, t.total_in >> 16 & 255),
        Et(n, t.total_in >> 24 & 255)) : (Rt(n, t.adler >>> 16),
        Rt(n, 65535 & t.adler)),
        zt(t),
        n.wrap > 0 && (n.wrap = -n.wrap),
        0 !== n.pending ? at : it)
    }
    ,
    deflateEnd: t=>{
        if (!t || !t.state)
            return nt;
        const e = t.state.status;
        return 42 !== e && 69 !== e && 73 !== e && 91 !== e && e !== gt && e !== mt && e !== pt ? kt(t, nt) : (t.state = null,
        e === mt ? kt(t, rt) : at)
    }
    ,
    deflateSetDictionary: (t,e)=>{
        let a = e.length;
        if (!t || !t.state)
            return nt;
        const i = t.state
          , n = i.wrap;
        if (2 === n || 1 === n && 42 !== i.status || i.lookahead)
            return nt;
        if (1 === n && (t.adler = M(t.adler, e, a, 0)),
        i.wrap = 0,
        a >= i.w_size) {
            0 === n && (yt(i.head),
            i.strstart = 0,
            i.block_start = 0,
            i.insert = 0);
            let t = new Uint8Array(i.w_size);
            t.set(e.subarray(a - i.w_size, a), 0),
            e = t,
            a = i.w_size
        }
        const r = t.avail_in
          , s = t.next_in
          , l = t.input;
        for (t.avail_in = a,
        t.next_in = 0,
        t.input = e,
        St(i); i.lookahead >= 3; ) {
            let t = i.strstart
              , e = i.lookahead - 2;
            do {
                i.ins_h = xt(i, i.ins_h, i.window[t + 3 - 1]),
                i.prev[t & i.w_mask] = i.head[i.ins_h],
                i.head[i.ins_h] = t,
                t++
            } while (--e);
            i.strstart = t,
            i.lookahead = 2,
            St(i)
        }
        return i.strstart += i.lookahead,
        i.block_start = i.strstart,
        i.insert = i.lookahead,
        i.lookahead = 0,
        i.match_length = i.prev_length = 2,
        i.match_available = 0,
        t.next_in = s,
        t.input = l,
        t.avail_in = r,
        i.wrap = n,
        at
    }
    ,
    deflateInfo: "pako deflate (from Nodeca project)"
};
const Ht = (t,e)=>Object.prototype.hasOwnProperty.call(t, e);
var Mt = function(t) {
    const e = Array.prototype.slice.call(arguments, 1);
    for (; e.length; ) {
        const a = e.shift();
        if (a) {
            if ("object" != typeof a)
                throw new TypeError(a + "must be non-object");
            for (const e in a)
                Ht(a, e) && (t[e] = a[e])
        }
    }
    return t
}
  , Kt = t=>{
    let e = 0;
    for (let i = 0, n = t.length; i = 252 ? 6 : ha >= 248 ? 5 : ha >= 240 ? 4 : ha >= 224 ? 3 : ha >= 192 ? 2 : 1;
Pt[254] = Pt[254] = 1;
var Yt = t=>{
    if ("function" == typeof TextEncoder && TextEncoder.prototype.encode)
        return (new TextEncoder).encode(t);
    let e, a, i, n, r, s = t.length, l = 0;
    for (n = 0; n >> 6,
        e[r++] = 128 | 63 & a) : a >> 12,
        e[r++] = 128 | a >>> 6 & 63,
        e[r++] = 128 | 63 & a) : (e[r++] = 240 | a >>> 18,
        e[r++] = 128 | a >>> 12 & 63,
        e[r++] = 128 | a >>> 6 & 63,
        e[r++] = 128 | 63 & a);
    return e
}
  , Gt = (t,e)=>{
    const a = e || t.length;
    if ("function" == typeof TextDecoder && TextDecoder.prototype.decode)
        return (new TextDecoder).decode(t.subarray(0, e));
    let i, n;
    const r = new Array(2 * a);
    for (n = 0,
    i = 0; i  4)
            r[n++] = 65533,
            i += s - 1;
        else {
            for (e &= 2 === s ? 31 : 3 === s ? 15 : 7; s > 1 && i  1 ? r[n++] = 65533 : e > 10 & 1023,
            r[n++] = 56320 | 1023 & e)
        }
    }
    return ((t,e)=>{
        if (e {
    (e = e || t.length) > t.length && (e = t.length);
    let a = e - 1;
    for (; a >= 0 && 128 == (192 & t[a]); )
        a--;
    return a  e ? a : e
}
;
var Wt = function() {
    this.input = null,
    this.next_in = 0,
    this.avail_in = 0,
    this.total_in = 0,
    this.output = null,
    this.next_out = 0,
    this.avail_out = 0,
    this.total_out = 0,
    this.msg = "",
    this.state = null,
    this.data_type = 2,
    this.adler = 0
};
const qt = Object.prototype.toString
  , {Z_NO_FLUSH: Jt, Z_SYNC_FLUSH: Qt, Z_FULL_FLUSH: Vt, Z_FINISH: $t, Z_OK: te, Z_STREAM_END: ee, Z_DEFAULT_COMPRESSION: ae, Z_DEFAULT_STRATEGY: ie, Z_DEFLATED: ne} = Y;
function re(t) {
    this.options = Mt({
        level: ae,
        method: ne,
        chunkSize: 16384,
        windowBits: 15,
        memLevel: 8,
        strategy: ie
    }, t || {});
    let e = this.options;
    e.raw && e.windowBits > 0 ? e.windowBits = -e.windowBits : e.gzip && e.windowBits > 0 && e.windowBits  0 && this.onData(a.output.subarray(0, a.next_out)),
                n = Ct.deflateEnd(this.strm),
                this.onEnd(n),
                this.ended = !0,
                n === te;
            if (0 !== a.avail_out) {
                if (r > 0 && a.next_out > 0)
                    this.onData(a.output.subarray(0, a.next_out)),
                    a.avail_out = 0;
                else if (0 === a.avail_in)
                    break
            } else
                this.onData(a.output)
        }
    return !0
}
,
re.prototype.onData = function(t) {
    this.chunks.push(t)
}
,
re.prototype.onEnd = function(t) {
    t === te && (this.result = Kt(this.chunks)),
    this.chunks = [],
    this.err = t,
    this.msg = this.strm.msg
}
;
var le = {
    Deflate: re,
    deflate: se,
    deflateRaw: function(t, e) {
        return (e = e || {}).raw = !0,
        se(t, e)
    },
    gzip: function(t, e) {
        return (e = e || {}).gzip = !0,
        se(t, e)
    },
    constants: Y
};
var oe = function(t, e) {
    let a, i, n, r, s, l, o, h, d, _, f, c, u, w, b, g, m, p, k, v, y, x, z, A;
    const E = t.state;
    a = t.next_in,
    z = t.input,
    i = a + (t.avail_in - 5),
    n = t.next_out,
    A = t.output,
    r = n - (e - t.avail_out),
    s = n + (t.avail_out - 257),
    l = E.dmax,
    o = E.wsize,
    h = E.whave,
    d = E.wnext,
    _ = E.window,
    f = E.hold,
    c = E.bits,
    u = E.lencode,
    w = E.distcode,
    b = (1 >> 24,
            f >>>= p,
            c -= p,
            p = m >>> 16 & 255,
            0 === p)
                A[n++] = 65535 & m;
            else {
                if (!(16 & p)) {
                    if (0 == (64 & p)) {
                        m = u[(65535 & m) + (f & (1 >>= p,
                c -= p),
                c >> 24,
                    f >>>= p,
                    c -= p,
                    p = m >>> 16 & 255,
                    !(16 & p)) {
                        if (0 == (64 & p)) {
                            m = w[(65535 & m) + (f & (1  l) {
                        t.msg = "invalid distance too far back",
                        E.mode = 30;
                        break t
                    }
                    if (f >>>= p,
                    c -= p,
                    p = n - r,
                    v > p) {
                        if (p = v - p,
                        p > h && E.sane) {
                            t.msg = "invalid distance too far back",
                            E.mode = 30;
                            break t
                        }
                        if (y = 0,
                        x = _,
                        0 === d) {
                            if (y += o - p,
                            p  2; )
                            A[n++] = x[y++],
                            A[n++] = x[y++],
                            A[n++] = x[y++],
                            k -= 3;
                        k && (A[n++] = x[y++],
                        k > 1 && (A[n++] = x[y++]))
                    } else {
                        y = n - v;
                        do {
                            A[n++] = A[y++],
                            A[n++] = A[y++],
                            A[n++] = A[y++],
                            k -= 3
                        } while (k > 2);
                        k && (A[n++] = A[y++],
                        k > 1 && (A[n++] = A[y++]))
                    }
                    break
                }
            }
            break
        }
    } while (a > 3,
    a -= k,
    c -= k {
    const o = l.bits;
    let h, d, _, f, c, u, w = 0, b = 0, g = 0, m = 0, p = 0, k = 0, v = 0, y = 0, x = 0, z = 0, A = null, E = 0;
    const R = new Uint16Array(16)
      , Z = new Uint16Array(16);
    let U, S, D, O = null, T = 0;
    for (w = 0; w = 1 && 0 === R[m]; m--)
        ;
    if (p > m && (p = m),
    0 === m)
        return n[r++] = 20971520,
        n[r++] = 20971520,
        l.bits = 1,
        0;
    for (g = 1; g  0 && (0 === t || 1 !== m))
        return -1;
    for (Z[1] = 0,
    w = 1; w  852 || 2 === t && x > 592)
        return 1;
    for (; ; ) {
        U = w - v,
        s  u ? (S = O[T + s],
        D = A[E + s]) : (S = 96,
        D = 0),
        h = 1 > v) + d] = U >= 1;
        if (0 !== h ? (z &= h - 1,
        z += h) : z = 0,
        b++,
        0 == --R[w]) {
            if (w === m)
                break;
            w = e[a + s]
        }
        if (w > p && (z & f) !== _) {
            for (0 === v && (v = p),
            c += g,
            k = w - v,
            y = 1  852 || 2 === t && x > 592)
                return 1;
            _ = z & f,
            n[_] = p (t >>> 24 & 255) + (t >>> 8 & 65280) + ((65280 & t) {
    if (!t || !t.state)
        return ve;
    const e = t.state;
    return t.total_in = t.total_out = e.total = 0,
    t.msg = "",
    e.wrap && (t.adler = 1 & e.wrap),
    e.mode = 1,
    e.last = 0,
    e.havedict = 0,
    e.dmax = 32768,
    e.head = null,
    e.hold = 0,
    e.bits = 0,
    e.lencode = e.lendyn = new Int32Array(852),
    e.distcode = e.distdyn = new Int32Array(592),
    e.sane = 1,
    e.back = -1,
    me
}
  , De = t=>{
    if (!t || !t.state)
        return ve;
    const e = t.state;
    return e.wsize = 0,
    e.whave = 0,
    e.wnext = 0,
    Se(t)
}
  , Oe = (t,e)=>{
    let a;
    if (!t || !t.state)
        return ve;
    const i = t.state;
    return e > 4),
    e  15) ? ve : (null !== i.window && i.wbits !== e && (i.window = null),
    i.wrap = a,
    i.wbits = e,
    De(t))
}
  , Te = (t,e)=>{
    if (!t)
        return ve;
    const a = new Ue;
    t.state = a,
    a.window = null;
    const i = Oe(t, e);
    return i !== me && (t.state = null),
    i
}
;
let Le, Ne, Fe = !0;
const Be = t=>{
    if (Fe) {
        Le = new Int32Array(512),
        Ne = new Int32Array(32);
        let e = 0;
        for (; e {
    let n;
    const r = t.state;
    return null === r.window && (r.wsize = 1 = r.wsize ? (r.window.set(e.subarray(a - r.wsize, a), 0),
    r.wnext = 0,
    r.whave = r.wsize) : (n = r.wsize - r.wnext,
    n > i && (n = i),
    r.window.set(e.subarray(a - i, a - i + n), r.wnext),
    (i -= n) ? (r.window.set(e.subarray(a - i, a), 0),
    r.wnext = i,
    r.whave = r.wsize) : (r.wnext += n,
    r.wnext === r.wsize && (r.wnext = 0),
    r.whave Te(t, 15),
    inflateInit2: Te,
    inflate: (t,e)=>{
        let a, i, n, r, s, l, o, h, d, _, f, c, u, w, b, g, m, p, k, v, y, x, z = 0;
        const A = new Uint8Array(4);
        let E, R;
        const Z = new Uint8Array([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
        if (!t || !t.state || !t.output || !t.input && 0 !== t.avail_in)
            return ve;
        a = t.state,
        a.mode === Ee && (a.mode = 13),
        s = t.next_out,
        n = t.output,
        o = t.avail_out,
        r = t.next_in,
        i = t.input,
        l = t.avail_in,
        h = a.hold,
        d = a.bits,
        _ = l,
        f = o,
        x = me;
        t: for (; ; )
            switch (a.mode) {
            case 1:
                if (0 === a.wrap) {
                    a.mode = 13;
                    break
                }
                for (; d >> 8 & 255,
                    a.check = j(a.check, A, 2, 0),
                    h = 0,
                    d = 0,
                    a.mode = 2;
                    break
                }
                if (a.flags = 0,
                a.head && (a.head.done = !1),
                !(1 & a.wrap) || (((255 & h) > 8)) % 31) {
                    t.msg = "incorrect header check",
                    a.mode = Re;
                    break
                }
                if ((15 & h) !== Ae) {
                    t.msg = "unknown compression method",
                    a.mode = Re;
                    break
                }
                if (h >>>= 4,
                d -= 4,
                y = 8 + (15 & h),
                0 === a.wbits)
                    a.wbits = y;
                else if (y > a.wbits) {
                    t.msg = "invalid window size",
                    a.mode = Re;
                    break
                }
                a.dmax = 1 > 8 & 1),
                512 & a.flags && (A[0] = 255 & h,
                A[1] = h >>> 8 & 255,
                a.check = j(a.check, A, 2, 0)),
                h = 0,
                d = 0,
                a.mode = 3;
            case 3:
                for (; d >> 8 & 255,
                A[2] = h >>> 16 & 255,
                A[3] = h >>> 24 & 255,
                a.check = j(a.check, A, 4, 0)),
                h = 0,
                d = 0,
                a.mode = 4;
            case 4:
                for (; d > 8),
                512 & a.flags && (A[0] = 255 & h,
                A[1] = h >>> 8 & 255,
                a.check = j(a.check, A, 2, 0)),
                h = 0,
                d = 0,
                a.mode = 5;
            case 5:
                if (1024 & a.flags) {
                    for (; d >> 8 & 255,
                    a.check = j(a.check, A, 2, 0)),
                    h = 0,
                    d = 0
                } else
                    a.head && (a.head.extra = null);
                a.mode = 6;
            case 6:
                if (1024 & a.flags && (c = a.length,
                c > l && (c = l),
                c && (a.head && (y = a.head.extra_len - a.length,
                a.head.extra || (a.head.extra = new Uint8Array(a.head.extra_len)),
                a.head.extra.set(i.subarray(r, r + c), y)),
                512 & a.flags && (a.check = j(a.check, i, c, r)),
                l -= c,
                r += c,
                a.length -= c),
                a.length))
                    break t;
                a.length = 0,
                a.mode = 7;
            case 7:
                if (2048 & a.flags) {
                    if (0 === l)
                        break t;
                    c = 0;
                    do {
                        y = i[r + c++],
                        a.head && y && a.length > 9 & 1,
                a.head.done = !0),
                t.adler = a.check = 0,
                a.mode = Ee;
                break;
            case 10:
                for (; d >>= 7 & d,
                    d -= 7 & d,
                    a.mode = 27;
                    break
                }
                for (; d >>= 1,
                d -= 1,
                3 & h) {
                case 0:
                    a.mode = 14;
                    break;
                case 1:
                    if (Be(a),
                    a.mode = 20,
                    e === ge) {
                        h >>>= 2,
                        d -= 2;
                        break t
                    }
                    break;
                case 2:
                    a.mode = 17;
                    break;
                case 3:
                    t.msg = "invalid block type",
                    a.mode = Re
                }
                h >>>= 2,
                d -= 2;
                break;
            case 14:
                for (h >>>= 7 & d,
                d -= 7 & d; d >> 16 ^ 65535)) {
                    t.msg = "invalid stored block lengths",
                    a.mode = Re;
                    break
                }
                if (a.length = 65535 & h,
                h = 0,
                d = 0,
                a.mode = 15,
                e === ge)
                    break t;
            case 15:
                a.mode = 16;
            case 16:
                if (c = a.length,
                c) {
                    if (c > l && (c = l),
                    c > o && (c = o),
                    0 === c)
                        break t;
                    n.set(i.subarray(r, r + c), s),
                    l -= c,
                    r += c,
                    o -= c,
                    s += c,
                    a.length -= c;
                    break
                }
                a.mode = Ee;
                break;
            case 17:
                for (; d >>= 5,
                d -= 5,
                a.ndist = 1 + (31 & h),
                h >>>= 5,
                d -= 5,
                a.ncode = 4 + (15 & h),
                h >>>= 4,
                d -= 4,
                a.nlen > 286 || a.ndist > 30) {
                    t.msg = "too many length or distance symbols",
                    a.mode = Re;
                    break
                }
                a.have = 0,
                a.mode = 18;
            case 18:
                for (; a.have >>= 3,
                    d -= 3
                }
                for (; a.have >> 24,
                    g = z >>> 16 & 255,
                    m = 65535 & z,
                    !(b >>= b,
                        d -= b,
                        a.lens[a.have++] = m;
                    else {
                        if (16 === m) {
                            for (R = b + 2; d >>= b,
                            d -= b,
                            0 === a.have) {
                                t.msg = "invalid bit length repeat",
                                a.mode = Re;
                                break
                            }
                            y = a.lens[a.have - 1],
                            c = 3 + (3 & h),
                            h >>>= 2,
                            d -= 2
                        } else if (17 === m) {
                            for (R = b + 3; d >>= b,
                            d -= b,
                            y = 0,
                            c = 3 + (7 & h),
                            h >>>= 3,
                            d -= 3
                        } else {
                            for (R = b + 7; d >>= b,
                            d -= b,
                            y = 0,
                            c = 11 + (127 & h),
                            h >>>= 7,
                            d -= 7
                        }
                        if (a.have + c > a.nlen + a.ndist) {
                            t.msg = "invalid bit length repeat",
                            a.mode = Re;
                            break
                        }
                        for (; c--; )
                            a.lens[a.have++] = y
                    }
                }
                if (a.mode === Re)
                    break;
                if (0 === a.lens[256]) {
                    t.msg = "invalid code -- missing end-of-block",
                    a.mode = Re;
                    break
                }
                if (a.lenbits = 9,
                E = {
                    bits: a.lenbits
                },
                x = ue(1, a.lens, 0, a.nlen, a.lencode, 0, a.work, E),
                a.lenbits = E.bits,
                x) {
                    t.msg = "invalid literal/lengths set",
                    a.mode = Re;
                    break
                }
                if (a.distbits = 6,
                a.distcode = a.distdyn,
                E = {
                    bits: a.distbits
                },
                x = ue(2, a.lens, a.nlen, a.ndist, a.distcode, 0, a.work, E),
                a.distbits = E.bits,
                x) {
                    t.msg = "invalid distances set",
                    a.mode = Re;
                    break
                }
                if (a.mode = 20,
                e === ge)
                    break t;
            case 20:
                a.mode = 21;
            case 21:
                if (l >= 6 && o >= 258) {
                    t.next_out = s,
                    t.avail_out = o,
                    t.next_in = r,
                    t.avail_in = l,
                    a.hold = h,
                    a.bits = d,
                    oe(t, f),
                    s = t.next_out,
                    n = t.output,
                    o = t.avail_out,
                    r = t.next_in,
                    i = t.input,
                    l = t.avail_in,
                    h = a.hold,
                    d = a.bits,
                    a.mode === Ee && (a.back = -1);
                    break
                }
                for (a.back = 0; z = a.lencode[h & (1 >> 24,
                g = z >>> 16 & 255,
                m = 65535 & z,
                !(b > p)],
                    b = z >>> 24,
                    g = z >>> 16 & 255,
                    m = 65535 & z,
                    !(p + b >>= p,
                    d -= p,
                    a.back += p
                }
                if (h >>>= b,
                d -= b,
                a.back += b,
                a.length = m,
                0 === g) {
                    a.mode = 26;
                    break
                }
                if (32 & g) {
                    a.back = -1,
                    a.mode = Ee;
                    break
                }
                if (64 & g) {
                    t.msg = "invalid literal/length code",
                    a.mode = Re;
                    break
                }
                a.extra = 15 & g,
                a.mode = 22;
            case 22:
                if (a.extra) {
                    for (R = a.extra; d >>= a.extra,
                    d -= a.extra,
                    a.back += a.extra
                }
                a.was = a.length,
                a.mode = 23;
            case 23:
                for (; z = a.distcode[h & (1 >> 24,
                g = z >>> 16 & 255,
                m = 65535 & z,
                !(b > p)],
                    b = z >>> 24,
                    g = z >>> 16 & 255,
                    m = 65535 & z,
                    !(p + b >>= p,
                    d -= p,
                    a.back += p
                }
                if (h >>>= b,
                d -= b,
                a.back += b,
                64 & g) {
                    t.msg = "invalid distance code",
                    a.mode = Re;
                    break
                }
                a.offset = m,
                a.extra = 15 & g,
                a.mode = 24;
            case 24:
                if (a.extra) {
                    for (R = a.extra; d >>= a.extra,
                    d -= a.extra,
                    a.back += a.extra
                }
                if (a.offset > a.dmax) {
                    t.msg = "invalid distance too far back",
                    a.mode = Re;
                    break
                }
                a.mode = 25;
            case 25:
                if (0 === o)
                    break t;
                if (c = f - o,
                a.offset > c) {
                    if (c = a.offset - c,
                    c > a.whave && a.sane) {
                        t.msg = "invalid distance too far back",
                        a.mode = Re;
                        break
                    }
                    c > a.wnext ? (c -= a.wnext,
                    u = a.wsize - c) : u = a.wnext - c,
                    c > a.length && (c = a.length),
                    w = a.window
                } else
                    w = n,
                    u = s - a.offset,
                    c = a.length;
                c > o && (c = o),
                o -= c,
                a.length -= c;
                do {
                    n[s++] = w[u++]
                } while (--c);
                0 === a.length && (a.mode = 21);
                break;
            case 26:
                if (0 === o)
                    break t;
                n[s++] = a.length,
                o--,
                a.mode = 21;
                break;
            case 27:
                if (a.wrap) {
                    for (; d {
        if (!t || !t.state)
            return ve;
        let e = t.state;
        return e.window && (e.window = null),
        t.state = null,
        me
    }
    ,
    inflateGetHeader: (t,e)=>{
        if (!t || !t.state)
            return ve;
        const a = t.state;
        return 0 == (2 & a.wrap) ? ve : (a.head = e,
        e.done = !1,
        me)
    }
    ,
    inflateSetDictionary: (t,e)=>{
        const a = e.length;
        let i, n, r;
        return t && t.state ? (i = t.state,
        0 !== i.wrap && 11 !== i.mode ? ve : 11 === i.mode && (n = 1,
        n = M(n, e, a, 0),
        n !== i.check) ? ye : (r = Ie(t, e, a, a),
        r ? (i.mode = 31,
        xe) : (i.havedict = 1,
        me))) : ve
    }
    ,
    inflateInfo: "pako inflate (from Nodeca project)"
};
var He = function() {
    this.text = 0,
    this.time = 0,
    this.xflags = 0,
    this.os = 0,
    this.extra = null,
    this.extra_len = 0,
    this.name = "",
    this.comment = "",
    this.hcrc = 0,
    this.done = !1
};
const Me = Object.prototype.toString
  , {Z_NO_FLUSH: Ke, Z_FINISH: je, Z_OK: Pe, Z_STREAM_END: Ye, Z_NEED_DICT: Ge, Z_STREAM_ERROR: Xe, Z_DATA_ERROR: We, Z_MEM_ERROR: qe} = Y;
function Je(t) {
    this.options = Mt({
        chunkSize: 65536,
        windowBits: 15,
        to: ""
    }, t || {});
    const e = this.options;
    e.raw && e.windowBits >= 0 && e.windowBits = 0 && e.windowBits  15 && e.windowBits  0 && r === Ye && a.state.wrap > 0 && 0 !== t[a.next_in]; )
            Ce.inflateReset(a),
            r = Ce.inflate(a, s);
        switch (r) {
        case Xe:
        case We:
        case Ge:
        case qe:
            return this.onEnd(r),
            this.ended = !0,
            !1
        }
        if (l = a.avail_out,
        a.next_out && (0 === a.avail_out || r === Ye))
            if ("string" === this.options.to) {
                let t = Xt(a.output, a.next_out)
                  , e = a.next_out - t
                  , n = Gt(a.output, t);
                a.next_out = e,
                a.avail_out = i - e,
                e && a.output.set(a.output.subarray(t, t + e), 0),
                this.onData(n)
            } else
                this.onData(a.output.length === a.next_out ? a.output : a.output.subarray(0, a.next_out));
        if (r !== Pe || 0 !== l) {
            if (r === Ye)
                return r = Ce.inflateEnd(this.strm),
                this.onEnd(r),
                this.ended = !0,
                !0;
            if (0 === a.avail_in)
                break
        }
    }
    return !0
}
,
Je.prototype.onData = function(t) {
    this.chunks.push(t)
}
,
Je.prototype.onEnd = function(t) {
    t === Pe && ("string" === this.options.to ? this.result = this.chunks.join("") : this.result = Kt(this.chunks)),
    this.chunks = [],
    this.err = t,
    this.msg = this.strm.msg
}
;
var Ve = {
    Inflate: Je,
    inflate: Qe,
    inflateRaw: function(t, e) {
        return (e = e || {}).raw = !0,
        Qe(t, e)
    },
    ungzip: Qe,
    constants: Y
};
里面写了几种算法,扣下来可以学习,备着以后用。看大站,可以学习很多。
因为使用python,我这里做了点处理,先把byte转base64然后再处理

所以js这样处理

python:

现在数据都处理好了,就剩r.Response这个proto文件了,就能解析了。

跟进去,发现是一个地方。我们刚刚不是处理过吗,找一下文件里面的,拿出来:
message webcast_im_Response{
repeated webcast_im_Message messages=1;
string cursor=2;
int64 fetchinterval=3;
int64 now=4;
string internalext=5;
int32 fetchtype=6;
int64 heartbeatduration=8;
bool needack=9;
string pushserver=10;
string livecursor=11;
bool historynomore=12;
}
message webcast_im_Message{
string method=1;
bytes payload=2;
int64 msgid=3;
int32 msgtype=4;
int64 offset=5;
bool needwrdsstore=6;
int64 wrdsversion=7;
string wrdssubkey=8;
}
编译一下,看效果:
网站上:

python:

       一致,本来第一项列表是有数据,可以看到里面消息类型的,刚刚不小心跳过断点了,然后直播结束了,没消息了....尴尬。但是暂时不影响分析,我们把ack也完成就换个直播间。
③ack处理:
因为这里needack是true,所以需要发一个包:

可以看到,我们构造没问题。
代码:
#ack处理
internalext=webcast_im_Response.internalext
payload_base64=ctx.call('get_ackpayload',internalext)
payload_byte=base64.b64decode(payload_base64)
ack_pushproto_PushFrame=new_pb2.pushproto_PushFrame()
ack_pushproto_PushFrame.payloadtype='ack'
ack_pushproto_PushFrame.payload=payload_byte
ack_pushproto_PushFrame.logid=pushproto_PushFrame.logid
ack_payload=ack_pushproto_PushFrame.SerializeToString()
print(base64.b64encode(ack_payload).decode())
       我们到现在还是没看到弹幕数据,因为他在下面emit函数处处理了,我们先别急,我换一个直播间再继续分析。
④最后数据处理emit函数:


猜测应该是payload里面是弹幕信息,我们进到emit函数里面看看吧。
            emit(e) {
                const t = e.getMessagesList(); //得到刚刚的message列表
                //forEach() 方法对数组的每个元素执行一次给定的函数。
                //这里相当于遍历每一个message,然后做处理。
                t.length && t.forEach((e=>{
                    const t = e.getMethod()
                      , a = "RoomMessage" === t ? t : e.getMsgId();
                    this.messageIdsForDistinct.has(a) || (this.messageIdsForDistinct.add(a),
                    //                                      
                    this.runAllEvents(t, e))
                }
                ))
            }
我们下个断点,断下时,看看值,这里应该是message列表第一个:

       我们可以看到,没错,是第一个WebcastMemberMessage,然后把这message列表第一个对象扔进了这个runAllEvents函数处理:

进入runAllEvents函数,我们又看到了熟悉的关键词:

大概猜测这里就能得到我们想要的数据了,下断点,运行到这看s值。

果然显示了用户名还有一些信息。
代码分析:
            runAllEvents(e, t) {
                var a;
                //this.eventsMap.entries()里面包含所有事件模块
                for (const [o,i] of this.eventsMap.entries()) {
                    const a = this.messageModules[o]; //创建模块
                    //if判断传进来的名字是否匹配模块
                    if (i && a && this.isCorrectEventName(o, e)) {
                        //匹配就执行相应代码
                        const r = t.getPayload_asU8()
                          , s = a.deserializeBinary(r); //可以在这里下断点找到a对象对应的反序列化对象,模块
                        return this.info(`emit Message Type: ${e} ${o}`),
                        this.info("emit Message Payload:", (()=>s.toObject())),
                        void i.forEach((e=>{
                            e(s, t, r)
                        }
                        ))
                    }
                }
                const r = null !== (a = this.messageNotUseCache.get(e)) && void 0 !== a ? a : [];
                r.length > o.ej && r.shift(),
                r.push(t),
                this.messageNotUseCache.set(e, r)
            }
       至此,基本上分析完了全部逻辑,一共解析了3次payload,他是遍历第二次得到的payload数据中的message列表,根据不同message类型,找到不同类型对应的proto文件对象,然后再解析。
五、用工具,得到基本proto文件,遇到proto文件报错解决方法
       跟前面得到的proto文件类似,进入到解析对象的js里面,然后复制js到工具处理。
       因为很多,而且webpack打包很分散,我讲一下我的处理方法吧:

       相似报错可以直接同一值替换(可能不太严谨,但是跟了很多都是同一个),我们可以把这个直接全部复制到一个大proto文件中,比如说我们最开始那个,折叠起来,然后慢慢把报错解决。
       我演示Common的处理,其他类似就好:

       跟进去,直接定位Common,因为p对象,所以代码无法体现是谁,所以处理会报错。
       跟进去后直接复制js到工具(记得创建自己要处理的文件js),直接处理。

       将补充的补充到下面,标注一下,说不定其他模块也要用,反正我们都复制下来。
       我这里采用nodepad++全体Common替换成 webcast_im_Common

       这里就解决了

       然后后面的也接着一样操作来,直到补全完整的proto文件,基本上就出来了,很多要有耐心,可能一开始错会很多,但是补到后面,你补其他message类型的时候就很轻松了。
       把大的Image,User,common,Text补完,基本上就剩一点点了。
              Text  =====> webcast_data_Text
              Image =====> webcast_data_Image
              User   =====> webcast_data_User
              Common =====> webcast_im_Common
              Room =====> webcast_data_Room

       补到后面会发现3个map类型的数据,因为工具没处理(大佬可以帮忙添加一下处理map类型的方法),我们注释掉里面的内容就好

       20分钟左右补完了,经历了3个webpack,都是上w行代码的js。

       很清楚的就得到刚刚那个WebcastMemberMessage类型数据的结构了,其他message类型(应该有6种)也是一样步骤,慢慢补充,就可以得到完整的proto文件,就能使用了。

       最后这个不常见,刚刚好关播给我抓到了。
六、python连接websocket一些注意点
       上面我们已经分析了数据处理方面的东西,现在我们来用python连接websocket地址,然后实现open,onmessage事件等功能,然后获取我们想要的数据,我这里讲一些注意点吧。
①连接需要cookie
在fd搜索:

发现原始请求头我们要什么数据:

       需要一个cookie,搜索ttwid就能看到cookie是怎么生成的,发现是刚开网页服务器发过来的。
②网址signature分析

       从最开始,我们知道,网址是这里的t,然后进入u方法。

       这里就是我们要的signature,可以看出是从p来的。
const s = {
    "X-MS-STUB": g()(n.substring(1))
         };
let p = {};
return
//这一串是问号表达式(三元表达式),":"前面是false就走":"后面的,否则走前面的
window.byted_acrawler && (p = null ===
(o = null === window || void 0 === window ?
void 0 : window.byted_acrawler
)
|| void 0 === o
? void 0 : o.frontierSign(s)),
//会进这个o.frontierSign(s)方法里面,大家可以自己调试
   {
     signature: null !== (a = p["X-Bogus"]) && void 0 !== a ? a : ""
   }
       里面全扣下来,然后补环境可以解决,有轻微混淆吧。


       只是可能js代码和node版本问题,警告了,不过没什么事。
       其实这个参数目前还不会校验,删掉不携带也可以获得响应,不排除以后校验的可能。
七、效果展示

因为有些数据可能敏感,就放这张效果图了。
八、代码及总结
       代码地址:https://github.com/Prince-cool/dy_protobuf
       希望大家能给个star支持一下,哈哈哈哈哈,写的真的好辛苦啊!!!!
       总结:这几天过节,也到处去玩,所以都是抽空慢慢补齐内容的,可能会有点跳跃,请见谅,因为如果要讲太细,可能篇幅太长了,文字也说不清楚,一共60多张图片。如果有机会,想要我录视频的,可以留言,我找机会录制一下,或者开个腾讯会议一起交流。
       希望这篇文章对你有益,感谢,也祝大家新年快乐,心想事成!

在这里, 插入图片

QingYi.   


忘记手机打卡 发表于 2023-1-21 00:10
楼主能不能把整个过程简要概括一下,就想知道个原理和大概~

付钱开小灶
hxs1   


QingYi. 发表于 2023-1-21 08:30
付钱开小灶

大佬,都用ast啊,不能硬杠啊
sunxing_1   

坐等大佬视频教学
lewislihao   

看了帖子大佬太强了
Arcticlyc   

期待大神的视频
忘记手机打卡   

大佬,学习了。期待教学视频
bennyt   

楼主能不能把整个过程简要概括一下,就想知道个原理和大概~
SVIP9大会员   

好详细的教程,学习一下。
xixicoco   

除夕快乐,大神 膜拜了,思路清晰缜密啊!
您需要登录后才可以回帖 登录 | 立即注册