本文以破解WP的XBL(Xbox LIVE)游戲?yàn)槔v解如何使用Cecil這把尚方寶劍,讓ILDasm修改IL的方法徹底成為歷史。
最近在Windows Phone Store的Nokia collection里面發(fā)現(xiàn)了《Parking Mania(瘋狂停車場)》這款游戲。試玩了一下覺得非常有趣,但只能試玩前面少數(shù)幾關(guān)。手癢癢的就開始了“盜版”部署。由于Parking Manin是XBL游戲,其調(diào)用GamerServicesComponent組件。但直接部署的XAP的應(yīng)用無法使用此組件,從而會在使用了組件的地方會直接終止應(yīng)用(DFT成就版ROM除外)。所以我們要做的就是在整個游戲中找到所有調(diào)用了XBL服務(wù)的地方并將其去除或跳過。但這樣有個明顯的缺點(diǎn):XBL最重要的成就和排名功能被閹割掉了。DFT成就版就是為了解決這一問題而產(chǎn)生的。
為了破解,當(dāng)然是要先找到所有調(diào)用了XBL服務(wù)的地方。直接在IL中搜索GamerServicesComponent,只有ParkingMania.ParkingManiaGame中槍。然后在Initialize方法中前兩句指令給刪掉,清空HandleGameUpdateRequired、Update方法。即下圖中紅框中的代碼要干掉。
找到其對應(yīng)的IL代碼:
對應(yīng)用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); }); } } } |
再進(jìn)一步閱讀分析源碼可以發(fā)現(xiàn)有ParkingMania.Services.Data.Achievments.XBLAService這個類。這里面要把Event、LoadMoneyLeaderboard、LoadStarsLeaderboard、GetAchievmentsForMenu方法統(tǒng)統(tǒng)干掉。具體方法也類似,就不重復(fù)貼代碼的。
需要注意的是:離開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 }); } } } |
續(xù),
快速代理等寫IL的需求可以用Emit實(shí)現(xiàn),并且是官方支持的方法;簡單修改或破解IL的需求可以用ILDasm實(shí)現(xiàn)。那么對我們來說,Cecil到底有什么用?
1、對代碼程序集進(jìn)行反編譯并規(guī)范化的修改其流程,特別是對于復(fù)雜的程序集的修改非常有優(yōu)勢
2、按照自己的需求額外的優(yōu)化代碼
3、插入特殊的操作指令,例如編譯時后自動完成WPF中觀察者模式中的通知接口
這里也順便說一下DFT成就版吧
首先要說一下XBL最大的優(yōu)勢是提供了游戲中“成就”和“排名”等服務(wù)的支持,這些服務(wù)非常有助于提高用戶粘性。@馬寧 為此還弄出了OpenXLive 這個開放的SNS平臺?梢钥纯瘩R寧的一篇博客《OpenXLive——開啟Windows Phone 7游戲社交平臺新時代》中關(guān)于Leaderboard、Achievements、Social Network的介紹。
XNA自身在Microsoft.Xna.Framework.Game.dll集成了類似的服務(wù),并提供了開發(fā)接口。WP7中許多XBL游戲都使用此接口?赡苁俏④涀陨淼谋Wo(hù)策略,禁止從市場安裝的應(yīng)用訪問XBL服務(wù)接口。DFT成就版就是在ROM中修改了此接口,并xxxx了。
有了Cecil這個玩意,我們完全可以設(shè)計出一種機(jī)制自動識別出使用了XBL服務(wù)接口的代碼并使其使用另外編寫好的第三方接口。在第三方接口中可以在本地或其他網(wǎng)絡(luò)中提供第三方服務(wù)。有了這樣的第三方服務(wù)才能算作是“完美”的破解。使用Cecil替換接口的調(diào)用并無什么復(fù)雜的技術(shù)(上一篇文章中就對調(diào)用的方法進(jìn)行了替換),難就難在要設(shè)計出一種完善的處理機(jī)制使其可以自動的處理任何一種情況。
好了,這只是一種想法,而且是一種邪惡的想法。本著支持正版的精神,大家還是力所能及的購買一些正版吧。
最后的最后,本文重點(diǎn)講述的已破解完的xap文件放置在群1749907的群共享中,從今天算起只有30天的存儲時間。