通過源碼可以得到以下MatchPlayInfo序列的生成代碼
const protobuf = require("protobufjs");
var i = protobuf.Reader, a = protobuf.Writer, r = protobuf.util;
MatchStepInfo = function () {
function t(t) {
if (t)
for (var e = Object.keys(t), o = 0; o < e.length; ++o) null != t[e[o]] &&
(this[e[o]] = t[e[o]])
}
return t.prototype.chessIndex = 0, t.prototype.timeTag = 0, t.create = function (
e) {
return new t(e)
}, t.encode = function (t, e) {
return e || (e = a.create()), null != t.chessIndex && Object.hasOwnProperty
.call(t, "chessIndex") && e.uint32(8).int32(t.chessIndex), null !=
t.timeTag && Object.hasOwnProperty.call(t, "timeTag") && e.uint32(
16).int32(t.timeTag), e
}, t.decode = function (t, e) {
t instanceof i || (t = i.create(t));
for (var o = void 0 === e ? t.len : t.pos + e, n = new MatchStepInfo; t
.pos < o;) {
var a = t.uint32();
switch (a >>> 3) {
case 1:
n.chessIndex = t.int32();
break;
case 2:
n.timeTag = t.int32();
break;
default:
t.skipType(7 & a)
}
}
return n
}, t
}(), MatchPlayInfo = function () {
function t(t) {
if (this.stepInfoList = [], t)
for (var e = Object.keys(t), o = 0; o < e.length; ++o) null != t[e[o]] &&
(this[e[o]] = t[e[o]])
}
return t.prototype.gameType = 0, t.prototype.mapId = 0, t.prototype.mapSeed = 0,
t.prototype.stepInfoList = r.emptyArray, t.create = function (e) {
return new t(e)
}, t.encode = function (t, e) {
if (e || (e = a.create()), null != t.gameType && Object.hasOwnProperty.call(
t, "gameType") && e.uint32(8).int32(t.gameType), null != t.mapId &&
Object.hasOwnProperty.call(t, "mapId") && e.uint32(16).int32(t.mapId),
null != t.mapSeed && Object.hasOwnProperty.call(t, "mapSeed") && e.uint32(
24).int32(t.mapSeed), null != t.stepInfoList && t.stepInfoList.length
)
for (var o = 0; o < t.stepInfoList.length; ++o) MatchStepInfo
.encode(t.stepInfoList[o], e.uint32(34).fork()).ldelim();
return e
}, t.decode = function (t, e) {
t instanceof i || (t = i.create(t));
for (var o = void 0 === e ? t.len : t.pos + e, n = new MatchPlayInfo; t
.pos < o;) {
var a = t.uint32();
switch (a >>> 3) {
case 1:
n.gameType = t.int32();
break;
case 2:
n.mapId = t.int32();
break;
case 3:
n.mapSeed = t.int32();
break;
case 4:
n.stepInfoList && n.stepInfoList.length || (n.stepInfoList = []),
n.stepInfoList.push(MatchStepInfo.decode(t,
t.uint32()));
break;
default:
t.skipType(7 & a)
}
}
return n
}, t
}()
var operationList = []
function addOp(t, e) { //增加操作
void 0 === e && (e = -100);
var o = {
id: t, // 操作卡片的id,從levelData第一層開始按順序編号
time: Date.now() // 操作時間
};
operationList.push(o)
}
function sleep(delay) {
for (var t = Date.now(); Date.now() - t <= delay;);
}
let range = n => [...Array(n).keys()]
for (let i of range(50)) { // 生成了50次操作
addOp(i);
sleep(Math.random() * 10); // 模拟操作過程中的等待
}
console.log(operationList)
for (var u = operationList, p = [], d = 0, h = 0; h < u.length; h++) // 把時間戳轉換為兩次操作的間隔
p.push({ chessIndex: u[h].id, timeTag: 0 == d ? 0 : u[h].time - d }), d = u[h].time;
console.log(p)
GAMEDAILY = 3
GAMETOPIC = 4
for (var f = { gameType: GAMEDAILY, stepInfoList: p }, y = MatchPlayInfo.create(f), v = MatchPlayInfo.encode(y).finish(), b = "", _ = 0; _ < v.length; _++)
b += String.fromCharCode(v[_]); // 序列化
var data = Buffer.from(b).toString('base64');
console.log(data);
data = Buffer.from(data, 'base64');
console.log(data);
console.log(MatchPlayInfo.decode(data));
分析一下MatchPlayInfo的生成操作
首先可以得知MatchPlayInfo是由stepInfoList和gameType組成的,stepInfoList裡有兩個參數分别是chessIndex和timeTag,分别記錄點選卡片id和兩次操作間隔
觀察MatchPlayInfo.encode可以看到
e = a.create()
會建立一個protobuf.Writer對象
e.uint32(8).int32(t.gameType)
會建立序列"\x08\x03",雖然寫着是32但是經過調試發現是1位元組的,不知道是為什麼
MatchStepInfo.encode(t.stepInfoList[o], e.uint32(34).fork()).ldelim()
這裡首先插入1位元組34變成"\x08\x03\x22",接着fork函數會生成一個分叉,類似于git等待處理完這個分支後調用ldelim就會把目前分支上内容的長度和内容合并回主分支,分析MatchStepInfo.encode後可以得知會插入4個位元組,分别是[8,chessIndex,16,timeTag],此時序列就是"\x08\x03\x22"+“\x04”+“\x08\x00\x10\x00”=“\x08\x03\x22\x04\x08\x00\x10\x00”,
重複這個過程把stepInfoList裡的chessIndex和timeTag循環增加到序列,可以得知生成過程是MatchPlayInfo=“\x08\x03“+(”\x04\x08\x??\x10\x??"*卡牌個數)
下面代碼,替換’t’值,放python直接跑,就能增加通關次數
import struct
import base64
import requests
# 安裝依賴 pip install requests
# 蘋果手機抓包,商城下載下傳Stream,證書配置參考Stream點選開始抓包,打開微信小程式-羊了個羊,開始遊戲,打開Stream,停止抓包。
# Stream檢視抓包曆史,打開最新記錄,找到"cat-match.easygame2021.com/sheep/v1/",點選進入詳情,再點選請求選項。
# 't'值就是t:後面的那一段。到conntent-type之前結束。
headers = {
't':'',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.27(0x18001b37) NetType/4G Language/zh_CN',
'Referer': 'https://servicewechat.com/wx141bfb9b73c970a9/23/page-frame.html'
}
url = 'https://cat-match.easygame2021.com/sheep/v1/game/personal_info?'
r = requests.get(url, headers=headers)
print(r.json())
url = 'https://cat-match.easygame2021.com/sheep/v1/game/map_info_ex?matchType=3'
r = requests.get(url, headers=headers)
map_md5 = r.json()['data']['map_md5'][1]
url = f'https://cat-match-static.easygame2021.com/maps/{map_md5}.txt' # 由于每天擷取的地圖不一樣,需要計算地圖大小
r = requests.get(url)
levelData = r.json()['levelData']
p = []
for h in range(len(sum(levelData.values(), []))): # 生成操作序列
p.append({'chessIndex': 127 if h > 127 else h, 'timeTag': 127 if h > 127 else h})
GAME_DAILY = 3
GAME_TOPIC = 4
data = struct.pack('BB', 8, GAME_DAILY)
for i in p:
c, t = i.values()
data += struct.pack('BBBBBB', 34, 4, 8, c, 16, t)
MatchPlayInfo = base64.b64encode(data).decode('utf-8')
print(MatchPlayInfo)
# 每日通關
url = 'https://cat-match.easygame2021.com/sheep/v1/game/game_over_ex?'
r = requests.post(url, headers=headers,
json={'rank_score': 1, 'rank_state': 1, 'rank_time': 1663723, 'rank_role': 1, 'skin': 7,
'MatchPlayInfo': MatchPlayInfo})
print(r.json())
url = 'https://cat-match.easygame2021.com/sheep/v1/game/personal_info?'
r = requests.get(url, headers=headers)
print(r.json())