本文以破解WP的XBL(Xbox LIVE)游戏为例讲解如何使用Cecil这把尚方宝剑,让ILDasm修改IL的方法彻底成为历史。
最近在Windows Phone Store的Nokia collection里面发现了《Parking Mania(疯狂停车场)》这款游戏。试玩了一下觉得非常有趣,但只能试玩前面少数几关。手痒痒的就开始了“盗版”部署。由于Parking Manin是XBL游戏,其调用GamerServicesComponent组件。但直接部署的XAP的应用无法使用此组件,从而会在使用了组件的地方会直接终止应用(DFT成就版ROM除外)。所以我们要做的就是在整个游戏中找到所有调用了XBL服务的地方并将其去除或跳过。但这样有个明显的缺点:XBL最重要的成就和排名功能被阉割掉了。DFT成就版就是为了解决这一问题而产生的。
为了破解,当然是要先找到所有调用了XBL服务的地方。直接在IL中搜索GamerServicesComponent,只有ParkingMania.ParkingManiaGame中枪。然后在Initialize方法中前两句指令给删掉,清空HandleGameUpdateRequired、Update方法。即下图中红框中的代码要干掉。
找到其对应的IL代码:
对应用C#代码操作的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | if (type.FullName == "ParkingMania.ParkingManiaGame") { var field = type.Fields.FirstOrDefault(s => s.Name == "GamerServiceInstance"); type.Fields.Remove(field); foreach (var method in type.Methods) { if (method.Body == null) continue; var worker = method.Body.GetILProcessor(); if (method.Name == "Initialize") { var list = method.Body.Instructions.Skip(1).Take(9).ToList(); list.ForEach(i => { worker.Remove(i); }); } if (method.Name == "HandleGameUpdateRequired") { var list = method.Body.Instructions.Skip(3).Take(4).ToList(); list.ForEach(i => { worker.Remove(i); }); } if (method.Name == "Update") { var list = method.Body.Instructions.Skip(8).Take(8).ToList(); list.ForEach(i => { worker.Remove(i); }); } } } |
再进一步阅读分析源码可以发现有ParkingMania.Services.Data.Achievments.XBLAService这个类。这里面要把Event、LoadMoneyLeaderboard、LoadStarsLeaderboard、GetAchievmentsForMenu方法统统干掉。具体方法也类似,就不重复贴代码的。
需要注意的是:离开try…catch…模块的时候需要leave.s指令、离开一个方法的时候需要ret指令。删除的时候记得保留这些指令。
最后贴出完整的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | using Mono.Cecil; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CecilParkingMania { class Program { static void Main(string[] args) { var bas = @"D:\Til\Parking_0\"; var src = bas + @"ParkingMania.org.dll"; var dst = bas + @"0\ParkingMania.dll"; var resolver = new DefaultAssemblyResolver(); resolver.AddSearchDirectory(bas + @"0"); var parameters = new ReaderParameters { AssemblyResolver = resolver, ReadSymbols = false, }; var assembly = AssemblyDefinition.ReadAssembly(src, parameters); foreach (var module in assembly.Modules) { foreach (var type in module.Types) { if (type.FullName == "ParkingMania.ParkingManiaGame") { var field = type.Fields.FirstOrDefault(s => s.Name == "GamerServiceInstance"); type.Fields.Remove(field); foreach (var method in type.Methods) { if (method.Body == null) continue; var worker = method.Body.GetILProcessor(); if (method.Name == "Initialize") { var list = method.Body.Instructions.Skip(1).Take(9).ToList(); list.ForEach(i => { worker.Remove(i); }); } if (method.Name == "HandleGameUpdateRequired") { var list = method.Body.Instructions.Skip(3).Take(4).ToList(); list.ForEach(i => { worker.Remove(i); }); } if (method.Name == "Update") { var list = method.Body.Instructions.Skip(8).Take(8).ToList(); list.ForEach(i => { worker.Remove(i); }); } } } if (type.FullName == "ParkingMania.Services.Data.Achievments.XBLAService") { foreach (var method in type.Methods) { if (method.Body == null) continue; var worker = method.Body.GetILProcessor(); if (method.Name == "Event") { var list = method.Body.Instructions.Take(method.Body.Instructions.Count - 1).ToList(); list.ForEach(i => { worker.Remove(i); }); } if (method.Name == "LoadMoneyLeaderboard") { var list = method.Body.Instructions.Take(method.Body.Instructions.Count - 1).ToList(); list.ForEach(i => { worker.Remove(i); }); } if (method.Name == "LoadStarsLeaderboard") { var list = method.Body.Instructions.Take(method.Body.Instructions.Count - 1).ToList(); list.ForEach(i => { worker.Remove(i); }); } if (method.Name == "GetAchievmentsForMenu") { var list = method.Body.Instructions.Take(method.Body.Instructions.Count - 1).ToList(); list.ForEach(i => { worker.Remove(i); }); } } } } } assembly.Write(dst, new WriterParameters { WriteSymbols = false }); } } } |
续,
快速代理等写IL的需求可以用Emit实现,并且是官方支持的方法;简单修改或破解IL的需求可以用ILDasm实现。那么对我们来说,Cecil到底有什么用?
1、对代码程序集进行反编译并规范化的修改其流程,特别是对于复杂的程序集的修改非常有优势
2、按照自己的需求额外的优化代码
3、插入特殊的操作指令,例如编译时后自动完成WPF中观察者模式中的通知接口
这里也顺便说一下DFT成就版吧
首先要说一下XBL最大的优势是提供了游戏中“成就”和“排名”等服务的支持,这些服务非常有助于提高用户粘性。@马宁 为此还弄出了OpenXLive 这个开放的SNS平台。可以看看马宁的一篇博客《OpenXLive——开启Windows Phone 7游戏社交平台新时代》中关于Leaderboard、Achievements、Social Network的介绍。
XNA自身在Microsoft.Xna.Framework.Game.dll集成了类似的服务,并提供了开发接口。WP7中许多XBL游戏都使用此接口。可能是微软自身的保护策略,禁止从市场安装的应用访问XBL服务接口。DFT成就版就是在ROM中修改了此接口,并xxxx了。
有了Cecil这个玩意,我们完全可以设计出一种机制自动识别出使用了XBL服务接口的代码并使其使用另外编写好的第三方接口。在第三方接口中可以在本地或其他网络中提供第三方服务。有了这样的第三方服务才能算作是“完美”的破解。使用Cecil替换接口的调用并无什么复杂的技术(上一篇文章中就对调用的方法进行了替换),难就难在要设计出一种完善的处理机制使其可以自动的处理任何一种情况。
好了,这只是一种想法,而且是一种邪恶的想法。本着支持正版的精神,大家还是力所能及的购买一些正版吧。
最后的最后,本文重点讲述的已破解完的xap文件放置在群1749907的群共享中,从今天算起只有30天的存储时间。