bilibili 彈幕協議分析,golang程式碼還原

gitxuzan發表於2022-05-09

我的文章

進位制之間轉換

1. 二進位制轉八進位制  %b -> %o
2. 二進位制轉十進位制  %b ->  %d
3. 二進位制轉十六進位制 %b -> %x
4. 八進位制轉二進位制 %o -> %b
5. 八進位制轉十進位制 %o -> %d
6. 八進位制轉十六進位制 %o -> %x
7. 十進位制轉二進位制 %d -> %b
8. 十進位制轉八進位制 %d -> %o
9. 十進位制轉十六進位制 %d -> %x
10. 十六進位制轉二進位制 %x -> %b
11. 十六進位制轉八進位制 %x -> %o
12. 十六進位制轉十進位制 %x -> %d
// 例
fmt.Printf("十進位制%d轉成八進位制%o",num1,num2)
%b    表示為二進位制
%c    該值對應的unicode碼值
%d    表示為十進位制
%o    表示為八進位制
%q    該值對應的單引號括起來的go語法字元字面值,必要時會採用安全的轉義表示
%x    表示為十六進位制,使用a-f
%X    表示為十六進位制,使用A-F
%U    表示為Unicode格式:U+1234,等價於"U+%04X"
%E    用科學計數法表示
%f    用浮點數表示
hex := fmt.Sprintf("%08x", i) // hex 16進位制

開啟bilibili 彈幕協議分析

在這裡插入圖片描述

#協議內容如下
相關協議連結地址 blog.csdn.net/xfgryujk/article/det...

協議的頭部是 4 2 2 4 4 總共16個位元組,加上傳送的長度,
頭部入參說明

  1. 第一個4個位元組 包總長度 00ff = 255
  2. 第二個2個位元組, 頭部長度 換算10進位制 0010 = 16
  3. 第三個2個位元組, 協議版本 0001
  4. 第四個4個位元組,0000 0007 = 7 ,7代表進入彈幕
  5. 第五個4個位元組 固定常數 0000 0001 = 1

返回引數:
第7到8個位元組:返回協議引數,int16型別
0:解析json值
1: 人氣值,int32
2: zip壓縮
3: brotli壓縮

