广告可跳过
广告依赖于wx.createRewardVideo来实现,搜索他就可以找到相关逻辑
将所有isEnded替换为true即可
a.isEnded [A-z]\.isEnded true
100%掉泡泡
泡泡掉落是由bubbleRate控制,修改判断bubbleRate的逻辑即可
过高等级的泡泡会要求使用钻石解锁,初步判断是bubbleCost控制,但是ui要求的钻石和bubbleCost对不上。。。
0<g.bubbleRate&&z.random(1,100)<=g.bubbleRate 3<=g.level&&g.level<=6 (console.log('MMMTT',g),(g.bubbleCost||0)<=5) true
改进掉率
- 掉率由z.random函数控制,全局搜索可以看到只有两种pattern
a = z.random(1, 100), a <= XXXRate- 用于各种合并掉落
a = z.random(1, XXXWeightSum), for (...) a <= XXXWeights[i]- 用于可以出多种item的box
- 实验将z.random始终返回b:
Math.floor(Math.random()*(b-a+1)+a) b
发现蒸锅始终返回锅铲
很简单的思路:
- 把所有的random(1,100)替换掉,变成random(1, 1),这样就100%会掉落
- 把剩下的都在random里面处理,让random(a,b)变成random(b/2,b)
- 后续发现1/2不够,2/3比较好
最终PATCH:
- 空间不够:测试发现static关键字在use strict时不能省略,所以只能将2b/3带入到算式中的a化简一下
static random(a,b){return Math.floor(Math.random()*(b-a+1)+a)} static random=(a,b)=>Math.floor(Math.random()*(b/3+1)+b/3*2); static random(a,b){return b==100?0:Math.random()>0.5?b:b/2} random(1,100) random(1,1)
Dump Elements数据
- 方案1:一开始不知道怎么看console log,只知道在出错的时候会报GameConsole log,所以想到通过throw Error把数据带出来
方案1 Dump Elements结果0<g.bubbleRate&&z.random(1,100)<=g.bubbleRate ((g)=>{throw Error(JSON.stringify(g))})(g)
- 方案2:后来知道小程序用vConsole输出日志,并且需要打开开发模式才可以输出日志,所以换成直接在vConsole里面把整个ModelManager拖出来
- 本来想直接和上面用一样的地方用t.globals来输出,但是似乎被shadow掉了,显示t是个Function
- 所以改成在ModelManager初始化的位置进行魔改
class t { static init() { ModelManager.rua(a => { this.reload(a) }) } static reload(a) { this.Zd = a; this.global = a.global; this.rza = (new Date) .getTimezoneOffset() / 60; this.mc = z.arrayToDict(a.goods, "id"); this.R1 = z.Yn(a.goods, "tab"); this.Nb = z.arrayToDict(a.product, "id"); this.up = z.arrayToDict(a.commonReward, "id"); this.videoAdRewardIdDict = z.arrayToDict(a.videoAdReward, "id"); this.Sna = z.arrayToDict(a.mark, "id"); z.arrayToDict(a.prop, "id"); this.elementsIdDict = z.arrayToDict(a
方案2 Dump整个Meta;x.Na(a.shopGifts,"id"); ;a.MT=JSON.stringify(a); ;x.Na(a.divisions,"id"); ;console.log('MT',a.MT);
掉泡泡改进
- 思考:为什么g.bubbleCost判断是否免费不准确?
- 通过console.log发现,是因为g.bubbleCost是合成前的element的,而判断是否可以免费则是按照mergeId的bubbleCost来计算的
- Patch:
- 有个辅助函数可以直接用elementId取到bubbleCost
// 逆向后: static getBubbleCost(a) { return (a = this.elementsIdDict[a]) ? a.bubbleCost : 0 } // 逆向前: static uia(a){return(a=this.dc[a])?a.bubbleCost:0}
(console.log('MMMTT',t),(g.bubbleCost||0)<=5) (this.uia(g.mergeId)||0)<=5
掉泡泡终极版
- 实验:为所有element开启videoBubble,发现了这样的现象
return 0<a&&a<=this.global.videoBubbleDiamond console.log('MMMTT',a);return true
- 思考:什么控制了UI界面的是否能看视频
- bubbleCost > 0,才可以看视频
- bubbleCD要够大,要不然不够看视频
- Patch:魔改meta数据
- 需要patch的id是:
- 活动经验id:996012,996022,996032,996042,996052
- 星星碎片id:995011,995021,995031
- 尝试在小程序js里面直接patch:空间不够
- 外部patch:分析meta.json的来源
- 加载:
Laya.loader.getRes('res/data/meta.json') - 查阅Laya.loader文档可知有getRes和load方法,返回搜索小程序源码发现果然也有
Laya.loader.load('res/data/meta.json') - 对cdnminigame域名开启抓包发现果然有对应meta.json加载,但是是带着版本号的
- 尝试在Surge里面直接动态替换
- 正则表达式直接替换:失败,replace几十秒都跑不完
function replaceBubbleCD(str, itemId, newCD) { const regex = new RegExp(String.raw `\{(.*?)"bubbleCD":([\d]+),(.*?)"id":(${itemId})(.*?)\}`, 'gm') const subst = String.raw `\{$1"bubbleCD":${newCD},$3"id":$4$5\}`; return str.replace(regex, subst); }
- 在服务器上写了个Python脚本魔改json,在surge上rewrite header重定向到服务器的json上
import sys import json metaFileName = sys.argv[1] a = json.load(open(metaFileName)) patched = False for c in a['elements']: patchCD = False if c['id'] in [996012,996022,996032,996042,996052]: # xianhuaminsu patchCD = True if c['id'] in [995011,995021,995031]: # xingxingsuipian patchCD = True if patchCD: patched = True c['bubbleCD'] = 60 c['bubbleCost'] = 1 assert patched #print(json.dumps(a)) with open('meta_patched/' + metaFileName, 'w') as f: f.write(json.dumps(a))
改进看广告
- 目前看广告需要点击三次ui:点击获取按钮、广告关闭按钮、确认广告关闭按钮
- 如果失败(提示视频不存在),则需要重复这一个流程
- 尝试将整个流程合并为1步
- 尝试1:替换callback “playRewardVideo creward”,直接执行回调a(true)
- 对应逻辑:
static Pk(a, b = null) { if (Na.createRewardedVideoAd) { b = b || "s68vlg3oe3"; var c = this.Kd(b); var d = g => { C.log("playRewardVideo creward"); c.state = 3; c.offClose(d); c.offLoad(e); c.offError(f); this.Kd(b); a && a(1 == g.isEnded) }; var e = () => { C.log("playRewardVideo cload"); c.state = 1; c.offLoad(e); c.show() };
var .=.=>{..log\("playRewardVideo creward"\); var d=g=>{C.log("playRewardVideo creward"); return a && a(!0); {
if (Na.createRewardedVideoAd) {,直接跳过整个逻辑执行回调a(true)- 对应逻辑:
static Pk(a, b = null, c = null) { if (pa.createRewardedVideoAd) { b = b || this.Jl; var d = this.Kd(b); var e = h => { d.state = 3; d.offClose(e); d.offLoad(f); d.offError(g); this.Kd(b); h && h.isEnded || void 0 === h ? a && a(!0) : a && a() }; var f = () => { d.offLoad(f); d.show(); c && c() }; var g = h => { d.offClose(e); d.offLoad(f); d.offError(g); C.error(h); Controller.open("TipDialog", v.OppoSdkManager(200274)); a && a(!1) }; C.log("playVideo:" + d.state); 0 == d.state ? (d.onClose(e), d.onLoad(f), d.onError(g)) : 1 == d.state ? (d.onClose(e), d.onLoad(f), d.onError(g), d .show(), c && c()) : (Controller.open("TipDialog", v.OppoSdkManager(200274)), a && a(!1)) } else a && a(!1) }
if \(([A-z]{1,3})\.createRewardedVideoAd\) { if(AA.createRewardedVideoAd){ return a&&a(!0);"$1";if(1){
掉率改进
- 掉率的内部机制是看clickIds里面的count来算
- 一般来说,越高级的箱子会同时掉落更高级的物品,低级箱子只能掉落1级,高级则可以掉落2级3级
- 稀有的物品,如刀片、面包、饼干则掉率更低
- 考虑将掉率平衡一下:
- 将普通的物品,如串串、调料、蛋糕的掉率调低
- 让所有的物品只掉落箱子中最高级的(比如同时掉落1级2级,则直接只掉落2级)
handle([1101, 1102, 1103], 4) # zhengguo 有些时候需要二级的 handle([3101, 3102], 5) # chujvjia 有任务需要最低等级的调料3101 handle([2101, 2102, 2103], 3) # kaoxiang #if 2201 in clickIds: # kaoxiang mianbao # clickIds[2201]['count'] *= 4 if 2301 in clickIds: # kaoxiang binggan clickIds[2301]['count'] *=2 handle([5101]) # zhiwujia 需要置物架来生成冰盒子 handle([8101, 8102]) # denglong handle([71601, 71602]) # huapen handle([71501, 71502]) # luzi handle([71301, 71302]) # xuyuanping handle([8301, 8302]) # chayeguan(unk) handle([9401, 9402]) # gougou(unk) handle([99101,99102,99103,99104]) # jinbi hezi handle([99201,99202,99203,99204]) # zuanshi hezi handle([99301,99302,99303,99304]) # tili hezi handle([1001,1002]) # guoshen (zhengguo) handle([5001,5002]) # gouzi (zhiwujia) handle([8001,8002]) # zhutiao (denglong) # 金色行李箱 handle([98101], 3) # 少点 无限体力, 够多了 handle([99801,99802]) # 多点 加速时钟 handle([3001, 3002, 3003, 3004]) # 螺丝钉直接给最高级 handle([4001, 4002, 4003], 100) # 不要宣传海报!够多了 handle([9001, 9002, 9003, 9004], 100) # 不要扫垃圾的!够多了 handle([2001, 2002, 2003, 2004], 100) # 不要做烤箱的,够多了 # 蓝色行李箱 handle([1001, 1002, 1003, 1004], 100) # 不要锅把手,够多了 handle([5001, 5002, 5003, 5004], 100) # 不要排钩,置物架够多了
尝试改进看广告(失败)
- 看广告的内部请求
- 他发送的请求body是 id、adtype、ticket、extra
- id是adunit的编号,代表看了什么广告
- adtype是广告渠道,配置里面写的一般是tt和wx
- ticket代表要不要用广告跳过券
- extra里面一般是XX_YYYY_0,XX是物品的位置,YYYY是物品的id
- 当速度过快的时候(超过10s一次),服务器就会返回错误retcode
- 从这个请求入手,尝试随机使用不同adtype
A.Gua(f.id,d);for(b=0;b<f.adtype.length;++b) b=x.random(0,f.adtype.length-1);
- 尝试直接强行使用ticket
,ticket:e||0 ,ticket:1||0
掉率改进终极版
- 改进了掉率但是因为任务的不同,有时候需要稀有物品多一点,有时候需要少一点,完全依赖运气太麻烦了,需要很多的棋盘空间来容纳当前不需要的物品
- 解决思路:动态控制掉率
- 方案:
- 控制机制:修改meta.json,根据前面的实验,需要服务器动态修改
- 手机端配合:
- UI:使用boxjs,支持checkbox等很多控件
- 脚本:surge把boxjs的配置通过参数传给服务器
- 服务器:flask搓一个
- 效果: