[python] ylgy攻略 用魔法打败魔法

发布时间:2026/9/14 11:14:27

[python] ylgy攻略 用魔法打败魔法 文章目录1. 引言2. 源码分析MatchPlayInfo 的生成逻辑3. 序列化过程详解protobuf 与 JSON 序列化对比4. 关键问题数值大于 127 导致失败5. 实战Python 脚本一键通关6. 总结1. 引言通过逆向分析微信小程序「羊了个羊」的源码可以获取到MatchPlayInfo序列的生成逻辑。本文将逐步拆解其 protobuf 序列化过程并提供一个可直接运行的 Python 脚本用于生成合法的通关数据。下面是本文的整体流程逆向分析小程序源码获取 MatchPlayInfo 生成逻辑分析 protobuf 序列化过程发现数值大于 127 的坑点编写 Python 脚本生成合法数据调用接口完成通关2. 源码分析MatchPlayInfo 的生成逻辑通过源码可以得到以下 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 newt(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 newt(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));3. 序列化过程详解分析一下 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?? * 卡牌个数)下面是 MatchPlayInfo 的序列化结构图MatchPlayInfogameTypestepInfoListchessIndextimeTag\\x08\\x03\\x08\\x??\\x10\\x??\\x08\\x03 (\\x04\\x08\\x??\\x10\\x?? * 卡牌个数)protobuf 与 JSON 序列化对比维度protobufJSON数据大小二进制紧凑编码体积小如MatchPlayInfo每条操作仅 6 字节文本格式含字段名和引号体积大同样数据可能膨胀数倍编码速度二进制直接写入无需字符串解析速度快需要字符串拼接与解析速度较慢可读性二进制不可直接阅读需借助工具或解码纯文本人类可直接阅读和调试适用场景对性能、流量敏感的高频接口如游戏对战数据上报调试、配置、跨语言简单数据交换为什么小程序选择 protobuf 而非 JSON核心原因在于性能和流量。羊了个羊这类小游戏在每局对局中会频繁上报操作序列数据量大且实时性要求高。protobuf 采用二进制编码去掉了字段名等冗余信息能显著压缩数据体积、降低网络传输耗时同时二进制编码省去了 JSON 的字符串解析开销编解码更快。此外protobuf 通过.proto文件定义强类型结构前后端可自动生成代码保证字段一致减少联调成本。相比之下JSON 虽然可读性好但在高频、大数据量的场景下体积和性能都不占优势因此小程序选择了 protobuf。4. 关键问题数值大于 127 导致失败然而生成的序列无法增加通关次数研究了一下发现stepInfoList里的值大于 127 的数值是错误的不知道是什么原因于是过滤掉大于 127 的数可以成功增加通关次数5. 实战Python 脚本一键通关下面代码替换t值放 Python 直接跑就能增加通关次数importstructimportbase64importrequests# 安装依赖 pip install requests# 苹果手机抓包,商城下载Stream,证书配置参考(https://blog.csdn.net/weixin_44504146/article/details/121946958),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}urlhttps://cat-match.easygame2021.com/sheep/v1/game/personal_info?rrequests.get(url,headersheaders)print(r.json())urlhttps://cat-match.easygame2021.com/sheep/v1/game/map_info_ex?matchType3rrequests.get(url,headersheaders)map_md5r.json()[data][map_md5][1]urlfhttps://cat-match-static.easygame2021.com/maps/{map_md5}.txt# 由于每天获取的地图不一样需要计算地图大小rrequests.get(url)levelDatar.json()[levelData]p[]forhinrange(len(sum(levelData.values(),[]))):# 生成操作序列p.append({chessIndex:127ifh127elseh,timeTag:127ifh127elseh})GAME_DAILY3GAME_TOPIC4datastruct.pack(BB,8,GAME_DAILY)foriinp:c,ti.values()datastruct.pack(BBBBBB,34,4,8,c,16,t)MatchPlayInfobase64.b64encode(data).decode(utf-8)print(MatchPlayInfo)# 每日通关urlhttps://cat-match.easygame2021.com/sheep/v1/game/game_over_ex?rrequests.post(url,headersheaders,json{rank_score:1,rank_state:1,rank_time:1663723,rank_role:1,skin:7,MatchPlayInfo:MatchPlayInfo})print(r.json())urlhttps://cat-match.easygame2021.com/sheep/v1/game/personal_info?rrequests.get(url,headersheaders)print(r.json())下面是 Python 脚本的执行流程抓包获取 t 值请求 personal_info 获取用户信息请求 map_info_ex 获取地图信息获取地图文件并解析 levelData生成操作序列 p序列化生成 MatchPlayInfoPOST game_over_ex 提交通关数据验证通关结果6. 总结本文通过逆向分析「羊了个羊」小程序的 protobuf 序列化逻辑梳理了MatchPlayInfo的完整生成流程并给出了一个可直接运行的 Python 脚本。核心要点如下MatchPlayInfo由gameType和stepInfoList组成其中stepInfoList记录每一步的chessIndex卡片 id和timeTag操作间隔。序列化时每个操作会被编码为\x04\x08\x??\x10\x??的固定格式。关键坑点chessIndex和timeTag的值不能超过 127否则序列化结果无法通过服务端校验导致通关失败。
延伸阅读

更多相关文章

2026/9/14 11:14:27

Qt/C++植物大战僵尸课设源码拆解:对象树、信号槽与游戏循环

简介:这份基于Qt和C框架编写的简易植物大战僵尸游戏源码,定位为计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的课程设计、毕业设计及课程大作业参考项目,也适合想要入门Qt图形界面编程和游戏开发的…

2026/9/14 11:14:27

Flutter与OpenHarmony跨平台开发实战:美食烹饪助手

1. 项目概述:当Flutter遇上OpenHarmony的美食之旅作为一名同时接触过Flutter和OpenHarmony的开发者,当我看到这个项目标题时,立刻意识到这是一个极具代表性的跨平台开发案例。Flutter作为Google推出的跨平台UI工具包,与华为主导的…

2026/9/14 12:09:32

C++实现HTTP服务器及阿里云ECS部署实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/14 12:04:32

SpringBoot+Vue音乐推荐系统:协同过滤全流程实现

简介:这是一套基于Spring Boot与Vue实现的协同过滤音乐推荐系统,专为计算机专业本科生毕业设计、课程设计及期末大作业打造,兼顾算法原理理解与全栈工程实践,适合零基础开发者快速上手。资源包共893个文件,涵盖104个Ja…

2026/9/14 2:17:50

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/14 0:03:22

KCF目标跟踪算法与OTB工程实现:毕业设计实战解析

简介:这是一份基于KCF核相关滤波算法、融合尺度池与抗遮挡处理的目标检测跟踪MATLAB完整源码,主要面向计算机相关专业准备毕业设计、课程设计或期末大作业的学生,也适合需要项目实战练习的初学者。源码在OTB数据集上完成验证,能够…

2026/9/14 0:03:22

语音情感识别实战:Keras实现LSTM、CNN、SVM与MLP多模型对比

简介:面向语音情感识别入门与进阶开发者,这份基于Keras的项目源码完整实现了LSTM、CNN、SVM、MLP四种模型,兼容Python3.8与Keras/TensorFlow2环境。压缩包内含49个文件,大小约70.31MB,主体包括Python脚本、yaml/json配…

2026/9/14 11:59:31

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/12 14:32:17

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/14 11:22:57

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码