00000000: 0000 00ff 0010 0001 0000 0007 0000 0001  ................
00000001: 7b22 7569 6422 3a33 3335 3230 3037 3434  {"uid":335200744
00000002: 2c22 726f 6f6d 6964 223a 3233 3230 3230  ,"roomid":232020
00000003: 3831 2c22 7072 6f74 6f76 6572 223a 332c  81,"protover":3,
00000004: 2270 6c61 7466 6f72 6d22 3a22 7765 6222  "platform":"web"
00000005: 2c22 7479 7065 223a 322c 226b 6579 223a  ,"type":2,"key":
00000006: 2232 5279 5173 6f35 3432 4c77 4756 7774  "2RyQso542LwGVwt
00000007: 536f 7379 7533 2d64 436c 4e73 5843 3051  Sosyu3-dClNsXC0Q
00000008: 3938 3046 4779 6874 4847 7062 4d39 7654  980FGyhtHGpbM9vT
00000009: 4734 6566 396f 7150 5935 4c39 6772 7470  G4ef9oqPY5L9grtp
0000000a: 4b44 7153 6568 3647 3634 7667 3145 5644  KDqSeh6G64vg1EVD
0000000b: 4378 5972 566f 2d47 4171 7267 6d6e 6370  CxYrVo-GAqrgmncp
0000000c: 4147 5054 3341 6c4b 6d57 7536 326d 666e  AGPT3AlKmWu62mfn
0000000d: 4772 3567 6564 4732 6677 4168 365f 7834  Gr5gedG2fwAh6_x4
0000000e: 5f59 6b49 3867 4a69 6f31 3648 5439 6542  _YkI8gJio16HT9eB
0000000f: 4873 6351 594f 4953 4a7a 773d 3d22 7d    HscQYOISJzw=="}


{"uid":335200743,"roomid":23202081,"protover":3,"platform":"web","type":2,"key":"2RyQso542LwGVwtSosyu3-dClNsXC0Q980FGyhtHGpbM9vTG4ef9oqPY5L9grtpKDqSeh6G64vg1EVDCxYrVo-GAqrgmncpAGPT3AlKmWu62mfnGr5gedG2fwAh6_x4_YkI8gJio16HT9eBHscQYOISJzr=="}

驗證頭部協議

    handshake := "000000ff001000010000000700000001"
    等價 16位元組
    byteArr := []byte{
        0x00, 0x00,0x00,0xff, 0x00,0x10, 0x00,0x01, 0x00,0x00, 0x00,0x07, 0x00,0x00, 0x00,0x01,
    }
    t.Log(len(byteArr))
    buf, err := hex.DecodeString(handshake)
    t.Log("長度",len(buf))
    t.Log(buf)
    t.Log("二進位制" + fmt.Sprintf("%08b",buf))
    t.Log("八進位制" + fmt.Sprintf("%08o",buf))
    t.Log("十六進位制" + fmt.Sprintf("%08x",buf))
    t.Log("十進位制" + fmt.Sprintf("%d",buf))
    輸出結果:
    qimiao_test.go:30: 長度 16位元組
    qimiao_test.go:31: [0 0 0 255 0 16 0 1 0 0 0 7 0 0 0 1]
    qimiao_test.go:32: 二進位制[00000000 00000000 00000000 11111111 00000000 00010000 00000000 00000001 00000000 00000000 00000000 00000111 00000000 00000000 00000000 00000001]
    qimiao_test.go:33: 八進位制[00000000 00000000 00000000 00000377 00000000 00000020 00000000 00000001 00000000 00000000 00000000 00000007 00000000 00000000 00000000 00000001]
    qimiao_test.go:34: 十六進位制000000ff001000010000000700000001
    qimiao_test.go:35: 十進位制[0 0 0 255 0 16 0 1 0 0 0 7 0 0 0 1]
    //     "github.com/imroc/biu"
    var b int32
    //入參二進位制字串
    err = biu.ReadBinaryString("[00000000 00000000 00000000 11111111]", &b)
    fmt.Println(b,err) //255 總長度

完整程式碼:

package main

import (
    "bytes"
    "compress/zlib"
    "encoding/binary"
    "encoding/json"
    "fmt"
    "github.com/gorilla/websocket"
    "github.com/andybalholm/brotli"
    "io"
    "log"
    "net/http"
)

// bilibili 彈幕demo
func main()  {

    ws, _, err := websocket.DefaultDialer.Dial("wss://tx-gz-live-comet-02.chat.bilibili.com/sub", nil)
    if err != nil {
        log.Println(err)
        return
    }
    // 根據抓包獲取
    str := `{"uid":335200741,"roomid":23202081,"protover":3,"platform":"web","type":2,"key":"2RyQso542LwGVwtSosyu3-dClNsXC0Q980FGyhtHGpbM9vTG4ef9oqPY5L9grtpKDqSeh6G64vg1EVDCxYrVo-GAqrgmncpAGPT3AlKmWu62mfnGr5gedG2fwAh6_x4_YkI8gJio16HT9eBHscQYOISJzw=="}`
    totalLen := 16 + len([]byte(str))
    strByte := []byte(str)


    dataBuff := bytes.NewBuffer([]byte{})

    // 總長度 4位元組
    if err := binary.Write(dataBuff,binary.BigEndian,int32(totalLen));err != nil {
        log.Println(err)
        return
    }
    // 頭部長度16 2位元組
    if err := binary.Write(dataBuff,binary.BigEndian,int16(16));err != nil {
        log.Println(err)
        return
    }
    // 協議版本號 固定1 2位元組
    if err := binary.Write(dataBuff,binary.BigEndian,int16(1));err != nil {
        log.Println(err)
        return
    }
    // 加入彈幕 固定協議7, 4位元組
    if err := binary.Write(dataBuff,binary.BigEndian,int32(7));err != nil {
        log.Println(err)
        return
    }

    // 常量 1固定 , 4位元組
    if err := binary.Write(dataBuff,binary.BigEndian,int32(1));err != nil {
        log.Println(err)
        return
    }


    err = ws.WriteMessage(websocket.BinaryMessage,append(dataBuff.Bytes(),strByte...))
    if err != nil {
        log.Println(err)
        return
    }
    for {
        // 等待資訊返回
        _, message, err := ws.ReadMessage()
        if err != nil {
            log.Println(err,111)
            return
        }

        log.Println(message[0:16],fmt.Sprintf("%08x",message[0:16]))
        //0 1 2 3 4 5 6 7 8
        headByte := bytes.NewBuffer(message[0:4])
        var headint32 int32
        err = binary.Read(headByte,binary.BigEndian,&headint32)
        if err != nil {
            log.Println(err,444)
            return
        }
        log.Println("長度",headint32,headint32 - 16)
        // 對第八位 解碼規則判斷 0 為正常可見的字串 2 為需要zlib解碼的關鍵資訊
        buf := message[6:8]
        bytesBuffer := bytes.NewBuffer(buf)
        var ageen int16
        err = binary.Read(bytesBuffer,binary.BigEndian,&ageen)
        if err != nil {
            log.Println(err,222)
            return
        }
        switch ageen {
        case 2:
            b := bytes.NewReader(message[16:])
            r, _ := zlib.NewReader(b)
            bs, _ := io.ReadAll(r)
            log.Printf("zip壓縮: %s", string(bs))
        case 0:
            b := message[16:]
            log.Println("json彈幕", string(b))
        case 1:
            b := message[16:]
            var renqiInt int32
            bytesBuffers := bytes.NewBuffer(b)
            err = binary.Read(bytesBuffers,binary.BigEndian,&renqiInt)
            if err != nil {
                log.Println(err,222)
                return
            }
            log.Println("人氣:",renqiInt)
        case 3:
            b := bytes.NewReader(message[16:])
            r := brotli.NewReader(b)
            bytess,_ := io.ReadAll(r)
            log.Println("brotli壓縮", string(bytess),"解壓前長度:" ,len(message[16:]) ,"解壓長度:",len(bytess))
        default:
            log.Println("其他:",ageen)
        }

    }


}

type BiRes struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Ttl     int    `json:"ttl"`
    Data    struct {
        Group            string  `json:"group"`
        BusinessId       int     `json:"business_id"`
        RefreshRowFactor float64 `json:"refresh_row_factor"`
        RefreshRate      int     `json:"refresh_rate"`
        MaxDelay         int     `json:"max_delay"`
        Token            string  `json:"token"`
        HostList         []struct {
            Host    string `json:"host"`
            Port    int    `json:"port"`
            WssPort int    `json:"wss_port"`
            WsPort  int    `json:"ws_port"`
        } `json:"host_list"`
    } `json:"data"`
}

