月度归档:2017年07月

我和手游《魔卡幻想》的故事

    前言:

    13年的时候,因加入网易游戏,开始从事手游行业。当时手游刚刚兴起,网易的大话梦幻手游还未开始,阴阳师的制作人也未入职,腾讯没有王者农药,当时只有《大掌门》等代入感很差却引领了一代风潮的游戏,而《魔卡幻想》则是当时接触的手游中最流行最绚丽最NB的一款,现在大浪淘沙之后,手游行业大局已定,回望当年这段玩魔卡幻想的经历,不禁唏嘘。

接触魔卡,同时玩三号:

最近,流行玩《魔卡幻想》,作为一名手游测试人员,也开始玩,于是申请了一个屌丝号码,取名“大头包子”。闯关、打塔,打了一个多月,只有一张五星黑龙,很是悲催。于是花了40两银子在淘宝上买了一个天使开局,附送了一个凤凰号。所谓天使、凤凰,就是其中两张比较NB的五星卡牌,如下图所示。

 



图一:天使,我的最爱
图二:凤凰

 

 

所以,我现在就有了三个号码,一个屌丝开局号,一个天使号,一个凤凰号。虽然说,手游本身不太花费时间,但三个号一起玩,就很费时间了,而且和其他手游一样,魔卡也有一个很“变态”的设置,就是有些行动,你必须间隔10分钟或者1个消失才能执行一次,每天中午事儿点和晚上十点还有“魔神入侵”的活动,然后在魔神被打死前,你需要每隔3分钟打一次魔神,才能获得足够的金币奖励,这样你就不能花五分钟玩完游戏,然后放手去做其他事情,你必须每隔一段时间上去看看,很是神伤。

另外,魔卡高级卡牌的获取有三种方式,一种是魔卡碎片换取,一种是晶钻兑换,一种是刷塔(反复的对指定的关卡进行通关),不管你是不是RMB玩家,不管你是大R还是小R,或者是如我之辈的屌丝玩家,换取高级卡牌都是很考验人品的,有人说洗漱好之后获得五星卡牌几率高,有人说,晚上六点到六点十分几率高,有人说贴吧爆照第二天会出五星,所以,十分的纠结,魔卡的五星卡牌发放到底是什么样的规则呢?

 

总结一下,问题有二,

第一、费时间:玩多个号、每天刷塔,耗费时间,而且需要你一直惦记着。

第二、运行机制搞不清楚:高级卡牌靠人品,不免想起,魔卡到底是怎么工作的呢?

遭遇困境,尝试反编译:

想搞清楚魔卡到底是怎么工作的,第一个,很自然的想法就是反编译。对Android程序更熟悉,所以,试着反编译Android版本的apk

 

我们知道android的安装文件.apk实际上是一个压缩包,所以将你下载下来的魔卡幻想的apk文件后缀改为zip并解压(我放置在D:\works\mokahuanxiang\下面),得到如下文件:

先看第一个文件夹assets下面,有两个文件夹,一个是卡牌的底图,一个是符文的底图,如下:

图:apk解压后的卡片底图,可以看到天使也在其中

图:apk解压后的符文图片

其他几个文件对我们用处不大,我们关注的代码都放置在classes.dex里面,游戏逻辑应该都写在里面了,不过classes.dex是经过Google制定的一种可以在android dalvik虚拟机上运行的格式,要查看里面的内容,先用dex2jar转换一下格式,命令行模式下运行:

dex2jar classes.dex

会生成classes_dex2jar.jar,这个jar包就可以利用jdgui来反编译,进入jdgui文件夹双击jd-gui.exe,打开上面生成的jarclasses_dex2jar.jar,即可看到源代码了,代码如下图:

图:反编译后的java代码

 

 

致此,大功告成!但深入去看的时候,找遍了所有的代码,发现竟然完全没有逻辑代码,比如卡牌强化是如何强化的,魔神是如何的,盗贼是什么情况下出现的,纠结呀。那么逻辑代码都是写在哪里的呢?

反编AIR,柳暗又花明

可以看到,dex2jar的文件夹下面有一个shanks.flash.ane的类,aneAdobe AIR Native Extension,因为Adobe AIR是跨平台的,macwindowsAndroidIos都能运行,但也有其局限性,比如在Android上面,AIR就无法调用Android系统级别的API,比如摄像头,比如系统通知等,如果想做类似的事情,就需要先用android写一段本地代码,让AIR和这段代码通信,进而完成类似的工作,这也就是魔卡游戏的总体工作机制,如下图所示:

图:魔卡游戏架构图

 

 

所以,大胆猜测,大部分游戏逻辑是写在Adobe AIR层的,据百度百科,AIR是针对网络与桌面应用的结合所开发出来的技术,可以不必经由浏览器而对网络上的云端程式做控制,这样的优势,就是跨平台,主要的逻辑代码无论在Android上还是在IOS上,甚至在Windows Phone上都是一致的,在每个平台上,只要实现对应的系统API即可,这也是为什么,魔卡的账号体系在IOS上和Android上是统一的,不像某掌门游戏。

既然确定,游戏逻辑写在AIR层,于是开始找对应的实现文件,终于发现了:

图:找到逻辑代码所在处

得到了Cardmain.swf文件,在浏览器下直接打开看看,没有任何反应,只是白屏,怎么办呢?再来反编译,使用业界出名的硕思闪客精灵进行反编译试试,安装了一个企业版,打开这个swf文件,经过“漫长”的等待,终于,结果出来了,可以看到,游戏界面主要的形状、图像、音频、视频以及关键的动作脚本都是在这个swf文件里面的。

图:反编译后的Cardmain.swf文件

点开对应的额动作复选框,查看对应的脚本代码。其中有几个关键的文件和目录,其中一个是URLconfig文件,是记录的所有的客户端命令所对应的服务器指令地址,比如:

这个card.php?do=GetAllCard就是用来获的指定用户的所有的卡片的信息的,后面还有很多:

图:反编译的UrlConfig文件

核心逻辑代码写在这里:

图:魔卡核心逻辑代码

 

以卡牌/符文强化为例,每张卡牌都对应着一定的升级金币和经验点数,每张符文也有对应的经验点数,这是每张卡牌固有的属性,当你升级的时候,是将被升级卡牌的金币和经验乘以一个系数进行累加,然后再计算被升级卡牌、符文的升级情况的,具体代码片段如下:

 

可以看到,一星卡牌的升级系数是0.6,二星卡牌是0.7,以此类推,也就是说,如果你吃三星卡牌,那么你将损失20%的经验,如果你吃五星卡牌的话,那么经验点将完全不会损失哦,不过又有几个非大R玩家能富裕到舍得吃五星呢~~

当然,里面还有很多其他秘密,待你去发掘。

协议分析,自动打迷宫

到目前为止,我们了解了魔卡的架构,还有一些底层的实现,我们知道,主题的逻辑是通过AIR来和服务器进行交互,所以,大部分命令都是server端完成的,那么,很自然的想到,分析一下协议,使用大名鼎鼎的wireshark软件抓包。但是,魔卡幻想是个手机游戏,想要抓手机给服务器端发包,怎么做呢?可以用模拟器来做。

大家知道BlueStacks本身就是一个android模拟器,这个模拟器运行在我的windows操作系统之上,那么,我在BlueStacks上玩魔卡幻想的时候,所有的命令包都会通过我操作系统的网卡发送出去,所以,利用wireshark抓取windows上的数据包,也就能够抓取到魔卡幻想给其服务器端的数据包了。

我们先看一下,打一个迷宫,大致的数据流如何。我现在在末日之塔,也就是7塔,第四层的最后一个怪,先在BlueStacks上进入这个状态,然后打开wireshark开始抓包,打怪,结束后停止录制,查看wireshark上的信息。

记录的网络通信包,如下图所示,因为wireshark记录了所有的网络交互,所以,我使用一个Filter来过滤我们所关注的数据包,filter为:

http && (ip.dst == 116.90.81.139) || (ip.src==116.90.81.139)

这句话的意思是,我过滤http协议的,目标地址为116.90.81.139或者源地址为116.90.81.139的协议包,其中116.90.81.139为魔卡幻想的web server。可以看到,其实,每次打一个怪,相当于,给服务器发送了一个POST命令!POST的数据内容为“manual=1&MapStageId=7&Layer=4&ItemIndex=13”,这句话什么意思呢?Manual=1,代表自动战斗,MapStageID=7,代表我打的是第7个塔,也就是末日之塔,ItemIndex=13是什么意思呢?为什么是13呢?暂时搞不清楚。

再刷几个怪,对比一下数据,我们发现,每进入一个楼层的时候,服务器端会返回一个本楼层的一个基本信息,我们看一下,末日之塔第四层的信息如下:

{"status":1,"data":{"Name":"\u672b\u65e5\u4e4b\u5854\u7b2c4\u5c42","BoxNum":2,"MonsterNum":2,"RemainBoxNum":2,"RemainMonsterNum":1,"Layer":4,"TotalLayer":5,"Map":{"IsFinish":false,"WallRows":[0,0,0,1,0,0,0,0,1,1,1,1,0,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0],"WallCols":[0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,1,1,1,0,0,1,0],"Items":[1,1,1,1,1,1,1,5,1,1,1,1,1,1,1,1,1,3,1,1,1,1,6,1,4,6,1,1,6,1,1,1]}},"version":{"http":"201302090","stop":"","appversion":"version_1","appurl":"ios:\/\/xxx"}}

 

其中Name字段是unicode编码,然后用gb2312编码打印出来,"\u672b\u65e5\u4e4b\u5854\u7b2c4\u5c42" 就是“末日之塔第4层”,后面的字段顾名思义,重点说一下Items字段,我们发现,"Items" : [1,1,1,1,1,1,1,5,1,1,1,1,1,1,1,1,1,3,1, 1,1,1, 6, 1 , 4, 6, 1,1,6,1,1,1 ] 28位,我们调整一下格式:

[1,1,1,1,1,1,1,5,

1,1,1,1,1,1,1,1,

1,3,1,1,1,1,6,1,

4,6,1,1,6,1,1,1]

再和下图对比一下,

你会发现,这个Items字段中,1代表普通方块,5代表是向上的楼梯,4代表向下的楼梯,6代表已经被打掉的宝箱或者怪物,同理可以分析,2代表宝箱,3代表怪物,然后indexitem就是方块的索引!

这样,我们自动打塔的思路就确定了:

先获得每一层的信息,然后分析一下这一层的怪物和宝箱的情况,得到对应的indexitem,然后再POST给对应的server就可以完成了,上代码:

其中maze_info返回的是一个指定地图和楼层的json格式的信息,我使用exec将其转化为一个dict对象,然后获得其中的Items字段,如果这个字段是2或者3的话,我就去打,最后再打去楼上的楼梯口,很简单吧。

 

具体如何打的呢?请看maze_battle函数:

 

关键是我们的PostMessage函数,我们最终都是通过这个函数,组成特定的魔卡服务器能识别的http协议包,利用Post命令发送出去,发送的http协议包的内容为:

 

POST /maze.php?do=Info&v=9945&phpp=ANDROID&phpl=ZH_CN&pvc=1.2.0&pvb=2013-04-16%209%3A18%3A23 HTTP/1.1

Host: s9.mysticalcard.com

Accept-Encoding: identity

Content-Length: 20

