不得不说,越狱后的iOS可以安装“任我行”,root之后的android也有类似的“Custom Location”,而WP7貌似还没有大神加入类似功能。如果你有需求,那你一定要好好看看此文。本文的方法能让你的gps坐标全球飘逸。
底子
WP7/8应用可以用Silverlight技术开发,不懂什么是Silverlight的先默默地学习一分钟。而Silverlight本身就是个精简的CLR + Framework,技术完全类似于.Net。Silverlight的.xap文件是zip格式的封装,用7z这样的工具就能解开。里面的DLL文件完全是扩展的PE格式封装。熟悉.Net的人都知道诸如Reflector、ILSpy此类的必备工具。在ILSpy程序的文件夹里面有个叫做Mono.Cecil.dll的文件,这玩意就是Mono项目组搞出来的Cecil。简单说这是个可以生成、注入和查看符合ECMA CIL标准的程序集的小项目。更重要的是这个小玩意可以让你用编程的方式实现任何你想要的功能。这时候你应该知道,ILSpy就是基于Cecil搞出来的,而这篇文章的重点也在于Cecil。虽然这个玩意已经有两年时间没有更新了,文档也不怎么适用了,但其依然魅力四射。
开始
从官方找到最新版的Cecil并下载(最新版 0.9.1/2010年4月发布)。不熟悉的自己先折腾一分钟看看都有哪些定义,先热热身。
思路
“技术不是问题”。是的,技术从来都不是问题,问题是你能不能想到解决技术问题的方案。WP7 SDK中获取地理位置坐标要用到GeoCoordinateWatcher类,通常是在PositionChanged事件或直接通过Position属性获取到坐标。而坐标都是用GeoCoordinate表示。好了,我们要做的是在获取到GeoCoordinate的代码里面注入一小段代码修改它。
假设
注意,这个假设很重要,因为源程序中使用坐标的方式直接决定应该如何注入。现假设需要修改的源程序如下
static void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e) { Debug.WriteLine(e.Position.Location); }
准备
为了让注入过程尽可能的简单,我们可以提前在外部把要注入的代码写好并编译成程序集。写好如下代码,这段代码可以根据你的需要随意修改:
public static class Geo { public static GeoCoordinate Hijeck(GeoCoordinate position) { var rnd = new Random(); return new GeoCoordinate( rnd.NextDouble() * 180 - 90, rnd.NextDouble() * 360 - 180 ); } }
注入
这一步是把我们准备好的代码注入到源程序中。注入之前,先看看源程序的IL,看IL的目的是为了找到一个合适的注入点:
.method private hidebysig static void watcher_PositionChanged ( object sender, class [System.Device]System.Device.Location.GeoPositionChangedEventArgs`1<class [System.Device]System.Device.Location.GeoCoordinate> e ) cil managed { // Method begins at RVA 0x209b // Code size 19 (0x13) .maxstack 8 IL_0000: nop IL_0001: ldarg.1 IL_0002: callvirt instance class [System.Device]System.Device.Location.GeoPosition`1<!0> class [System.Device]System.Device.Location.GeoPositionChangedEventArgs`1<class [System.Device]System.Device.Location.GeoCoordinate>::get_Position() IL_0007: callvirt instance !0 class [System.Device]System.Device.Location.GeoPosition`1<class [System.Device]System.Device.Location.GeoCoordinate>::get_Location() IL_000c: call void [System]System.Diagnostics.Debug::WriteLine(object) IL_0011: nop IL_0012: ret }
我们要在get_Location之后注入我们的代码,相当于直接修改了传递给WriteLine的Location值。实现的代码如下:
static void Main(string[] args) { //需要修改的程序集 var ad = AssemblyDefinition.ReadAssembly(@"Wp7App.dll"); //需要注入的程序集 var hjad = AssemblyDefinition.ReadAssembly(@"GeoTest.dll"); //增加对此程序集的引用 var anr = new AssemblyNameReference(hjad.Name.Name, hjad.Name.Version) { PublicKey = hjad.Name.PublicKey, PublicKeyToken = hjad.Name.PublicKeyToken }; ad.MainModule.AssemblyReferences.Add(anr); //找到需要注入的类 var hjType = hjad.MainModule.Types.First(s => s.Name == "Geo"); //找到需要注入的方法并导入到需要修改的程序集 var hjMethod = ad.MainModule.Import(hjType.Methods.First(s => s.Name == "Hijeck")); //枚举所有的模块 foreach (var module in ad.Modules) { //枚举所有的类型 foreach (var type in module.Types) { //枚举所有的方法 foreach (var method in type.Methods) { //如果是空方法则跳过 if (!method.HasBody) continue; //在当前方法内查找是否有调用get_Location方法的地方,此处就是注入点 var ins0 = method.Body.Instructions.FirstOrDefault(s => s.OpCode == OpCodes.Callvirt && s.Operand is MethodReference && ((MethodReference)s.Operand).Name == "get_Location"); if (ins0 != null) { //获取到方法的修改器 var worker = method.Body.GetILProcessor(); //创建一个Call指令,调用需要注入的方法 var ins1 = worker.Create(OpCodes.Call, hjMethod); //将此指令注入到注入点之后 worker.InsertAfter(ins0, ins1); } } } } //保存完成注入的程序集 ad.Write(@"Wp7App_Hijeck.dll"); }
这时候再用ILSpy看看注入之后的代码,变成了下面这样
private static void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e) { Debug.WriteLine(Geo.Hijeck(e.Position.Location)); }
最后记得修改AppManifest.xaml文件,把GeoTest.dll加入到AssemblyPart。当初做的时候就是忘了这一步,一直导致无法正常的导航到注入的程序集。
PS
1、由于本人已经用上了VS2012,但可用的wp sdk还未发布,所以本文所有代码都是在fx4.0下模拟的。(fx4.0也有相应的System.Device.dll,一样可用在电脑上进行定位)
2、不同情况需要不同分析,本文代码并不通用。实际需要根据不同的情况自己分析IL,并注入不同的IL,这对操作者的要求较高。
3、对于IL不太熟悉的人,可用另外写个相似的代码,修改后比对编译器给出的不同编译结果,然后依葫芦画瓢。
4、实际的修改中,可能需要同时启动多个VS,多个项目,额可以在编译事件中写入一些指令,例如自动把编译好的dll复制到另一个项目中。
5、最后的打包成xap文件和部署都可以用批处理自动化完成,节约大量人力物力。
6、Cecil还有许多更为强大的玩法,本文抛砖引玉,剩下的看你自己了。
7、最后附上一个可以随意“跳”的微信,地址见二维码。