也谈.Net中间语言——破解Delphi2CS行数和时间限制
其实我一直在研究将Delphi版的传奇2源代码使用C#实现,不过由于我并没有学习过Delphi。就只能说先试着用一些工具转换代码。
后来我在网上找到了一款软件:Delphi2CS。这款软件比较强大,虽然不支持条件编译,但竟然能对窗体控件达到非常高的转换效率!且直接生成vs.net的项目,令我十分高兴,这意味着我们只需要修复很少的部分。
不过真正尝试转换的时候发现有一个限制:源文件不得超过500行。
如下,图片为准换完成后的报告,代码为转换完成后的cs文件:
1 using System; 2 using System.IO; 3 using DCPconst; 4 using Base64; 5 using Sha1; 6 7 namespace DCPcrypt 8 { 9 // Delphi2CS trial converts the .PAS file that is less than 500 lines. 10 // DCPcrypt.pas is 1029 lines. 11 // Please purchse the final version to avoid the limitation. 12 }
这令我十分困扰。
后来在网上发现博客园的liufei同学解决了这个问题,他说明了方法并提供了可用程序。
使用他的程序确实可以达到效果,但是另一个问题却出现了:
大概意思是说过期了。我是从官网上下载了文件进行安装,然后将liufei同学的破解文件放到程序目录下执行的(如果是直接使用liu同学的程序是可行的)。不过出现了上图的情况。
虽然不知道原因,不过貌似现在只能自己来破解了。
先打开IL,载入程序
然后依次点击“文件”=>“转储”,使用默认设置就行了
然后打开il文件开始找,不过没什么挑战性,一下就找到了
上述代码可用.NET Reflector还原
1 public void F() 2 { 3 if (!this.TB) 4 { 5 try 6 { 7 DC.Q = this; 8 StringBuilder builder = new StringBuilder(); 9 bool flag = false; 10 StringBuilder builder2 = new StringBuilder(); 11 bool flag2 = false; 12 StringBuilder builder3 = new StringBuilder(); 13 StringBuilder b = null; 14 if (this.R != null) 15 { 16 AB ab = null; 17 foreach (string str in this.R) 18 { 19 ab = this.NB.BB(str); 20 if (ab != null) 21 { 22 this.U(this.LD(ab.O()), null); 23 } 24 } 25 } 26 if (this.OB != null) 27 { 28 foreach (string str2 in this.OB) 29 { 30 if (!str2.Equals(this.O())) 31 { 32 builder.Append("using ").Append(str2).Append(";").Append("\r\n"); 33 } 34 } 35 } 36 string str3 = SB.U(); 37 if ((str3 != null) && (str3 != "")) 38 { 39 builder.Append(str3); 40 } 41 if (this.EB != null) 42 { 43 builder.Append(this.EB.Replace("U_K_N_O_W_N ", " ")); 44 } 45 if (QB.B != 100) 46 { 47 this.ED("Delphi2CS has expired."); 48 builder3.Append("// Delphi2CS has expired, please purchase the final version. \r\n"); 49 flag = true; 50 } 51 else if (this.GB.A > 0x1f4) 52 { 53 this.ED("Delphi2CS trial converts the .pas file that is less than 500 lines."); 54 builder3.Append(" // Delphi2CS trial converts the .PAS file that is less than 500 lines. \r\n"); 55 string fileName = Path.GetFileName(this.O); 56 builder3.Append(string.Concat(new object[] { " // ", fileName, " is ", this.GB.A, " lines. \r\n" })); 57 builder3.Append(" // Please purchse the final version to avoid the limitation.\r\n"); 58 flag = true; 59 } 60 if (!flag) 61 { 62 if ((this.MB != null) && (this.MB.Count > 0)) 63 { 64 foreach (YB yb in this.MB.ToArray()) 65 { 66 if (yb.L() == null) 67 { 68 continue; 69 } 70 if (SB.J()) 71 { 72 if (b == null) 73 { 74 b = new StringBuilder(); 75 } 76 b.Append("namespace " + this.O() + "\r\n{").Append("\r\n"); 77 b.Append(" partial class ").Append(yb.B.A).Append("\r\n"); 78 b.Append(" {\r\n"); 79 b.Append(yb.L().U()); 80 b.Append(" }\r\n"); 81 b.Append("}\r\n"); 82 } 83 builder3.Append(yb.HB()); 84 this.MB.Remove(yb); 85 } 86 } 87 if ((this.MB != null) && (this.MB.Count > 0)) 88 { 89 foreach (YB yb2 in this.MB) 90 { 91 builder3.Append(yb2.HB()); 92 } 93 } 94 if (this.U.K.W != null) 95 { 96 flag2 = true; 97 StringBuilder builder5 = new StringBuilder(); 98 builder5.Append(this.U.K.W.FB()); 99 if (builder5.Length > 0) 100 { 101 builder2.Append("namespace ").Append(this.O()).Append(".Units\r\n"); 102 builder2.Append("{\r\n"); 103 builder2.Append(builder5.ToString()); 104 builder2.Append("}\r\n"); 105 builder2.Append("\r\n"); 106 string str5 = this.HB(); 107 if (str5 != null) 108 { 109 str5 = str5.Replace("U_K_N_O_W_N ", ""); 110 builder2.Append(str5); 111 } 112 } 113 } 114 } 115 string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(this.O); 116 string a = fileNameWithoutExtension + ".Designer.cs"; 117 if (this.RB) 118 { 119 string str8 = this.NB.LB(this.O); 120 fileNameWithoutExtension = fileNameWithoutExtension + str8; 121 } 122 else 123 { 124 fileNameWithoutExtension = fileNameWithoutExtension + ".cs"; 125 } 126 string str9 = this.O.ToLower().Replace(this.NB.K, this.NB.J); 127 if (!str9.StartsWith(this.NB.J)) 128 { 129 str9 = this.NB.J(str9, this.NB.J); 130 } 131 if (!str9.StartsWith(this.NB.J)) 132 { 133 str9 = this.NB.K(str9, this.NB.J); 134 } 135 str9 = Path.Combine(Path.GetDirectoryName(str9), fileNameWithoutExtension); 136 FileInfo info = new FileInfo(str9); 137 if (info.Exists) 138 { 139 info.Delete(); 140 } 141 else 142 { 143 info.Directory.Create(); 144 } 145 this.PC(); 146 FileStream stream = info.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); 147 StreamWriter writer = new StreamWriter(stream, Encoding.Default); 148 writer.Write(builder.ToString()); 149 if (builder3.Length > 0) 150 { 151 writer.WriteLine("namespace " + this.O() + "\r\n{"); 152 writer.Write(builder3.ToString()); 153 writer.WriteLine("}\r\n"); 154 } 155 if (flag2) 156 { 157 writer.Write(builder2.ToString()); 158 } 159 writer.Close(); 160 stream.Close(); 161 writer = null; 162 stream = null; 163 if ((b != null) && (b.Length > 0)) 164 { 165 this.VC(a, b); 166 } 167 builder2 = null; 168 builder3 = null; 169 builder = null; 170 this.P = str9.Replace(this.NB.J, ""); 171 if (this.P.StartsWith(Path.DirectorySeparatorChar.ToString())) 172 { 173 this.P = this.P.Substring(1); 174 } 175 this.NB.T.Add(this.P); 176 if (this.KB != null) 177 { 178 string key = this.KB.Replace(this.NB.J, ""); 179 this.JC("Creating " + Path.GetFileName(this.KB)); 180 try 181 { 182 this.LB.Close(); 183 this.NB.U.Add(key, this.P); 184 } 185 catch 186 { 187 } 188 } 189 } 190 catch (Exception exception) 191 { 192 this.NB.W(exception.ToString()); 193 this.ED(exception.Message); 194 } 195 } 196 }
上面的语句其实是一个条件判断,判断读取到的行数是否小于500,在第51行处。我们可以改到500000。
行数限制似乎破解了,那么时间限制呢?文章第一副图片所示大概是说我们使用的是Delphi2CS评估版,而现在它过期了。
破解过期时间很简单,我们在il文件中找到过期判断语句
1 .method private hidebysig instance void 2 H(object A, 3 class [mscorlib]System.EventArgs B) cil managed 4 { 5 // 代码大小 435 (0x1b3) 6 .maxstack 4 7 .locals init (string V_0, 8 string V_1, 9 class [mscorlib]System.Threading.ThreadStart V_2, 10 class [mscorlib]System.Threading.Thread V_3) 11 IL_0000: ldsfld int32 QB::B 12 IL_0005: ldc.i4.s 100 13 IL_0007: beq.s IL_001d 14 15 IL_0009: ldstr "Delphi2CS Evaluation Version has expired, please p" 16 + "urchase the final version." 17 IL_000e: ldstr "Error" 18 IL_0013: ldc.i4.0 19 IL_0014: ldc.i4.s 16
搜索“Delphi2CS Evaluation”即可找到,上面代码第11行即判断了QB.B和100是否相等,我们通过.NET Reflector来查看可得下面的代码:
1 private void H(object A, EventArgs B) 2 { 3 if (QB.B != 100) 4 { 5 MessageBox.Show("Delphi2CS Evaluation Version has expired, please purchase the final version.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Hand); 6 } 7 else 8 { 9 this.UB = this.O.Text; 10 if ((this.UB.Trim().Length == 0) || (this.UB == null)) 11 { 12 MessageBox.Show("You must input an existed Delphi Project filename", "Error", MessageBoxButtons.OK, MessageBoxIcon.Hand); 13 } 14 else if (!File.Exists(this.UB)) 15 { 16 MessageBox.Show("You must input an existed Delphi Project filename:\n '" + this.UB + "'", "Error", MessageBoxButtons.OK, MessageBoxIcon.Hand); 17 } 18 else 19 { 20 this.VB = this.J.Text; 21 if (this.VB.Trim().Length == 0) 22 { 23 MessageBox.Show("Please specify an output path for the generated C# files", "Error", MessageBoxButtons.OK, MessageBoxIcon.Hand); 24 } 25 else 26 { 27 if (!Directory.Exists(this.VB)) 28 { 29 Directory.CreateDirectory(this.VB); 30 } 31 this.VB = Path.GetFullPath(this.VB); 32 if (!this.VB.EndsWith(new string(Path.DirectorySeparatorChar, 1))) 33 { 34 this.VB = this.VB + Path.DirectorySeparatorChar; 35 } 36 string str = Path.GetDirectoryName(this.UB).ToLower(); 37 string str2 = Path.GetDirectoryName(this.VB).ToLower(); 38 if (str.StartsWith(str2)) 39 { 40 MessageBox.Show("Please make sure the Output path is different from the project path.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Hand); 41 } 42 else 43 { 44 this.WB = new LD(this); 45 ThreadStart start = new ThreadStart(this.I); 46 Thread thread = new Thread(start); 47 thread.Start(); 48 this.WB.ShowDialog(this); 49 try 50 { 51 if (thread != null) 52 { 53 thread.Abort(); 54 } 55 else 56 { 57 thread = null; 58 } 59 } 60 catch 61 { 62 } 63 this.WB = null; 64 base.Activate(); 65 } 66 } 67 } 68 } 69 }
其实能够猜到:这个方法是点击浏览按钮选择了文件后的事件处理方法。第一步就判断了QB.B是否等于100,如果不等的话就会终端执行。我们只需把if的条件设为永远不等即可,如下面这样
1 .method private hidebysig instance void 2 H(object A, 3 class [mscorlib]System.EventArgs B) cil managed 4 { 5 // 代码大小 435 (0x1b3) 6 .maxstack 4 7 .locals init (string V_0, 8 string V_1, 9 class [mscorlib]System.Threading.ThreadStart V_2, 10 class [mscorlib]System.Threading.Thread V_3) 11 IL_0000: ldc.i4.s 100 12 IL_0005: ldc.i4.s 100 13 IL_0007: beq.s IL_001d 14 15 IL_0009: ldstr "Delphi2CS Evaluation Version has expired, please p" 16 + "urchase the final version." 17 IL_000e: ldstr "Error" 18 IL_0013: ldc.i4.0 19 IL_0014: ldc.i4.s 16
在IL_0000里我把二元判断表达式左边的值从QB.B直接换成了100,这样的话就不存在过期了,因为if(100 != 100)永远为false!
其实现在要做的就很简单了,把上面的0x1f4(即500)改大和把这个使用了QB.B判断并终止程序执行的地方修正(其实重要的地方有两个,上述的KD.H方法中和BB.F中,后者的if判断正在500行判断前)。我更改过后后进入vs.net的命令行生成可执行文件。
按照liu同学的说法,delphi2cs程序需要.net framework3.5,所以vs2005的cmd无法编译,不过我没有尝试。按照上图的方法就能生成exe文件了,使用生成的文件替换掉安装目录下文件即可。
我把破解了的文件上传上来,大家可以看看。
(最后编辑时间2013-08-17 01:05:38)