Accept: text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, text/css, image/png, image/jpeg, image/gif;q=0.8, application/x-shockwave-flash, video/mp4;q=0.9, flv-application/octet-stream;q=0.8, video/x-flv;q=0.7, audio/mp4, application/futuresplash, */*;q=0.5

User-Agent: Mozilla/5.0 (Android; U; zh-CN) AppleWebKit/533.19.4 (KHTML, like Gecko) AdobeAIR/3.6

Connection: Keep-Alive

Cookie: _sid=qjl7qn8js501756f57lrevet55

Cache-control: no-cache

Content-Type: application/x-www-form-urlencoded

Refer: app:/assets/CardMain.swf

 

Layer=2&MapStageId=5

 

 

使用pythonurllib库来进行http协议发送,另外,由于server端可能返回gzip格式的数据内容,所以需要利用pythongzip库对gzip格式的返回数据进行解码。

这样,刷塔的程序就大致成型了。

最终,你只需要两行代码就能完成刷塔工作了:

这两句话的意思是,我新建一个魔卡幻想的对象,然后刷第五塔的一二三四层!搞定。

再举一个简单的例子,就是对某个地图进行探索,返回信息,下图为我进行一次探索的运行截图,我对编号为44的关卡进行探索,获得了740点经验,1280个金币,以及一个8号魔幻碎片。

图:探索地图协议流

图:探索地图获得奖励

除了自动打地图、自动探索外,你可以利用协议做更多的事情,比如每隔10分钟获取一次盗贼,如果有盗贼,则挑选级别最高的打。再比如每天中午12点魔神入侵的时候,你每隔3分钟打一次魔神,这样会累积比较高的积分,也不用你总是惦记着打魔神这回事了。

登陆破解,大江已东去

上文中,利用http协议进行自动打塔,有一个关键的地方没有说明,就是登陆。魔卡幻想本身具有自动登陆的功能,其实现的原理就是利用Cookie信息,如果你发送http请求的时候,http头携带了Cookie字段,而且Cookie字段和你最近登录一次的设备匹配的话,那么就不需要重新登录,而可以直接进行一些对应操作了。魔卡对cookie信息的处理流程大致是,如果你发送过来的cookie信息在我的server端有记录,而且最近登录设备匹配的话,那么我就返回给你所有你需要的信息。这样,如果你有一个账号,只在一台设备上登录的话,那么你可以一直用这个Cookie,但是如果你有多个账号的话,则需要一个手动登录账号的过程,非常麻烦,能否进行自动登陆呢?那么我们就来看一下魔卡的登陆是怎么做的吧。

继续抓取登陆的时候的协议,如下图所示,即给login.php?do=PassportLogin发送一个POST命令,POST的消息体内容为Devicetoken=&time=1371305578867&key=7a370df34c5fddd1f1c6ee9d6279d7da&Origin=TTGW&Udid=3D%3ABF%3A4A%3A6A%3AC0%3A7D&UserName=qqqaaagsr&Password=1000616820。其中Devicetoken为空,time为自Epoc以来的秒数,key这个东西,猜测是用来做验证的,比如验证cookieuid等是否是一致的。Udid是设备的唯一标示信息。UserName是我的账号名,password=1000616820,这个是经过加密之后的密码。

试着直接将UserName改成我另外一个好的用户名,发送POST信息,得到一个错误“passport\u7b7e\u540d\u9a8c\u8bc1\u5931\u8d25!”,意思是:“passport签名验证失败!”。猜测玄机在key上,其应该是用来验证UdidUserNamePassword的一致性的。

图:魔卡登陆协议包抓取

但是,这个key是怎么生成的呢?猜测是在登陆页面生成的,同样利用反编译,找到其登陆界面的网址,为:http://pp.fantasytoyou.com/pp/start.do?udid=3D:BF:4A:6A:C0:7D&locale=CHS&gameName=CARD-ANDROID-CHS&client=flash,这样就可以在Chrome中打开了:

图:在chrome中打开登陆界面

邮件查看源码,发现其主要的登陆逻辑都是写在“http://d.muhecdn.com/pp/V20130528/js/startHttp.js”这个js脚本里面的。时间关系,这个登陆的破解暂时还没有搞定,服务器端总是提示Passport验证错误,到此为止吧,后续有进展再通报。

【更新】:经过一段时间探索,自动登陆终于搞定了。魔卡的服务器,主要有两个,第一个是业务服务器,hosts9.mysticalcard.com,绝大多数的业务操作,日常事务都是和这个服务器交互完成的,还有一个是登陆验证服务器,hostpp.fantasytoyou.com,是专门进行用户登陆的验证的,所以,登陆共分为三步:

第一步,先给业务服务器发送一个GET请求,进入登陆界面。包主题内容为:GET /pp/start.do?udid=3D:BF:4A:6A:C0:7D&locale=CHS&gameName=CARD-ANDROID-CHS&client=flash,返回的即使登陆界面。

第二步,将用户名、密码、设备标识信息发送给验证服务器,获得密钥。包主体内容为POST  /pp/httpService.doPOST的内容为:{"serviceName":"login","callPara":{"userName":"qqqaaagsr","userPassword":"password1","gameName":"CARD-ANDROID-CHS","udid":"3D:BF:4A:6A:C0:7D","clientType":"flash","releaseChannel":"","locale":"chs"}}

返回的信息(密钥)如下:

{"returnCode":"0","returnMsg":"No error.","returnObjs":{"GS_NAME":"server9","GS_IP":"http://s9.mysticalcard.com/","friendCode":"null","GS_PORT":"80","timestamp":"1372814484134","GS_CHAT_PORT":"8000","source":"qqqaaagsr","userName":"qqqaaagsr","GS_DESC":"","U_ID":"1000616820","key":"b1ddb0dd6c2ab73ff1b5cb84b2c9d465","G_TYPE":"1","GS_CHAT_IP":"116.90.81.139"}}

第三部,利用上述返回的信息进行登陆,在业务服务器上的login.php进行登陆,包主体内容为:POST /login.php?do=PassportLogin&v=3539&phpp=ANDROID&phpl=ZH_CN&pvc=1.2.0&pvb=2013-04-16%2012%3A46%3A54POST的消息体为:Devicetoken=&time=1372814484134&key=b1ddb0dd6c2ab73ff1b5cb84b2c9d465&Origin=TTGW&Udid=3D%3ABF%3A4A%3A6A%3AC0%3A7D&UserName=qqqaaagsr&Password=1000616820,登陆成功。

 

其中,登陆能否成功,上述的三个蓝色字段是关键字段,其中timestamp是个时间戳,验证的时间戳必须和登陆的时间戳为同一时间,key是验证服务器生成的一个类似MD5的数值,要伪造不太可能,只能通过截取转发的方式来记录,而U_ID字段,则是在登陆的时候作为Password这个参数的值传递给server的。

最终的效果为:

这段代码既可完成对我三个账号的第8塔的1~5层进行刷塔。

    后记

    本文原创,因为涉及到摩卡幻想等商业游戏,如有侵权,请联系本人修改或者删除。

玩德州扑克有感

这次徽杭古道,和一帮同事朋友玩德州扑克,大概玩了两个小时吧,输了300块,如果时间长一点,相信应该能收支平衡的。这是我第三次正规的玩德州,前两次分别是+300,+3000,这次负300,还是有一些感触的。

玩德州还是要主玩运气,而不是技术。和非顶尖高手玩德州,还是不要bluff,靠运气为主吧。如果牌不算好,就急流勇退,不要担心自己可能错过某个对子而不进场。牌好的话,再进彩池。这样,即使每轮大小盲都需要花一点钱,只要自己能控制住,500块够玩一晚上了。

不要秒Call,关键时刻、筹码大的时候,做决定的时候不要太快。记得有一手,我转牌击中Set,对手则天成的顺子,手里是AJ,而翻牌则是KQT,一把被输了近500块。我当时被All in,call的时候是秒call的,甚至没有考虑到还有顺子的可能,如果有考虑的话,我大概能少损失至少300块钱。

 

保持自律。德州扑克是很考验技巧、经验的。给自己设定一个底限,比如今天晚上如果我输了一千块就不玩了,那么,当输了一千的时候,就一定退场。比如,如果大家都要熬夜来玩德州的话,可以找机会说想休息下,找人替代一下,这样既不会扫大家的兴,也能保持自律而不过度熬夜。

Have fun~~

什么是好的下属?

所谓好的下属,个人觉得要包括如下方面:

一、“结果”好的下属。

结果好包括两方面,首先是没有明显的负面反馈,第二是工作有亮点。

二、让老板“省心”的下属。

好的下属需要了解老板的风格,不让老板担心,也不耽搁老板太多的时间,在关键的问题上让老板决策,同时,提供足够的信息和帮助以减轻老板决策的压力。

三、能培养自己下属的下属。

一个好的下属,团队里面来了牛人,不会让这个牛人埋没,担心自己被抢了功劳,相反,他会给牛人更多展现的机会,更多培训的机会,更重要的项目,以让这个牛人脱颖而出。

四、“积极主动”+“正能量”的下属。

这两个性格特质,也是很多公司都很看中的。积极主动的人就让人很“省心”,你交代的工作,他完成的很好,你没有交代的工作,他会主动发现并完成。正能量的人,只要他在团队中,整个团队的斗志就会被带动起来。这两种性格特质的人越多,你的团队就越好带。对于这两种性格特质反面的人,则需要冷处理甚至替换了。