type BiliReq struct {
    Uid      int    `json:"uid"`
    Roomid   int    `json:"roomid"`
    Protover int    `json:"protover"`
    Platform string `json:"platform"`
    Type     int    `json:"type"`
    Key      string `json:"key"`
}


func GetInfoHttpUrl() *BiRes {
    var bili = new(BiRes)
    res, err := http.Get("https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=23202081&type=0")
    if err != nil {
        log.Println(err)
        return nil
    }

    defer res.Body.Close()
    b, err := io.ReadAll(res.Body)
    err = json.Unmarshal(b, &bili)
    if err != nil {
        return nil
    }
    return bili
}

可以借鑑bilibili的資料壓縮,通過brotli,解壓前和解壓後的資料大小還是差別蠻大的

其他

int 和 []byte 互轉

//isSymbol表示有無符號
func BytesToInt(b []byte, isSymbol bool)  (int, error){
    if isSymbol {
        return bytesToIntS(b)
    }
    return bytesToIntU(b)
}


//位元組數(大端)組轉成int(無符號的)
func bytesToIntU(b []byte) (int, error) {
    if len(b) == 3 {
        b = append([]byte{0},b...)
    }
    bytesBuffer := bytes.NewBuffer(b)
    switch len(b) {
    case 1:
        var tmp uint8
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 2:
        var tmp uint16
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 4:
        var tmp uint32
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    default:
        return 0,fmt.Errorf("%s", "BytesToInt bytes lenth is invaild!")
    }
}



//位元組數(大端)組轉成int(有符號)
func bytesToIntS(b []byte) (int, error) {
    if len(b) == 3 {
        b = append([]byte{0},b...)
    }
    bytesBuffer := bytes.NewBuffer(b)
    switch len(b) {
    case 1:
        var tmp int8
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 2:
        var tmp int16
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 4:
        var tmp int32
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    default:
        return 0,fmt.Errorf("%s", "BytesToInt bytes lenth is invaild!")
    }
}


//整形轉換成位元組
func IntToBytes(n int,b byte) ([]byte,error) {
    switch b {
    case 1:
        tmp := int8(n)
        bytesBuffer := bytes.NewBuffer([]byte{})
        binary.Write(bytesBuffer, binary.BigEndian, &tmp)
        return bytesBuffer.Bytes(),nil
    case 2:
        tmp := int16(n)
        bytesBuffer := bytes.NewBuffer([]byte{})
        binary.Write(bytesBuffer, binary.BigEndian, &tmp)
        return bytesBuffer.Bytes(),nil
    case 3,4:
        tmp := int32(n)
        bytesBuffer := bytes.NewBuffer([]byte{})
        binary.Write(bytesBuffer, binary.BigEndian, &tmp)
        return bytesBuffer.Bytes(),nil
    }
    return nil,fmt.Errorf("IntToBytes b param is invaild")
}

hex string 和 []byte 互轉

[]byte
import "hex"
// 省略部分程式碼....

hexStr := "fee9ecaadafeee72d2eb66a0bd344cdd"
data, err := hex.DecodeString(hexStr)
if err != nil {
// handle error
}

轉 hex string
import (
"fmt"
"crypto/md5"
)
// 省略部分程式碼
data := "test string"
// md5.Sum() return a byte array
h := md5.Sum([]byte(data))

// with "%x" format byte array into hex string
hexStr := fmt.Sprintf("%x", h)

位元組大小端介紹

大端模式:高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端;

小端模式:低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端;
如下 彈幕協議前4個位元組如圖

    十進位制: 255
    大端16進位制: 000000ff
    小端16進位制: ff000000

    var value uint32 = 255
    by := make([]byte,4)
    binary.LittleEndian.PutUint32(by,value)
    sixStr := hex.EncodeToString(by)
    resSmall := 255 // 小端
    log.Println(resSmall,sixStr,*(*byte)(unsafe.Pointer(&resSmall)) == 0xff)
    // 輸出結果
    2022/05/09 10:29:00 255 ff000000 true

相關連結:
github.com/lovelyyoshino/Bilibili-...

blog.csdn.net/xfgryujk/article/det...

大小端https://segmentfault.com/a/1190000039738719?utm_source=sf-similar-article#comment-area

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章