├── .gitignore ├── ConfuserExCustomModuleConstUnpacker ├── ConfuserExCustomModuleConstUnpacker.sln └── ConfuserExCustomModuleConstUnpacker │ ├── App.config │ ├── ConfuserExCustomModuleConstUnpacker.csproj │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── TargetAssembly.cs ├── README.md └── icscdecomp_cfxc_deobf.patch /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # Benchmark Results 46 | BenchmarkDotNet.Artifacts/ 47 | 48 | # .NET Core 49 | project.lock.json 50 | project.fragment.lock.json 51 | artifacts/ 52 | **/Properties/launchSettings.json 53 | 54 | *_i.c 55 | *_p.c 56 | *_i.h 57 | *.ilk 58 | *.meta 59 | *.obj 60 | *.pch 61 | *.pdb 62 | *.pgc 63 | *.pgd 64 | *.rsp 65 | *.sbr 66 | *.tlb 67 | *.tli 68 | *.tlh 69 | *.tmp 70 | *.tmp_proj 71 | *.log 72 | *.vspscc 73 | *.vssscc 74 | .builds 75 | *.pidb 76 | *.svclog 77 | *.scc 78 | 79 | # Chutzpah Test files 80 | _Chutzpah* 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opendb 87 | *.opensdf 88 | *.sdf 89 | *.cachefile 90 | *.VC.db 91 | *.VC.VC.opendb 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | *.sap 98 | 99 | # TFS 2012 Local Workspace 100 | $tf/ 101 | 102 | # Guidance Automation Toolkit 103 | *.gpState 104 | 105 | # ReSharper is a .NET coding add-in 106 | _ReSharper*/ 107 | *.[Rr]e[Ss]harper 108 | *.DotSettings.user 109 | 110 | # JustCode is a .NET coding add-in 111 | .JustCode 112 | 113 | # TeamCity is a build add-in 114 | _TeamCity* 115 | 116 | # DotCover is a Code Coverage Tool 117 | *.dotCover 118 | 119 | # Visual Studio code coverage results 120 | *.coverage 121 | *.coveragexml 122 | 123 | # NCrunch 124 | _NCrunch_* 125 | .*crunch*.local.xml 126 | nCrunchTemp_* 127 | 128 | # MightyMoose 129 | *.mm.* 130 | AutoTest.Net/ 131 | 132 | # Web workbench (sass) 133 | .sass-cache/ 134 | 135 | # Installshield output folder 136 | [Ee]xpress/ 137 | 138 | # DocProject is a documentation generator add-in 139 | DocProject/buildhelp/ 140 | DocProject/Help/*.HxT 141 | DocProject/Help/*.HxC 142 | DocProject/Help/*.hhc 143 | DocProject/Help/*.hhk 144 | DocProject/Help/*.hhp 145 | DocProject/Help/Html2 146 | DocProject/Help/html 147 | 148 | # Click-Once directory 149 | publish/ 150 | 151 | # Publish Web Output 152 | *.[Pp]ublish.xml 153 | *.azurePubxml 154 | # Note: Comment the next line if you want to checkin your web deploy settings, 155 | # but database connection strings (with potential passwords) will be unencrypted 156 | *.pubxml 157 | *.publishproj 158 | 159 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 160 | # checkin your Azure Web App publish settings, but sensitive information contained 161 | # in these scripts will be unencrypted 162 | PublishScripts/ 163 | 164 | # NuGet Packages 165 | *.nupkg 166 | # The packages folder can be ignored because of Package Restore 167 | **/packages/* 168 | # except build/, which is used as an MSBuild target. 169 | !**/packages/build/ 170 | # Uncomment if necessary however generally it will be regenerated when needed 171 | #!**/packages/repositories.config 172 | # NuGet v3's project.json files produces more ignorable files 173 | *.nuget.props 174 | *.nuget.targets 175 | 176 | # Microsoft Azure Build Output 177 | csx/ 178 | *.build.csdef 179 | 180 | # Microsoft Azure Emulator 181 | ecf/ 182 | rcf/ 183 | 184 | # Windows Store app package directories and files 185 | AppPackages/ 186 | BundleArtifacts/ 187 | Package.StoreAssociation.xml 188 | _pkginfo.txt 189 | *.appx 190 | 191 | # Visual Studio cache files 192 | # files ending in .cache can be ignored 193 | *.[Cc]ache 194 | # but keep track of directories ending in .cache 195 | !*.[Cc]ache/ 196 | 197 | # Others 198 | ClientBin/ 199 | ~$* 200 | *~ 201 | *.dbmdl 202 | *.dbproj.schemaview 203 | *.jfm 204 | *.pfx 205 | *.publishsettings 206 | orleans.codegen.cs 207 | 208 | # Since there are multiple workflows, uncomment next line to ignore bower_components 209 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 210 | #bower_components/ 211 | 212 | # RIA/Silverlight projects 213 | Generated_Code/ 214 | 215 | # Backup & report files from converting an old project file 216 | # to a newer Visual Studio version. Backup files are not needed, 217 | # because we have git ;-) 218 | _UpgradeReport_Files/ 219 | Backup*/ 220 | UpgradeLog*.XML 221 | UpgradeLog*.htm 222 | 223 | # SQL Server files 224 | *.mdf 225 | *.ldf 226 | *.ndf 227 | 228 | # Business Intelligence projects 229 | *.rdl.data 230 | *.bim.layout 231 | *.bim_*.settings 232 | 233 | # Microsoft Fakes 234 | FakesAssemblies/ 235 | 236 | # GhostDoc plugin setting file 237 | *.GhostDoc.xml 238 | 239 | # Node.js Tools for Visual Studio 240 | .ntvs_analysis.dat 241 | node_modules/ 242 | 243 | # Typescript v1 declaration files 244 | typings/ 245 | 246 | # Visual Studio 6 build log 247 | *.plg 248 | 249 | # Visual Studio 6 workspace options file 250 | *.opt 251 | 252 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 253 | *.vbw 254 | 255 | # Visual Studio LightSwitch build output 256 | **/*.HTMLClient/GeneratedArtifacts 257 | **/*.DesktopClient/GeneratedArtifacts 258 | **/*.DesktopClient/ModelManifest.xml 259 | **/*.Server/GeneratedArtifacts 260 | **/*.Server/ModelManifest.xml 261 | _Pvt_Extensions 262 | 263 | # Paket dependency manager 264 | .paket/paket.exe 265 | paket-files/ 266 | 267 | # FAKE - F# Make 268 | .fake/ 269 | 270 | # JetBrains Rider 271 | .idea/ 272 | *.sln.iml 273 | 274 | # CodeRush 275 | .cr/ 276 | 277 | # Python Tools for Visual Studio (PTVS) 278 | __pycache__/ 279 | *.pyc 280 | 281 | # Cake - Uncomment if you are using it 282 | # tools/** 283 | # !tools/packages.config 284 | 285 | # Tabs Studio 286 | *.tss 287 | 288 | # Telerik's JustMock configuration file 289 | *.jmconfig 290 | 291 | # BizTalk build output 292 | *.btp.cs 293 | *.btm.cs 294 | *.odx.cs 295 | *.xsd.cs 296 | -------------------------------------------------------------------------------- /ConfuserExCustomModuleConstUnpacker/ConfuserExCustomModuleConstUnpacker.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfuserExCustomModuleConstUnpacker", "ConfuserExCustomModuleConstUnpacker\ConfuserExCustomModuleConstUnpacker.csproj", "{6006C5CB-D461-45A5-91B6-C0E5D9349128}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6006C5CB-D461-45A5-91B6-C0E5D9349128}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6006C5CB-D461-45A5-91B6-C0E5D9349128}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6006C5CB-D461-45A5-91B6-C0E5D9349128}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6006C5CB-D461-45A5-91B6-C0E5D9349128}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /ConfuserExCustomModuleConstUnpacker/ConfuserExCustomModuleConstUnpacker/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ConfuserExCustomModuleConstUnpacker/ConfuserExCustomModuleConstUnpacker/ConfuserExCustomModuleConstUnpacker.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6006C5CB-D461-45A5-91B6-C0E5D9349128} 8 | Exe 9 | Properties 10 | ConfuserExCustomModuleConstUnpacker 11 | ConfuserExCustomModuleConstUnpacker 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | False 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 64 | -------------------------------------------------------------------------------- /ConfuserExCustomModuleConstUnpacker/ConfuserExCustomModuleConstUnpacker/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using System.IO; 8 | 9 | namespace ConfuserExCustomModuleConstUnpacker 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | var options = args.TakeWhile(s => s.StartsWith("-")); 16 | 17 | if (options.Any(s => s.ToLowerInvariant() == "--unchecked")) 18 | TargetAssembly.Unchecked = true; 19 | 20 | var infiles = args.Except(options); 21 | if (!infiles.Any()) 22 | { 23 | Console.WriteLine("usage: ConfuserExCustomModuleConstUnpacker [--unchecked] "); 24 | Console.WriteLine("supply the --unchecked option to skip whitelisting of module call sites. This will permit arbitrary code execution from the target assembly, so use with caution."); 25 | return; 26 | } 27 | 28 | try 29 | { 30 | foreach (var s in infiles) 31 | { 32 | var absdir = Path.GetDirectoryName(s); 33 | if (string.IsNullOrWhiteSpace(absdir)) 34 | absdir = "."; 35 | absdir = Path.GetFullPath(absdir); 36 | 37 | foreach (var fs in Directory.GetFiles(absdir, Path.GetFileName(s))) 38 | { 39 | var ta = TargetAssembly.LoadFile(fs); 40 | ta.DecryptAndSave(Path.Combine(Path.GetDirectoryName(fs), Path.GetFileNameWithoutExtension(fs) + ".unpacked" + Path.GetExtension(fs))); 41 | } 42 | } 43 | } 44 | finally 45 | { 46 | if (File.Exists(TargetAssembly.dummy_stub_dll_name)) 47 | File.Delete(TargetAssembly.dummy_stub_dll_name); 48 | if (File.Exists(TargetAssembly.module_out_dll_name)) 49 | File.Delete(TargetAssembly.module_out_dll_name); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ConfuserExCustomModuleConstUnpacker/ConfuserExCustomModuleConstUnpacker/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ConfuserExCustomModuleConstUnpacker")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ConfuserExCustomModuleConstUnpacker")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6006c5cb-d461-45a5-91b6-c0e5d9349128")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ConfuserExCustomModuleConstUnpacker/ConfuserExCustomModuleConstUnpacker/TargetAssembly.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | using System.IO; 8 | 9 | using System.Reflection; 10 | using System.Reflection.Emit; 11 | 12 | using dnlib.DotNet; 13 | using dnlib.DotNet.Emit; 14 | 15 | namespace ConfuserExCustomModuleConstUnpacker 16 | { 17 | class TargetAssembly 18 | { 19 | public static bool Unchecked = false; 20 | 21 | public static TargetAssembly LoadFile(string path) 22 | { 23 | return new TargetAssembly(path); 24 | } 25 | 26 | private string InFile; 27 | 28 | private TargetAssembly(string path) 29 | { 30 | InFile = path; 31 | } 32 | 33 | public static readonly string dummy_stub_dll_name = "cexcmcup_dummy.dll"; 34 | public static readonly string module_out_dll_name = "cexcmcup_module.dll"; 35 | 36 | private static readonly string[] module_call_whitelist = 37 | { 38 | "System.Int32 System.IO.Stream::ReadByte()", 39 | "System.Void System.Object::.ctor()", 40 | "System.UInt32 System.Math::Max(System.UInt32,System.UInt32)", 41 | "System.Void System.Buffer::BlockCopy(System.Array,System.Int32,System.Array,System.Int32,System.Int32)", 42 | "System.Void System.IO.Stream::Write(System.Byte[],System.Int32,System.Int32)", 43 | "System.Void System.IO.MemoryStream::.ctor(System.Byte[])", 44 | "System.Int32 System.IO.Stream::Read(System.Byte[],System.Int32,System.Int32)", 45 | "System.Void System.IO.MemoryStream::.ctor(System.Byte[],System.Boolean)", 46 | "System.Int64 System.IO.Stream::get_Length()", 47 | "System.Void System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(System.Array,System.RuntimeFieldHandle)", 48 | "System.Text.Encoding System.Text.Encoding::get_UTF8()", 49 | "System.String System.Text.Encoding::GetString(System.Byte[],System.Int32,System.Int32)", 50 | "System.String System.String::Intern(System.String)", 51 | "System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)", 52 | "System.Type System.Type::GetElementType()", 53 | "System.Array System.Array::CreateInstance(System.Type,System.Int32)", 54 | }; 55 | 56 | void CreateDummy() 57 | { 58 | // this can be a one time thing. it could also be done in dnlib but i was too dumb to find ModuleDefUser 59 | var ABuilder = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("Dummy"), AssemblyBuilderAccess.RunAndSave); 60 | var MBuilder = ABuilder.DefineDynamicModule("DummyMod", dummy_stub_dll_name); 61 | var tb = MBuilder.DefineType("DummyType", System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class); 62 | tb.CreateType(); 63 | ABuilder.Save(dummy_stub_dll_name); 64 | } 65 | 66 | HashSet GetUses(TypeDef mt) 67 | { 68 | var mtypes = mt.GetTypes().ToList(); 69 | mtypes.Add(mt); 70 | 71 | // whitelist verification 72 | var uses = new HashSet(); 73 | foreach (var mtype in mtypes) 74 | { 75 | foreach (var method in mtype.Methods.Where(m => m.HasBody && m.Body.HasInstructions)) 76 | { 77 | // quite possibly not exhaustive 78 | foreach (var instruction in method.Body.Instructions.Where(ins => ins.GetOpCode() == dnlib.DotNet.Emit.OpCodes.Call || ins.GetOpCode() == dnlib.DotNet.Emit.OpCodes.Callvirt || ins.GetOpCode() == dnlib.DotNet.Emit.OpCodes.Calli || ins.GetOpCode() == dnlib.DotNet.Emit.OpCodes.Newobj)) 79 | { 80 | if (instruction.Operand is IMemberRef) 81 | { 82 | var dc = (instruction.Operand as IMemberRef).DeclaringType; 83 | while (dc != null) 84 | { 85 | if (dc == mt) 86 | break; 87 | dc = dc.DeclaringType; 88 | } 89 | if (dc == mt) 90 | continue; 91 | } 92 | 93 | uses.Add(instruction.Operand.ToString()); 94 | } 95 | } 96 | } 97 | 98 | return uses; 99 | } 100 | 101 | // yank the module out of the source assembly and smack it into a class inside its own assembly - just a small precaution so we dont load the entire source assembly with reflection 102 | bool VerifyAndRewriteModuleToDummy() 103 | { 104 | using (var src_mdd = ModuleDefMD.Load(InFile)) 105 | { 106 | var mt = src_mdd.GlobalType; 107 | 108 | // dnlib doesnt like in memory reflection modules - they have no HINSTANCE 109 | using (var dummy_mdd = ModuleDefMD.Load(dummy_stub_dll_name)) 110 | { 111 | var dummyclass = dummy_mdd.Types.Single(t => !t.IsGlobalModuleType); 112 | 113 | var uses = GetUses(mt); 114 | //foreach (var s in uses) 115 | // System.Diagnostics.Debug.WriteLine("\"" + s + "\","); 116 | 117 | if (!Unchecked) 118 | { 119 | var violation = uses.FirstOrDefault(u => !module_call_whitelist.Contains(u)); 120 | if (violation != null) 121 | { 122 | Console.WriteLine("Non-whitelisted call operand found in module: " + violation); 123 | Console.WriteLine("Supply --unchecked to skip."); 124 | Console.WriteLine("Aborting."); 125 | return false; 126 | } 127 | } 128 | 129 | foreach (var item in mt.Fields.ToList()) 130 | { 131 | item.DeclaringType = null; 132 | dummyclass.Fields.Add(item); 133 | } 134 | 135 | foreach (var item in mt.Methods.ToList()) 136 | { 137 | item.DeclaringType = null; 138 | dummyclass.Methods.Add(item); 139 | } 140 | 141 | foreach (var item in mt.NestedTypes.ToList()) 142 | { 143 | item.DeclaringType = null; 144 | dummyclass.NestedTypes.Add(item); 145 | } 146 | 147 | dummy_mdd.Write(module_out_dll_name); 148 | } 149 | } 150 | 151 | return true; 152 | } 153 | 154 | List CreateConstant(object value, TypeSig ret_type) 155 | { 156 | var new_ins = new List(); 157 | 158 | if (value is string) 159 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Ldstr, value)); 160 | else if (value is int) // I have yet to encounter this but whatever its free 161 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Ldc_I4, value)); 162 | else if (value is Array) 163 | { 164 | var arr = value as Array; 165 | var arrtype = arr.GetType().GetElementType(); 166 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Ldc_I4, arr.Length)); 167 | 168 | var md = ret_type.Next.ToTypeDefOrRef(); 169 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Newarr, md)); 170 | for (int arri = 0; arri < arr.Length; arri++) 171 | { 172 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Dup)); 173 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Ldc_I4, arri)); 174 | if (arrtype == typeof(char)) 175 | { 176 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Ldc_I4, (int)(char)arr.GetValue(arri))); 177 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Stelem_I2)); 178 | } 179 | else if (arrtype == typeof(int)) 180 | { 181 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Ldc_I4, arr.GetValue(arri))); 182 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Stelem_I4)); 183 | } 184 | else if (arrtype == typeof(byte)) // untested but seems like a good one to have 185 | { 186 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Ldc_I4, (int)(byte)arr.GetValue(arri))); 187 | new_ins.Add(new Instruction(dnlib.DotNet.Emit.OpCodes.Stelem_I1)); 188 | } 189 | else 190 | { 191 | Console.WriteLine("unable to handle array type: " + arrtype.FullName); 192 | return null; 193 | } 194 | } 195 | } 196 | 197 | return new_ins; 198 | } 199 | 200 | int HandleMethod(TypeDef mt, Type simulation, MethodDef method) 201 | { 202 | int replcount = 0; 203 | 204 | for (int i = method.Body.Instructions.Count - 1; i > 0; i--) // skip instruction 0 - it can never match 205 | { 206 | var ins = method.Body.Instructions[i]; 207 | MethodSpec site; 208 | if (ins.GetOpCode() == dnlib.DotNet.Emit.OpCodes.Call && (site = ins.Operand as MethodSpec) != null && site.DeclaringType == mt) 209 | { 210 | var prev = method.Body.Instructions[i - 1]; 211 | if (!prev.IsLdcI4()) 212 | continue; 213 | var param = (uint)(int)prev.Operand; 214 | 215 | var ret_type = site.GenericInstMethodSig.GenericArguments.Single(); 216 | if (!CanHandle(ret_type)) // note: just because we can handle it doesnt mean the code to emit the constant exists below :P 217 | { 218 | Console.WriteLine("unable to handle type: " + ret_type.FullName); 219 | continue; 220 | } 221 | 222 | var omi = simulation.GetRuntimeMethods().Single(m => m.Name == site.Method.Name); 223 | 224 | MethodInfo gen_method; 225 | if (ret_type.IsSZArray && !ret_type.Next.IsCorLibType) // array of enum - we just treat it as ints and save ourself the hassle of bringing over the array type to the reflection assembly. enums can in theory also be based on other numeric types; this scenario is not handled 226 | gen_method = omi.MakeGenericMethod(new Type[] { Type.GetType("System.Int32").MakeArrayType() }); 227 | else 228 | gen_method = omi.MakeGenericMethod(new Type[] { Type.GetType(ret_type.FullName) }); 229 | 230 | var value = gen_method.Invoke(null, new object[] { param }); 231 | 232 | var new_ins = CreateConstant(value, ret_type); 233 | 234 | if (new_ins != null && new_ins.Count > 0) 235 | { 236 | replcount++; 237 | 238 | method.Body.SimplifyBranches(); // just in case short jumps will cease working 239 | 240 | var old_to_new = new Dictionary(); 241 | 242 | old_to_new[method.Body.Instructions[i - 1]] = new_ins[0]; 243 | method.Body.Instructions[i - 1] = new_ins[0]; 244 | 245 | old_to_new[method.Body.Instructions[i]] = new_ins[new_ins.Count - 1]; 246 | method.Body.Instructions.RemoveAt(i); 247 | 248 | for (int ii = new_ins.Count - 1; ii > 0; ii--) // skip element 0 249 | method.Body.Instructions.Insert(i, new_ins[ii]); 250 | 251 | method.Body.UpdateInstructionOffsets(); 252 | 253 | FixUp(method, old_to_new); 254 | 255 | method.Body.OptimizeBranches(); 256 | } 257 | } 258 | } 259 | 260 | return replcount; 261 | } 262 | 263 | void FixUp(MethodDef method, Dictionary old_to_new) 264 | { 265 | // fix all references to the old instructions. really wish dnlib had a smart list for instruction tracking/referencing that we could leverage - or maybe I just cant find it again. write it? 266 | foreach (var cf in method.Body.Instructions.Where(ii => ii.Operand is Instruction || ii.Operand is Instruction[])) 267 | { 268 | var insa = cf.Operand as Instruction[]; 269 | if (insa != null) 270 | { 271 | for (int ii = 0; ii < insa.Length; ii++) 272 | if (old_to_new.ContainsKey(insa[ii])) 273 | insa[ii] = old_to_new[insa[ii]]; 274 | } 275 | else if (old_to_new.ContainsKey(cf.Operand as Instruction)) 276 | { 277 | cf.Operand = old_to_new[cf.Operand as Instruction]; 278 | } 279 | } 280 | foreach (var eh in method.Body.ExceptionHandlers) 281 | { 282 | if (old_to_new.ContainsKey(eh.HandlerStart)) 283 | eh.HandlerStart = old_to_new[eh.HandlerStart]; 284 | if (old_to_new.ContainsKey(eh.HandlerEnd)) 285 | eh.HandlerEnd = old_to_new[eh.HandlerEnd]; 286 | if (eh.FilterStart != null && old_to_new.ContainsKey(eh.FilterStart)) 287 | eh.FilterStart = old_to_new[eh.FilterStart]; 288 | if (old_to_new.ContainsKey(eh.TryStart)) 289 | eh.TryStart = old_to_new[eh.TryStart]; 290 | if (old_to_new.ContainsKey(eh.TryEnd)) 291 | eh.TryEnd = old_to_new[eh.TryEnd]; 292 | } 293 | } 294 | 295 | public void DecryptAndSave(string outfile) 296 | { 297 | CreateDummy(); 298 | 299 | if (!VerifyAndRewriteModuleToDummy()) 300 | return; 301 | 302 | // we re-open src because we set some declaring types to things they shouldnt be in VerifyAndRewriteModuleToDummy() 303 | using (var src_mdd = ModuleDefMD.Load(InFile)) 304 | { 305 | var mt = src_mdd.GlobalType; 306 | 307 | // load module assembly 308 | Assembly modass; 309 | using (var a = new BinaryReader(File.OpenRead(Path.GetFullPath(module_out_dll_name)))) 310 | modass = Assembly.Load(a.ReadBytes((int)a.BaseStream.Length)); // load through memory instead of file so we can delete it later 311 | var simulation = modass.ExportedTypes.Single(); 312 | 313 | int replcount = 0; 314 | 315 | foreach (var type in src_mdd.GetTypes().Where(t => t != mt)) 316 | { 317 | // dont touch any types that are somehow contained in the module itself 318 | var dc = type.DeclaringType; 319 | while (dc != null) 320 | { 321 | if (dc == mt) 322 | break; 323 | dc = dc.DeclaringType; 324 | } 325 | if (dc == mt) 326 | continue; 327 | 328 | foreach (var method in type.Methods.Where(m => m.HasBody && m.Body.HasInstructions)) 329 | { 330 | replcount += HandleMethod(mt, simulation, method); 331 | } 332 | } 333 | 334 | Console.WriteLine("replaced " + replcount + " constants in " + Path.GetFileName(InFile) + ". Attempting to save.."); 335 | try 336 | { 337 | src_mdd.Write(outfile, new dnlib.DotNet.Writer.ModuleWriterOptions() { MetaDataOptions = new dnlib.DotNet.Writer.MetaDataOptions(dnlib.DotNet.Writer.MetaDataFlags.KeepOldMaxStack) }); 338 | } 339 | catch (Exception ex) 340 | { 341 | Console.WriteLine("FAILED"); 342 | Console.WriteLine(ex.ToString()); 343 | Console.WriteLine(); 344 | Console.WriteLine(); 345 | } 346 | } 347 | } 348 | 349 | private bool CanHandle(TypeSig ret_type) 350 | { 351 | if (ret_type.IsCorLibType) 352 | return true; 353 | 354 | if (ret_type.IsSZArray) 355 | { 356 | if (ret_type.Next.IsCorLibType) 357 | return true; // not technically true - only char, int and byte(?) will work 358 | 359 | var vts = ret_type.Next as ValueTypeSig; 360 | 361 | if (vts != null && vts.TypeDef.BaseType.FullName.Contains("System.Enum")) 362 | return true; 363 | } 364 | 365 | return false; 366 | } 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A ConfuserEx-custom deobfuscation toolchain 2 | =========================================== 3 | To treat assemblies obfuscated with [yck1509's ConfuserEX](https://github.com/yck1509/ConfuserEx), with particular focus on Unity/Mono. 4 | This toolchain is targeted at developers that more or less know what they are doing, and should provide you with fairly clean code to analyze. 5 | 6 | No warranty, use at your own risk, yadayada. Licenses of libraries and tools used apply. This was done as a personal research project into obfuscation. Laws may apply that restrict usage of this tool. 7 | 8 | Overview 9 | -------- 10 | This package contains and describes in this README file: 11 | 12 | 1. A few pointers on how to use de4dot to prepare an obfuscated assembly. 13 | 2. A string and array constant unpacker that bakes ungainly method calls to the hidden back into ldstr/ldc on the IL level. 14 | 3. A deobfuscation method for the switch mangler employed for control flow obfuscation - including a modified yield return decompiler for Mono. 15 | 16 | How to use 17 | ---------- 18 | ### 1. Preparing and sanitizing the assembly 19 | Grab a copy of [0xd4d's de4dot](https://github.com/0xd4d/de4dot/) (and optionally, if you got the source, build it), then run 20 | 21 | de4dot TARGET_ASSEMBLY -p un --un-name "!^_[0-9a-zA-Z]{26,28}$&^[a-zA-Z_<{$][a-zA-Z_0-9<>{}$.`-]*$" 22 | 23 | The additional regex will prettify some symbol names, and generally this cleans up any unwieldy/invalid unicode in symbol names. 24 | 25 | This should generate TARGET_ASSEMBLY-cleaned, which you will use for the following step. 26 | 27 | ### 2. Unpacking constants 28 | #### WARNING 29 | *This step will execute code in your target assembly via reflection!* If you have any doubt about the nature of it, do not risk this without looking at the code in its Module, and ensure it does nothing besides internal data manipulation. 30 | There are measures in place that should in theory prevent the code from running rampart, but they may not be exhaustive. 31 | 32 | Clone this repository if you haven't already, open up ConfuserExCustomModuleConstUnpacker.sln, ensure it can find and references dnlib, and build it. 33 | If this gives you grief, skip ahead to step 3 where you build dnSpy, which in turn builds dnlib.dll in the process. You can now add a reference to this dll in the ConfuserExCustomModuleConstUnpacker project. 34 | 35 | Run 36 | 37 | ConfuserExCustomModuleConstUnpacker TARGET_ASSEMBLY 38 | 39 | If this step fails, you can skip it, look to other similar projects on GitHub (search for ConfuserEx), or try the --unchecked option at a considerable risk of arbitrary code execution. Or look at the code to see what the hell I'm doing wrong. 40 | 41 | If it succeeds, it will generate TARGET_ASSEMBLY.unpacked.EXT, which you will use for the following step. 42 | 43 | ### 3. Patching dnSpy and exporting your assembly's code to a project 44 | Start off by cloning [dnSpy](https://github.com/0xd4d/dnSpy) (assuming a git MINGW bash): 45 | 46 | git clone https://github.com/0xd4d/dnSpy 47 | cd dnSpy 48 | git submodule init 49 | git submodule update --recursive 50 | 51 | Apply my patch, found in the root of this repository. 52 | 53 | cd Extensions/ILSpy.Decompiler/ICSharpCode.Decompiler 54 | git apply --ignore-whitespace ../../../../cfxc-deobf/icscdecomp_cfxc_deobf.patch 55 | 56 | Your path to the patch may vary, of course. If it fails, you can first grab the revision this patch was made against via **git checkout af3940e**, or experiment with the **--3way** option. 57 | 58 | Build and run (make sure you have NuGet and it restores packages), load up and select the assembly from the previous step, File->Export to Project. Done! 59 | 60 | If this process breaks on Debugger.Break(), you found a scenario I didn't expect or cover. Happy hunting, or hit F5 and ignore it. You can comment those breaks if they annoy you. 61 | 62 | ### 4. The result 63 | Some methods will still produce garbage or exceptions. Particularly yield return constructs are fickle. 64 | You can use dnSpy's step-by-step ILAst generation to get a read on what's going on and perhaps manually extract code for such scenarios, there shouldn't be too many. Sometimes a vanilla version of dnSpy or ILSpy might also be the solution. 65 | 66 | Sometimes you will be left with some compiler-generated, invalid symbol names. That usually also means something went awry, but sometimes just renaming them with a regex will do. Here' one for VS: 67 | 68 | <>f?__([a-zA-Z0-9]+)\$?([a-zA-Z0-9]*) 69 | $1$2 70 | 71 | If you still get garbage control flow, a different method from what I target was employed. Good hunting! I hope my patch is somewhat comprehensible and buildable-upon. It's honestly pretty hackish. Poor you. 72 | 73 | Appendix: Author's ramblings 74 | ---------------------- 75 | Huge shoutouts to the ILspy developers (particularly, whoever created the ILAst and Decompiler bits), 0xd4d and yck1509. I mostly did this out of intellectual curiosity, and to see what was inside certain Unity assemblies. Most ConfuserEx unpackers don't tackle the control-flow obfuscation, and I found it 76 | a great challenge. I also got yield-return to work (mostly) and fixed some bugs/false assumptions in the decompilation process. 77 | 78 | It should be noted that despite ConfuserEx being open source, I did 80% of this with a black box approach. I did at some point cave 79 | and sneaked a peek at its source, and perhaps a bit of inspiration came from that, but I was very close to a working approach then. 80 | 81 | Others have tackled the 2nd step of my package, but I didn't really like their approaches, so I made my own. I can't rightly claim I tested any of the stuff on github. It might do a better job. 82 | 83 | #### Wishlist 84 | * Rewrite the yield return decompiler entirely - it's a little too narrow to catch many obfuscation scenarios, though for Mono, I think I did an okay job. What's missing is mostly documented in my patch. 85 | * Yield-return for platforms other than Mono. Ties in with the above. 86 | * Getting this in an all-in-one package... yea probably not happening. 87 | * Submit what I believe to be actual improvements to ICSharpCode.Decompiler. 88 | * Make Deobf a discernible and "skippable" entry for dnSpy. 89 | * Code quality. 90 | * More constant types supported in ConfuserExCustomModuleConstUnpacker. 91 | 92 | #### Why..? 93 | ###### Why a patch instead of a fork? 94 | It seemed more appropriate to me, and i dislike forks that also add random stuff, as I would have had to do here. 95 | ###### Why do this as part of dnSpy instead of something much more obvious, like de4dot? 96 | I liked ILAst a lot for this, which is not innately part of de4dot. I liked that it did a control flow graph for me. I liked that I could take a look at the ILAst at different stages to figure out what was going on. But you're right, an all-in-one solution would have been better. 97 | 98 | #### Bonus 99 | Here is a fun bit of valid IL that decompiles to invalid C# I encountered along the way: 100 | 101 | ###### ILAst 102 | 103 | ceq:bool( 104 | ldfld:SomeEnum(var_0, ldloc:T(this)), 105 | and:SomeEnum( 106 | ldfld:SomeEnum(var_0, ldloc:T(this)), 107 | neg:SomeEnum(ldfld:SomeEnum(var_0, ldloc:T(this))) 108 | ) 109 | ) 110 | 111 | ###### C# 112 | 113 | [Flags] 114 | enum SomeEnum 115 | { 116 | ... 117 | } 118 | 119 | ... 120 | 121 | SomeEnum e; 122 | ... 123 | if (e == (e & -e)) 124 | ... 125 | 126 | Now, negation is not a valid operation on enums, even Flags, but while IL doesn't care about that, the C# compiler does. 127 | For the curious, this check is used, when iterating all members of an enum, to exclude elements of an enum that have more than one bit set, which is to say, bit masks! 128 | You can fix this by first casting the enum to an integer type. 129 | -------------------------------------------------------------------------------- /icscdecomp_cfxc_deobf.patch: -------------------------------------------------------------------------------- 1 | diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs 2 | index 6cbb6af..75e5ba6 100644 3 | --- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs 4 | +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs 5 | @@ -168,7 +168,7 @@ static MethodBaseSig GetMethodBaseSig(ITypeDefOrRef type, MethodBaseSig msig, IL 6 | 7 | internal static bool IsAnonymousMethod(DecompilerContext context, MethodDef method) 8 | { 9 | - if (method == null || !(method.HasGeneratedName() || method.Name.Contains("$"))) 10 | + if (method == null || !(method.IsCompilerGeneratedOrIsInCompilerGeneratedClass() || method.Name.Contains("$"))) 11 | return false; 12 | if (!(method.IsCompilerGenerated() || IsPotentialClosure(context, method.DeclaringType))) 13 | return false; 14 | diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs 15 | index 4fcfed0..756eb6b 100644 16 | --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs 17 | +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs 18 | @@ -228,17 +228,22 @@ internal void Optimize(DecompilerContext context, ILBlock method, AutoPropertyPr 19 | if (abortBeforeStep == ILAstOptimizationStep.CopyPropagation) return; 20 | inlining1.CopyPropagation(Optimize_List_ILNode); 21 | 22 | - if (abortBeforeStep == ILAstOptimizationStep.YieldReturn) return; 23 | - YieldReturnDecompiler.Run(context, method, autoPropertyProvider, Optimize_List_ILNode, del_getILInlining, Optimize_List_ILExpression, Optimize_List_ILBlock, Optimize_Dict_ILLabel_Int32); 24 | - var yrd = AsyncDecompiler.RunStep1(context, method, autoPropertyProvider, Optimize_List_ILExpression, Optimize_List_ILBlock, Optimize_Dict_ILLabel_Int32); 25 | - 26 | - if (abortBeforeStep == ILAstOptimizationStep.AsyncAwait) return; 27 | - yrd?.RunStep2(context, method, Optimize_List_ILExpression, Optimize_List_ILBlock, Optimize_Dict_ILLabel_Int32, Optimize_List_ILNode, del_getILInlining); 28 | - 29 | - if (abortBeforeStep == ILAstOptimizationStep.PropertyAccessInstructions) return; 30 | + var needs_deobf = GetLoopsAndConditions(context).NeedsDeobf(method, autoPropertyProvider); 31 | + if (!needs_deobf) 32 | + { 33 | + if (abortBeforeStep == ILAstOptimizationStep.YieldReturn) return; 34 | + YieldReturnDecompiler.Run(context, method, autoPropertyProvider, Optimize_List_ILNode, del_getILInlining, Optimize_List_ILExpression, Optimize_List_ILBlock, Optimize_Dict_ILLabel_Int32); 35 | + } 36 | + 37 | + // misnomer? 38 | + var yrd = AsyncDecompiler.RunStep1(context, method, autoPropertyProvider, Optimize_List_ILExpression, Optimize_List_ILBlock, Optimize_Dict_ILLabel_Int32); 39 | + if (abortBeforeStep == ILAstOptimizationStep.AsyncAwait) return; 40 | + yrd?.RunStep2(context, method, Optimize_List_ILExpression, Optimize_List_ILBlock, Optimize_Dict_ILLabel_Int32, Optimize_List_ILNode, del_getILInlining); 41 | + 42 | + if (abortBeforeStep == ILAstOptimizationStep.PropertyAccessInstructions) return; 43 | IntroducePropertyAccessInstructions(method); 44 | 45 | - if (abortBeforeStep == ILAstOptimizationStep.SplitToMovableBlocks) return; 46 | + if (abortBeforeStep == ILAstOptimizationStep.SplitToMovableBlocks) return; 47 | foreach (ILBlock block in method.GetSelfAndChildrenRecursive(Optimize_List_ILBlock)) { 48 | SplitToBasicBlocks(block); 49 | } 50 | @@ -317,7 +322,73 @@ internal void Optimize(DecompilerContext context, ILBlock method, AutoPropertyPr 51 | } while (modified); 52 | } 53 | 54 | - if (abortBeforeStep == ILAstOptimizationStep.FindLoops) return; 55 | + if (needs_deobf) 56 | + { 57 | + foreach (ILBlock block in method.GetSelfAndChildrenRecursive(Optimize_List_ILBlock)) 58 | + { 59 | + GetLoopsAndConditions(context).Deobf(block); 60 | + } 61 | + 62 | + if (abortBeforeStep == ILAstOptimizationStep.YieldReturn) return; 63 | + YieldReturnDecompiler.Run(context, method, autoPropertyProvider, Optimize_List_ILNode, del_getILInlining, Optimize_List_ILExpression, Optimize_List_ILBlock, Optimize_Dict_ILLabel_Int32); 64 | + 65 | + if (method.Body.Count > 0 && !(method.Body[0] is ILBasicBlock)) 66 | + { 67 | + foreach (ILBlock block in method.GetSelfAndChildrenRecursive(Optimize_List_ILBlock)) 68 | + SplitToBasicBlocks(block); 69 | + 70 | + bool modified; 71 | + do 72 | + { 73 | + modified = false; 74 | + 75 | + foreach (ILBlock block in method.GetSelfAndChildrenRecursive(Optimize_List_ILBlock)) 76 | + modified |= GetLoopsAndConditions(context).Fixup(method, block); 77 | + } while (modified); 78 | + } 79 | + 80 | + // repeat this stuff to prettyify our output. may wanna skip some steps, not sure 81 | + foreach (ILBlock block in method.GetSelfAndChildrenRecursive(Optimize_List_ILBlock)) 82 | + { 83 | + bool modified; 84 | + do 85 | + { 86 | + modified = false; 87 | + modified |= block.RunOptimization(GetSimpleControlFlow(context, method).SimplifyShortCircuit); 88 | + modified |= block.RunOptimization(GetSimpleControlFlow(context, method).SimplifyTernaryOperator); 89 | + modified |= block.RunOptimization(GetSimpleControlFlow(context, method).SimplifyNullCoalescing); 90 | + modified |= block.RunOptimization(GetSimpleControlFlow(context, method).JoinBasicBlocks); 91 | + modified |= block.RunOptimization(SimplifyLogicNot); 92 | + modified |= block.RunOptimization(SimplifyShiftOperators); 93 | + //modified |= block.RunOptimization(TypeConversionSimplifications); 94 | + modified |= block.RunOptimization(SimplifyLdObjAndStObj); 95 | + modified |= block.RunOptimization(GetSimpleControlFlow(context, method).SimplifyCustomShortCircuit); 96 | + modified |= block.RunOptimization(SimplifyLiftedOperators); 97 | + //modified |= block.RunOptimization(TransformArrayInitializers); 98 | + //modified |= block.RunOptimization(TransformMultidimensionalArrayInitializers); 99 | + //modified |= block.RunOptimization(TransformObjectInitializers); 100 | + if (context.Settings.MakeAssignmentExpressions) 101 | + { 102 | + modified |= block.RunOptimization(MakeAssignmentExpression); 103 | + } 104 | + modified |= block.RunOptimization(MakeCompoundAssignments); 105 | + if (context.Settings.IntroduceIncrementAndDecrement) 106 | + { 107 | + modified |= block.RunOptimization(IntroducePostIncrement); 108 | + } 109 | + if (context.Settings.ExpressionTrees) 110 | + { 111 | + modified |= block.RunOptimization(InlineExpressionTreeParameterDeclarations); 112 | + } 113 | + modified |= GetILInlining(method).InlineAllInBlock(block); 114 | + GetILInlining(method).CopyPropagation(Optimize_List_ILNode); 115 | + 116 | + } while (modified); 117 | + } 118 | + } 119 | + // -- 120 | + 121 | + if (abortBeforeStep == ILAstOptimizationStep.FindLoops) return; 122 | foreach (ILBlock block in method.GetSelfAndChildrenRecursive(Optimize_List_ILBlock)) { 123 | GetLoopsAndConditions(context).FindLoops(block); 124 | } 125 | @@ -1425,7 +1496,7 @@ internal static void RemoveRedundantCode(DecompilerContext context, ILBlock meth 126 | foreach (var target in e.GetBranchTargets()) 127 | labelRefCount[target] = labelRefCount.GetOrDefault(target) + 1; 128 | } 129 | - 130 | + 131 | foreach(ILBlock block in method.GetSelfAndChildrenRecursive(listBlock)) { 132 | List body = block.Body; 133 | List newBody = new List(body.Count); 134 | @@ -1648,7 +1719,7 @@ void IntroducePropertyAccessInstructions(ILExpression expr, ILExpression parentE 135 | /// The method adds necessary branches to make control flow between blocks 136 | /// explicit and thus order independent. 137 | /// 138 | - void SplitToBasicBlocks(ILBlock block) 139 | + internal void SplitToBasicBlocks(ILBlock block) 140 | { 141 | List basicBlocks = new List(); 142 | 143 | diff --git a/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs b/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs 144 | index d1e6dc6..ba59cda 100644 145 | --- a/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs 146 | +++ b/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs 147 | @@ -16,469 +16,1141 @@ 148 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 149 | // DEALINGS IN THE SOFTWARE. 150 | 151 | +using System; 152 | using System.Collections.Generic; 153 | using System.Linq; 154 | using dnSpy.Contracts.Decompiler; 155 | using ICSharpCode.Decompiler.FlowAnalysis; 156 | 157 | -namespace ICSharpCode.Decompiler.ILAst { 158 | - /// 159 | - /// Description of LoopsAndConditions. 160 | - /// 161 | - public class LoopsAndConditions 162 | - { 163 | - readonly Dictionary labelToCfNode = new Dictionary(); 164 | - 165 | - DecompilerContext context; 166 | - 167 | - uint nextLabelIndex; 168 | - 169 | - public LoopsAndConditions(DecompilerContext context) 170 | - { 171 | - Initialize(context); 172 | - } 173 | - 174 | - public void Initialize(DecompilerContext context) 175 | - { 176 | - this.context = context; 177 | - this.labelToCfNode.Clear(); 178 | - this.nextLabelIndex = 0; 179 | - } 180 | - 181 | - public void FindLoops(ILBlock block) 182 | - { 183 | - if (block.Body.Count > 0) { 184 | - ControlFlowGraph graph; 185 | - graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); 186 | - graph.ComputeDominance(context.CancellationToken); 187 | - graph.ComputeDominanceFrontier(); 188 | - //TODO: Keep BinSpans when writing to Body 189 | - block.Body = FindLoops(new HashSet(graph.Nodes.Skip(3)), graph.EntryPoint, false); 190 | - } 191 | - } 192 | - 193 | - public void FindConditions(ILBlock block) 194 | - { 195 | - if (block.Body.Count > 0) { 196 | - ControlFlowGraph graph; 197 | - graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); 198 | - graph.ComputeDominance(context.CancellationToken); 199 | - graph.ComputeDominanceFrontier(); 200 | - //TODO: Keep BinSpans when writing to Body 201 | - block.Body = FindConditions(new HashSet(graph.Nodes.Skip(3)), graph.EntryPoint); 202 | - } 203 | - } 204 | - 205 | - readonly ControlFlowGraph cached_ControlFlowGraph = new ControlFlowGraph(); 206 | - ControlFlowGraph BuildGraph(List nodes, ILLabel entryLabel) 207 | - { 208 | - cached_ControlFlowGraph.Nodes.Clear(); 209 | - int index = 0; 210 | - var cfNodes = cached_ControlFlowGraph.Nodes; 211 | - ControlFlowNode entryPoint = new ControlFlowNode(index++, 0, ControlFlowNodeType.EntryPoint); 212 | - cfNodes.Add(entryPoint); 213 | - ControlFlowNode regularExit = new ControlFlowNode(index++, null, ControlFlowNodeType.RegularExit); 214 | - cfNodes.Add(regularExit); 215 | - ControlFlowNode exceptionalExit = new ControlFlowNode(index++, null, ControlFlowNodeType.ExceptionalExit); 216 | - cfNodes.Add(exceptionalExit); 217 | - 218 | - // Create graph nodes 219 | - labelToCfNode.Clear(); 220 | - Dictionary astNodeToCfNode = new Dictionary(); 221 | - List listLabels = null; 222 | - foreach(ILBasicBlock node in nodes) { 223 | - ControlFlowNode cfNode = new ControlFlowNode(index++, null, ControlFlowNodeType.Normal); 224 | - cfNodes.Add(cfNode); 225 | - astNodeToCfNode[node] = cfNode; 226 | - cfNode.UserData = node; 227 | - 228 | - // Find all contained labels 229 | - foreach(ILLabel label in node.GetSelfAndChildrenRecursive(listLabels ?? (listLabels = new List()))) { 230 | - labelToCfNode[label] = cfNode; 231 | - } 232 | - } 233 | - 234 | - // Entry endge 235 | - ControlFlowNode entryNode = labelToCfNode[entryLabel]; 236 | - ControlFlowEdge entryEdge = new ControlFlowEdge(entryPoint, entryNode, JumpType.Normal); 237 | - entryPoint.Outgoing.Add(entryEdge); 238 | - entryNode.Incoming.Add(entryEdge); 239 | - 240 | - // Create edges 241 | - List listExpressions = null; 242 | - foreach(ILBasicBlock node in nodes) { 243 | - ControlFlowNode source = astNodeToCfNode[node]; 244 | - 245 | - // Find all branches 246 | - foreach(ILLabel target in node.GetSelfAndChildrenRecursive(listExpressions ?? (listExpressions = new List()), e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) { 247 | - ControlFlowNode destination; 248 | - // Labels which are out of out scope will not be in the collection 249 | - // Insert self edge only if we are sure we are a loop 250 | - if (labelToCfNode.TryGetValue(target, out destination) && (destination != source || target == node.Body.FirstOrDefault())) { 251 | - ControlFlowEdge edge = new ControlFlowEdge(source, destination, JumpType.Normal); 252 | - source.Outgoing.Add(edge); 253 | - destination.Incoming.Add(edge); 254 | - } 255 | - } 256 | - } 257 | - 258 | - return cached_ControlFlowGraph; 259 | - } 260 | - 261 | - List FindLoops(HashSet scope, ControlFlowNode entryPoint, bool excludeEntryPoint) 262 | - { 263 | - List result = new List(); 264 | - 265 | - // Do not modify entry data 266 | - scope = new HashSet(scope); 267 | - 268 | - Queue agenda = new Queue(); 269 | - agenda.Enqueue(entryPoint); 270 | - while(agenda.Count > 0) { 271 | - ControlFlowNode node = agenda.Dequeue(); 272 | - 273 | - // If the node is a loop header 274 | - if (scope.Contains(node) 275 | - && node.DominanceFrontier.Contains(node) 276 | - && (node != entryPoint || !excludeEntryPoint)) 277 | - { 278 | - HashSet loopContents = FindLoopContent(scope, node); 279 | - 280 | - // If the first expression is a loop condition 281 | - ILBasicBlock basicBlock = (ILBasicBlock)node.UserData; 282 | - ILExpression condExpr; 283 | - ILLabel trueLabel; 284 | - ILLabel falseLabel; 285 | - // It has to be just brtrue - any preceding code would introduce goto 286 | - if(basicBlock.MatchSingleAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) 287 | - { 288 | - ControlFlowNode trueTarget; 289 | - labelToCfNode.TryGetValue(trueLabel, out trueTarget); 290 | - ControlFlowNode falseTarget; 291 | - labelToCfNode.TryGetValue(falseLabel, out falseTarget); 292 | - 293 | - // If one point inside the loop and the other outside 294 | - if ((!loopContents.Contains(trueTarget) && loopContents.Contains(falseTarget)) || 295 | - (loopContents.Contains(trueTarget) && !loopContents.Contains(falseTarget)) ) 296 | - { 297 | - loopContents.RemoveOrThrow(node); 298 | - scope.RemoveOrThrow(node); 299 | - 300 | - // If false means enter the loop 301 | - if (loopContents.Contains(falseTarget) || falseTarget == node) 302 | - { 303 | - // Negate the condition 304 | - condExpr = new ILExpression(ILCode.LogicNot, null, condExpr); 305 | - ILLabel tmp = trueLabel; 306 | - trueLabel = falseLabel; 307 | - falseLabel = tmp; 308 | - } 309 | - 310 | - ControlFlowNode postLoopTarget; 311 | - labelToCfNode.TryGetValue(falseLabel, out postLoopTarget); 312 | - if (postLoopTarget != null) { 313 | - // Pull more nodes into the loop 314 | - HashSet postLoopContents = FindDominatedNodes(scope, postLoopTarget); 315 | - var pullIn = scope.Except(postLoopContents).Where(n => node.Dominates(n)); 316 | - loopContents.UnionWith(pullIn); 317 | - } 318 | - 319 | - // Use loop to implement the brtrue 320 | - var tail = basicBlock.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); 321 | - ILWhileLoop whileLoop; 322 | - basicBlock.Body.Add(whileLoop = new ILWhileLoop() { 323 | - Condition = condExpr, 324 | - BodyBlock = new ILBlock(CodeBracesRangeFlags.LoopBraces) { 325 | - EntryGoto = new ILExpression(ILCode.Br, trueLabel), 326 | - Body = FindLoops(loopContents, node, false) 327 | - } 328 | - }); 329 | - if (context.CalculateBinSpans) { 330 | - whileLoop.BinSpans.AddRange(tail[0].BinSpans); // no recursive add 331 | - tail[1].AddSelfAndChildrenRecursiveBinSpans(whileLoop.BinSpans); 332 | - } 333 | - basicBlock.Body.Add(new ILExpression(ILCode.Br, falseLabel)); 334 | - result.Add(basicBlock); 335 | - 336 | - scope.ExceptWith(loopContents); 337 | - } 338 | - } 339 | - 340 | - // Fallback method: while(true) 341 | - if (scope.Contains(node)) { 342 | - result.Add(new ILBasicBlock() { 343 | - Body = new List() { 344 | - new ILLabel() { Name = "Loop_" + (nextLabelIndex++).ToString() }, 345 | - new ILWhileLoop() { 346 | - BodyBlock = new ILBlock(CodeBracesRangeFlags.LoopBraces) { 347 | - EntryGoto = new ILExpression(ILCode.Br, (ILLabel)basicBlock.Body.First()), 348 | - Body = FindLoops(loopContents, node, true) 349 | - } 350 | - }, 351 | - }, 352 | - }); 353 | - 354 | - scope.ExceptWith(loopContents); 355 | - } 356 | - } 357 | - 358 | - // Using the dominator tree should ensure we find the the widest loop first 359 | - foreach(var child in node.DominatorTreeChildren) { 360 | - agenda.Enqueue(child); 361 | - } 362 | - } 363 | - 364 | - // Add whatever is left 365 | - foreach(var node in scope) { 366 | - result.Add((ILNode)node.UserData); 367 | - } 368 | - scope.Clear(); 369 | - 370 | - return result; 371 | - } 372 | - 373 | - List FindConditions(HashSet scope, ControlFlowNode entryNode) 374 | - { 375 | - List result = new List(); 376 | - 377 | - // Do not modify entry data 378 | - scope = new HashSet(scope); 379 | - 380 | - Stack agenda = new Stack(); 381 | - agenda.Push(entryNode); 382 | - while(agenda.Count > 0) { 383 | - ControlFlowNode node = agenda.Pop(); 384 | - 385 | - // Find a block that represents a simple condition 386 | - if (scope.Contains(node)) { 387 | - 388 | - ILBasicBlock block = (ILBasicBlock)node.UserData; 389 | - 390 | - { 391 | - // Switch 392 | - ILLabel[] caseLabels; 393 | - ILExpression switchArg; 394 | - ILLabel fallLabel; 395 | - if (block.MatchLastAndBr(ILCode.Switch, out caseLabels, out switchArg, out fallLabel)) { 396 | - 397 | - // Replace the switch code with ILSwitch 398 | - ILSwitch ilSwitch = new ILSwitch() { Condition = switchArg }; 399 | - var tail = block.Body.RemoveTail(ILCode.Switch, ILCode.Br); 400 | - if (context.CalculateBinSpans) { 401 | - ilSwitch.BinSpans.AddRange(tail[0].BinSpans); // no recursive add 402 | - tail[1].AddSelfAndChildrenRecursiveBinSpans(ilSwitch.BinSpans); 403 | - } 404 | - block.Body.Add(ilSwitch); 405 | - block.Body.Add(new ILExpression(ILCode.Br, fallLabel)); 406 | - result.Add(block); 407 | - 408 | - // Remove the item so that it is not picked up as content 409 | - scope.RemoveOrThrow(node); 410 | - 411 | - // Find the switch offset 412 | - int addValue = 0; 413 | - List subArgs; 414 | - if (ilSwitch.Condition.Match(ILCode.Sub, out subArgs) && subArgs[1].Match(ILCode.Ldc_I4, out addValue)) { 415 | - var old = ilSwitch.Condition; 416 | - ilSwitch.Condition = subArgs[0]; 417 | - if (context.CalculateBinSpans) { 418 | - ilSwitch.Condition.BinSpans.AddRange(old.BinSpans); // no recursive add 419 | - for (int i = 1; i < subArgs.Count; i++) 420 | - subArgs[i].AddSelfAndChildrenRecursiveBinSpans(ilSwitch.Condition.BinSpans); 421 | - } 422 | - } 423 | - 424 | - // Pull in code of cases 425 | - ControlFlowNode fallTarget = null; 426 | - labelToCfNode.TryGetValue(fallLabel, out fallTarget); 427 | - 428 | - HashSet frontiers = new HashSet(); 429 | - if (fallTarget != null) 430 | - frontiers.UnionWith(fallTarget.DominanceFrontier.Except(new [] { fallTarget })); 431 | - 432 | - foreach(ILLabel condLabel in caseLabels) { 433 | - ControlFlowNode condTarget; 434 | - labelToCfNode.TryGetValue(condLabel, out condTarget); 435 | - if (condTarget != null) 436 | - frontiers.UnionWith(condTarget.DominanceFrontier.Except(new [] { condTarget })); 437 | - } 438 | - 439 | - bool includedDefault = false; 440 | - for (int i = 0; i < caseLabels.Length; i++) { 441 | - ILLabel condLabel = caseLabels[i]; 442 | - 443 | - // Find or create new case block 444 | - ILSwitch.CaseBlock caseBlock = ilSwitch.CaseBlocks.FirstOrDefault(b => b.EntryGoto.Operand == condLabel); 445 | - if (caseBlock == null) { 446 | - caseBlock = new ILSwitch.CaseBlock() { 447 | - Values = new List(), 448 | - EntryGoto = new ILExpression(ILCode.Br, condLabel) 449 | - }; 450 | - ilSwitch.CaseBlocks.Add(caseBlock); 451 | - if (!includedDefault && condLabel == fallLabel) { 452 | - includedDefault = true; 453 | - block.Body.RemoveTail(ILCode.Br); 454 | - caseBlock.Values = null; 455 | - } 456 | - 457 | - ControlFlowNode condTarget = null; 458 | - labelToCfNode.TryGetValue(condLabel, out condTarget); 459 | - if (condTarget != null && !frontiers.Contains(condTarget)) { 460 | - HashSet content = FindDominatedNodes(scope, condTarget); 461 | - scope.ExceptWith(content); 462 | - caseBlock.Body.AddRange(FindConditions(content, condTarget)); 463 | - // Add explicit break which should not be used by default, but the goto removal might decide to use it 464 | - caseBlock.Body.Add(new ILBasicBlock() { 465 | - Body = { 466 | - new ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++).ToString() }, 467 | - new ILExpression(ILCode.LoopOrSwitchBreak, null) 468 | - } 469 | - }); 470 | - } 471 | - } 472 | - caseBlock.Values?.Add(i + addValue); 473 | - } 474 | - 475 | - // Heuristis to determine if we want to use fallthough as default case 476 | - if (!includedDefault && fallTarget != null && !frontiers.Contains(fallTarget)) { 477 | - HashSet content = FindDominatedNodes(scope, fallTarget); 478 | - if (content.Any()) { 479 | - var caseBlock = new ILSwitch.CaseBlock() { EntryGoto = new ILExpression(ILCode.Br, fallLabel) }; 480 | - ilSwitch.CaseBlocks.Add(caseBlock); 481 | - tail = block.Body.RemoveTail(ILCode.Br); 482 | - if (context.CalculateBinSpans) 483 | - tail[0].AddSelfAndChildrenRecursiveBinSpans(caseBlock.BinSpans); 484 | - 485 | - scope.ExceptWith(content); 486 | - caseBlock.Body.AddRange(FindConditions(content, fallTarget)); 487 | - // Add explicit break which should not be used by default, but the goto removal might decide to use it 488 | - caseBlock.Body.Add(new ILBasicBlock() { 489 | - Body = { 490 | - new ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++).ToString() }, 491 | - new ILExpression(ILCode.LoopOrSwitchBreak, null) 492 | - } 493 | - }); 494 | - } 495 | - } 496 | - } 497 | - 498 | - // Two-way branch 499 | - ILExpression condExpr; 500 | - ILLabel trueLabel; 501 | - ILLabel falseLabel; 502 | - if(block.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) { 503 | - 504 | - // Swap bodies since that seems to be the usual C# order 505 | - ILLabel temp = trueLabel; 506 | - trueLabel = falseLabel; 507 | - falseLabel = temp; 508 | - condExpr = new ILExpression(ILCode.LogicNot, null, condExpr); 509 | - 510 | - // Convert the brtrue to ILCondition 511 | - ILCondition ilCond = new ILCondition() { 512 | - Condition = condExpr, 513 | - TrueBlock = new ILBlock(CodeBracesRangeFlags.ConditionalBraces) { EntryGoto = new ILExpression(ILCode.Br, trueLabel) }, 514 | - FalseBlock = new ILBlock(CodeBracesRangeFlags.ConditionalBraces) { EntryGoto = new ILExpression(ILCode.Br, falseLabel) } 515 | - }; 516 | - var tail = block.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); 517 | - if (context.CalculateBinSpans) { 518 | - condExpr.BinSpans.AddRange(tail[0].BinSpans); // no recursive add 519 | - tail[1].AddSelfAndChildrenRecursiveBinSpans(ilCond.FalseBlock.BinSpans); 520 | - } 521 | - block.Body.Add(ilCond); 522 | - result.Add(block); 523 | - 524 | - // Remove the item immediately so that it is not picked up as content 525 | - scope.RemoveOrThrow(node); 526 | - 527 | - ControlFlowNode trueTarget = null; 528 | - labelToCfNode.TryGetValue(trueLabel, out trueTarget); 529 | - ControlFlowNode falseTarget = null; 530 | - labelToCfNode.TryGetValue(falseLabel, out falseTarget); 531 | - 532 | - // Pull in the conditional code 533 | - if (trueTarget != null && HasSingleEdgeEnteringBlock(trueTarget)) { 534 | - HashSet content = FindDominatedNodes(scope, trueTarget); 535 | - scope.ExceptWith(content); 536 | - ilCond.TrueBlock.Body.AddRange(FindConditions(content, trueTarget)); 537 | - } 538 | - if (falseTarget != null && HasSingleEdgeEnteringBlock(falseTarget)) { 539 | - HashSet content = FindDominatedNodes(scope, falseTarget); 540 | - scope.ExceptWith(content); 541 | - ilCond.FalseBlock.Body.AddRange(FindConditions(content, falseTarget)); 542 | - } 543 | - } 544 | - } 545 | - 546 | - // Add the node now so that we have good ordering 547 | - if (scope.Contains(node)) { 548 | - result.Add((ILNode)node.UserData); 549 | - scope.Remove(node); 550 | - } 551 | - } 552 | - 553 | - // depth-first traversal of dominator tree 554 | - for (int i = node.DominatorTreeChildren.Count - 1; i >= 0; i--) { 555 | - agenda.Push(node.DominatorTreeChildren[i]); 556 | - } 557 | - } 558 | - 559 | - // Add whatever is left 560 | - foreach(var node in scope) { 561 | - result.Add((ILNode)node.UserData); 562 | - } 563 | - 564 | - return result; 565 | - } 566 | - 567 | - static bool HasSingleEdgeEnteringBlock(ControlFlowNode node) 568 | - { 569 | - return node.Incoming.Count(edge => !node.Dominates(edge.Source)) == 1; 570 | - } 571 | - 572 | - static HashSet FindDominatedNodes(HashSet scope, ControlFlowNode head) 573 | - { 574 | - HashSet agenda = new HashSet(); 575 | - HashSet result = new HashSet(); 576 | - agenda.Add(head); 577 | - 578 | - while(agenda.Count > 0) { 579 | - ControlFlowNode addNode = agenda.First(); 580 | - agenda.Remove(addNode); 581 | - 582 | - if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) { 583 | - for (int i = 0; i < addNode.Outgoing.Count; i++) { 584 | - agenda.Add(addNode.Outgoing[i].Target); 585 | - } 586 | - } 587 | - } 588 | - 589 | - return result; 590 | - } 591 | - 592 | - static HashSet FindLoopContent(HashSet scope, ControlFlowNode head) 593 | - { 594 | - HashSet agenda = new HashSet(); 595 | - for (int i = 0; i < head.Incoming.Count; i++) { 596 | - var p = head.Incoming[i].Source; 597 | - if (head.Dominates(p)) 598 | - agenda.Add(p); 599 | - } 600 | - HashSet result = new HashSet(); 601 | - 602 | - while(agenda.Count > 0) { 603 | - ControlFlowNode addNode = agenda.First(); 604 | - agenda.Remove(addNode); 605 | - 606 | - if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) { 607 | - for (int i = 0; i < addNode.Incoming.Count; i++) 608 | - agenda.Add(addNode.Incoming[i].Source); 609 | - } 610 | - } 611 | - if (scope.Contains(head)) 612 | - result.Add(head); 613 | - 614 | - return result; 615 | - } 616 | - } 617 | +namespace ICSharpCode.Decompiler.ILAst 618 | +{ 619 | + /// 620 | + /// Description of LoopsAndConditions. 621 | + /// 622 | + public class LoopsAndConditions 623 | + { 624 | + readonly Dictionary labelToCfNode = new Dictionary(); 625 | + 626 | + DecompilerContext context; 627 | + 628 | + uint nextLabelIndex; 629 | + 630 | + public LoopsAndConditions(DecompilerContext context) 631 | + { 632 | + Initialize(context); 633 | + } 634 | + 635 | + public void Initialize(DecompilerContext context) 636 | + { 637 | + this.context = context; 638 | + this.labelToCfNode.Clear(); 639 | + this.nextLabelIndex = 0; 640 | + } 641 | + 642 | + public void FindLoops(ILBlock block) 643 | + { 644 | + if (block.Body.Count > 0) 645 | + { 646 | + ControlFlowGraph graph; 647 | + graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); 648 | + graph.ComputeDominance(context.CancellationToken); 649 | + graph.ComputeDominanceFrontier(); 650 | + //TODO: Keep BinSpans when writing to Body 651 | + block.Body = FindLoops(new HashSet(graph.Nodes.Skip(3)), graph.EntryPoint, false); 652 | + } 653 | + } 654 | + 655 | + public void FindConditions(ILBlock block) 656 | + { 657 | + if (block.Body.Count > 0) 658 | + { 659 | + ControlFlowGraph graph; 660 | + graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); 661 | + graph.ComputeDominance(context.CancellationToken); 662 | + graph.ComputeDominanceFrontier(); 663 | + //TODO: Keep BinSpans when writing to Body 664 | + block.Body = FindConditions(new HashSet(graph.Nodes.Skip(3)), graph.EntryPoint); 665 | + } 666 | + } 667 | + 668 | + public bool Fixup(ILBlock method, ILBlock block) 669 | + { 670 | + var modified = false; 671 | + 672 | + if (block.Body.Count > 0) 673 | + { 674 | + ControlFlowGraph graph; 675 | + graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); 676 | + graph.ComputeDominance(context.CancellationToken); 677 | + graph.ComputeDominanceFrontier(); 678 | + //TODO: Keep BinSpans when writing to Body 679 | + 680 | + var newbody = new List(); 681 | + foreach (var node in graph.Nodes.Skip(3)) 682 | + { 683 | + if (node.Incoming.Count > 0) 684 | + { 685 | + var bb = node.UserData as ILBasicBlock; 686 | + 687 | + if (bb.Body.Count >= 3 && bb.Body[bb.Body.Count - 2].Match(ILCode.Brtrue)) 688 | + { 689 | + var bexp = (bb.Body[bb.Body.Count - 2] as ILExpression); 690 | + var target = bexp.Operand as ILLabel; 691 | + 692 | + if (bexp.Arguments[0].Match(ILCode.Ldloc) || (bexp.Arguments[0].Match(ILCode.LogicNot) && bexp.Arguments[0].Arguments[0].Match(ILCode.Ldloc)) ) 693 | + { 694 | + ILVariable loc; 695 | + if (bexp.Arguments[0].Match(ILCode.Ldloc)) 696 | + loc = bexp.Arguments[0].Operand as ILVariable; 697 | + else 698 | + loc = bexp.Arguments[0].Arguments[0].Operand as ILVariable; 699 | + 700 | + if (loc != null) 701 | + { 702 | + ILVariable oloc; 703 | + var uses = method.GetSelfAndChildrenRecursive(e => (e.Match(ILCode.Stloc, out oloc) || e.Match(ILCode.Ldloc, out oloc)) && oloc == loc); 704 | + 705 | + if (uses.Count == 1) 706 | + { 707 | + bb.Body.RemoveAt(bb.Body.Count - 1); 708 | + bb.Body.RemoveAt(bb.Body.Count - 1); 709 | + bb.Body.Add(new ILExpression(ILCode.Br, target)); 710 | + modified = true; 711 | + } 712 | + } 713 | + } 714 | + } 715 | + 716 | + newbody.Add(bb); 717 | + } 718 | + else { modified = true; } 719 | + } 720 | + 721 | + block.Body = newbody; 722 | + } 723 | + 724 | + return modified; 725 | + } 726 | + 727 | + internal bool NeedsDeobf(ILBlock method, AutoPropertyProvider autoPropertyProvider) 728 | + { 729 | + var yrt = YieldReturnDecompiler.YieldReturnType(context, method); 730 | + if (yrt != null) 731 | + { 732 | + foreach (var m in MethodUtils.GetMethod_Dispose(yrt).Concat(MethodUtils.GetMethod_GetEnumerator(yrt).Concat(MethodUtils.GetMethod_get_Current(yrt).Concat(MethodUtils.GetMethod_MoveNext(yrt))))) 733 | + { 734 | + if (m != null && m.HasBody) 735 | + { 736 | + ILBlock ilMethod = new ILBlock(CodeBracesRangeFlags.MethodBraces); 737 | + 738 | + var astBuilder = context.Cache.GetILAstBuilder(); 739 | + try 740 | + { 741 | + ilMethod.Body = astBuilder.Build(m, true, context); 742 | + } 743 | + finally 744 | + { 745 | + context.Cache.Return(astBuilder); 746 | + } 747 | + 748 | + var optimizer = this.context.Cache.GetILAstOptimizer(); 749 | + try 750 | + { 751 | + optimizer.Optimize(context, ilMethod, autoPropertyProvider, ILAstOptimizationStep.PropertyAccessInstructions); // obviously we dont want to deobfuscate it when we test if it needs deobfuscation.. 752 | + } 753 | + finally 754 | + { 755 | + this.context.Cache.Return(optimizer); 756 | + } 757 | + 758 | + if (NeedsDeobfImpl(ilMethod)) 759 | + return true; 760 | + } 761 | + } 762 | + } 763 | + 764 | + return NeedsDeobfImpl(method); 765 | + } 766 | + 767 | + private bool NeedsDeobfImpl(ILBlock method) 768 | + { 769 | + foreach (var block in method.GetSelfAndChildrenRecursive()) 770 | + { 771 | + var body = block.Body; 772 | + for (int i = 0; i < body.Count; i++) 773 | + { 774 | + if (body[i].Match(ILCode.Switch) && i >= 2) 775 | + { 776 | + var swexp = body[i] as ILExpression; 777 | + if (swexp.Arguments[0].Match(ILCode.Rem_Un)) 778 | + { 779 | + var nexp = swexp.Arguments[0].Arguments[0]; 780 | + if (nexp.Operand == null) // switch without store 781 | + { 782 | + if (nexp.Match(ILCode.Xor)) 783 | + return true; 784 | + } 785 | + 786 | + if (body[i - 1].Match(ILCode.Stloc) && body[i - 2].Match(ILCode.Stloc)) 787 | + { 788 | + if ((body[i - 2] as ILExpression).Arguments[0].Match(ILCode.Xor) && (body[i - 2] as ILExpression).Operand == nexp.Operand) 789 | + return true; 790 | + } 791 | + 792 | + } 793 | + } 794 | + } 795 | + } 796 | + 797 | + return false; 798 | + } 799 | + 800 | + public void Deobf(ILBlock block) 801 | + { 802 | + if (block.Body.Count > 0) 803 | + { 804 | + var nums = new HashSet(); // locals 805 | + var args = new HashSet(); // generated stack variables 806 | + 807 | + // cfo switches with stloc 808 | + // FIXME: context.CurrentMethod.Body.Variables isnt accurate after yield return transformation! 809 | + //foreach (var num_candidate in context.CurrentMethod.Body.Variables.Where(v => v.Type.FullName == "System.UInt32" && string.IsNullOrEmpty(v.Name))) // "num" candidate 810 | + { 811 | + // consideration: does this GetSelfAndChildrenRecursive get things not in scope? the whole scope thing still seems a little magical. 812 | + foreach (var the_switch in block.GetSelfAndChildrenRecursive(e => e.Match(ILCode.Switch) && e.Arguments[0].Match(ILCode.Rem_Un) 813 | + && e.Arguments[0].Arguments[0].Match(ILCode.Stloc) && e.Arguments[0].Arguments[0].Operand is ILVariable)) // && (e.Arguments[0].Arguments[0].Operand as ILVariable).OriginalVariable == num_candidate)) 814 | + { 815 | + var num_ilvar = the_switch.Arguments[0].Arguments[0].Operand as ILVariable; 816 | + 817 | + // catch: with more than one switch, these get "split" by a previous step (SplitVariables). thus we check .OriginalVariable 818 | + // I believe this is what I need to do everywhere. Basically what this means is that it uses the same variable, but it gets re-initialized 819 | + // you can see it in the generated c#, the thing is re-declared in every (c#) block 820 | + // in IL, I cannot see it being zeroed implicitly, but it can just get assigned over. 821 | + // since I'm eliminating them anyways, I probably dont need to take too much care, except to possibly remove all of its spawns from the locals if necessary 822 | + // i reflect this by initializing all of these as 0, which should work as expected. 823 | + if (nums.Any() && nums.First().OriginalVariable != num_ilvar.OriginalVariable) // there can be only one! 824 | + { 825 | + System.Diagnostics.Debugger.Break(); 826 | + return; 827 | + } 828 | + 829 | + nums.Add(num_ilvar); 830 | + 831 | + var switch_reads = the_switch.GetSelfAndChildrenRecursive(e => e.Match(ILCode.Xor) && e.Arguments[0].Operand is ILVariable && (e.Arguments[0].Operand as ILVariable).GeneratedByDecompiler && (e.Arguments[0].Operand as ILVariable).Type.FullName == "System.Int32"); 832 | + if (switch_reads.Count == 1) 833 | + { 834 | + var arg = switch_reads.Single().Arguments[0].Operand as ILVariable; 835 | + args.Add(arg); 836 | + } 837 | + } 838 | + } 839 | + 840 | + // cfo switches without stloc 841 | + // switch([labels..], rem.un:int32(xor:int32(arg_1A_0:int32, ldc.i4:int32(1931050559)), ldc.i4:int32(4))) 842 | + foreach (var the_switch in block.GetSelfAndChildrenRecursive(e => e.Match(ILCode.Switch) && e.Arguments[0].Match(ILCode.Rem_Un) && e.Arguments[0].Arguments[0].Match(ILCode.Xor) && 843 | + e.Arguments[0].Arguments[0].Arguments[0].Operand is ILVariable)) 844 | + { 845 | + var arg = the_switch.Arguments[0].Arguments[0].Arguments[0].Operand as ILVariable; 846 | + System.Diagnostics.Debug.Assert(arg.GeneratedByDecompiler); // this has to be a stack variable, otherwise it would be the other form of switch 847 | + 848 | + args.Add(arg); 849 | + } 850 | + 851 | + if (nums.Any() || args.Any()) 852 | + { 853 | + ControlFlowGraph graph; 854 | + graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); 855 | + graph.ComputeDominance(context.CancellationToken); 856 | + graph.ComputeDominanceFrontier(); 857 | + 858 | + var states = new VarStates(); 859 | + foreach (var v in nums.Concat(args).Distinct()) 860 | + states[v] = 0; 861 | + 862 | + //TODO: Keep BinSpans when writing to Body 863 | + block.Body = Deobf(new HashSet(graph.Nodes.Skip(3)), graph.EntryPoint, false, states); 864 | + } 865 | + } 866 | + } 867 | + 868 | + bool EvalSwitch(VarStates states, ILExpression switch_statement, ILLabel[] caseLabels, ILLabel fallLabel, out ControlFlowNode target, out ILLabel target_label, out VarStates effect) 869 | + { 870 | + var eval = EvalExp(switch_statement, states); 871 | + if (eval.Success) 872 | + { 873 | + effect = eval.Effect; 874 | + if (eval.Value < caseLabels.Length) 875 | + target_label = caseLabels[eval.Value]; 876 | + else 877 | + target_label = fallLabel; 878 | + 879 | + // this used to happen with old yield return decompilation 880 | + if (!labelToCfNode.ContainsKey(target_label)) 881 | + System.Diagnostics.Debugger.Break(); 882 | + 883 | + target = labelToCfNode[target_label]; 884 | + return true; 885 | + } 886 | + 887 | + target_label = null; 888 | + target = null; 889 | + effect = null; 890 | + return false; 891 | + } 892 | + 893 | + // using our trace, we remove unreachable BBs and re-write BBs that branch to a cfo switch to either br to their single, direct decendent or to brtrue br to the two outcomes of their tern. 894 | + List Deobf(HashSet scope, ControlFlowNode entryPoint, bool excludeEntryPoint, VarStates states) 895 | + { 896 | + HashSet cf_switches; 897 | + DefaultDictionary> extra_sw_code; 898 | + var trace = DeobfTrace(scope, entryPoint, states, out cf_switches, out extra_sw_code); 899 | + 900 | + var nodes_hit = trace.Select(t => t.dst).Distinct(); 901 | + 902 | + var new_bbs = new List(); 903 | + foreach (var node in nodes_hit) 904 | + { 905 | + var bb = node.UserData as ILBasicBlock; 906 | + 907 | + if (node.Outgoing.Count == 1 && cf_switches.Contains(node.Outgoing.Single().Target)) 908 | + { 909 | + var real_targets = trace.Where(tn => tn.src == node); 910 | + var tcnt = real_targets.Count(); 911 | + 912 | + // not currently observed 913 | + var extra_code = extra_sw_code[node.Outgoing.Single().Target]; 914 | + 915 | + // 0 or more than 2 targets breaks our assumption 916 | + if (tcnt != 1 && tcnt != 2) 917 | + System.Diagnostics.Debugger.Break(); 918 | + 919 | + bb.Body.RemoveAt(bb.Body.Count - 1); 920 | + 921 | + bb.Body.AddRange(extra_code); 922 | + 923 | + if (tcnt == 1) 924 | + { 925 | + var dbb = real_targets.Single().dst.UserData as ILBasicBlock; 926 | + 927 | + bb.Body.Add(new ILExpression(ILCode.Br, dbb.Body.First())); 928 | + } 929 | + else if (tcnt == 2) 930 | + { 931 | + if (real_targets.First().last_tern == real_targets.Last().last_tern) 932 | + System.Diagnostics.Debugger.Break(); 933 | + 934 | + TraceNode truenode, falsenode; 935 | + 936 | + truenode = real_targets.Single(tn => tn.last_tern == true); 937 | + falsenode = real_targets.Single(tn => tn.last_tern == false); 938 | + 939 | + var ternexp = bb.GetSelfAndChildrenRecursive(e => e.Match(ILCode.TernaryOp)).Single().Arguments[0]; 940 | + 941 | + bb.Body.Add(new ILExpression(ILCode.Brtrue, (truenode.dst.UserData as ILBasicBlock).Body.First(), ternexp)); 942 | + bb.Body.Add(new ILExpression(ILCode.Br, (falsenode.dst.UserData as ILBasicBlock).Body.First())); 943 | + } 944 | + } 945 | + 946 | + // remove CFO variable shenans 947 | + for (int i = bb.Body.Count - 1; i >= 0; i--) 948 | + { 949 | + if (EvalExp(bb.Body[i], states, true).Success) 950 | + bb.Body.RemoveAt(i); 951 | + } 952 | + 953 | + new_bbs.Add(bb); 954 | + } 955 | + 956 | + return new_bbs; 957 | + } 958 | + 959 | + // generate a trace that represents all possible control flow. while building this we use our VarState simulation. 960 | + // we remove the cfo switches from our flow graph, and flatten the resulting edge collection 961 | + List DeobfTrace(HashSet scope, ControlFlowNode entryPoint, VarStates states, out HashSet cf_switches, out DefaultDictionary> extra_sw_code) 962 | + { 963 | + var agenda = new Queue(); 964 | + var visited = new List(); 965 | + 966 | + cf_switches = new HashSet(); 967 | + extra_sw_code = new DefaultDictionary>(_ => new HashSet()); 968 | + 969 | + agenda.Enqueue(new TraceNode(entryPoint, entryPoint.Outgoing.Single().Target, states)); 970 | + while (agenda.Count > 0) 971 | + { 972 | + var aitem = agenda.Dequeue(); 973 | + var node = aitem.dst; 974 | + 975 | + if (visited.Contains(aitem)) 976 | + { 977 | + foreach (var prev in aitem.prevs) 978 | + { 979 | + visited.Single(i => i.Equals(aitem)).prevs.Add(prev); // looks dumb but is correct? 980 | + } 981 | + 982 | + continue; 983 | + } 984 | + 985 | + visited.Add(aitem); 986 | + 987 | + var bb = node.UserData as ILBasicBlock; 988 | + 989 | + // if cfo switch, find and add target 990 | + ILLabel[] caseLabels; 991 | + ILExpression switchArg; 992 | + ILLabel fallLabel; 993 | + if(bb.MatchLastAndBr(ILCode.Switch, out caseLabels, out switchArg, out fallLabel)) 994 | + { 995 | + ControlFlowNode target; 996 | + ILLabel target_label; 997 | + VarStates sw_effect; 998 | + if (EvalSwitch(aitem.states, switchArg, caseLabels, fallLabel, out target, out target_label, out sw_effect)) 999 | + { 1000 | + cf_switches.Add(node); 1001 | + 1002 | + // this does not actually happen, I mistakenly thought it did. either way, I'll leave this here just so it's handled 1003 | + if (bb.Body.Count > 3) // [label:] [switch()] [br] 1004 | + { 1005 | + System.Diagnostics.Debug.WriteLine("CFO SWITCH WITH MORE THAN 3 INSTRUCTIONS: " + this.context.CurrentMethod.FullName.ToString()); 1006 | + for (int i = 1; i < bb.Body.Count - 2; i++) 1007 | + { 1008 | + extra_sw_code[node].Add(bb.Body[i]); 1009 | + System.Diagnostics.Debug.WriteLine("INSTRUCTION " + i + ": " + bb.Body[i].ToString()); 1010 | + } 1011 | + } 1012 | + 1013 | + agenda.Enqueue(new TraceNode(node, target, sw_effect, aitem)); 1014 | + 1015 | + continue; 1016 | + } 1017 | + 1018 | + // else we have non-cfo switch, treat as normal cf 1019 | + } 1020 | + 1021 | + // do cf sim 1022 | + var working_state = new VarStates(aitem.states); 1023 | + var cf_handled = false; 1024 | + foreach (var inst in bb.Body) 1025 | + { 1026 | + // if complicated (trycatch), deobf is out of scope and done as part of a seperate block 1027 | + if (inst is ILTryCatchBlock) 1028 | + break; 1029 | + 1030 | + var terns = inst.GetSelfAndChildrenRecursive(e => e.Match(ILCode.TernaryOp)); 1031 | + 1032 | + if (terns.Any()) 1033 | + { 1034 | + var tern_cond = terns.Single().Arguments[0]; 1035 | + 1036 | + var tern_true_res = EvalExp(inst, working_state, true); 1037 | + var tern_false_res = EvalExp(inst, working_state, false); 1038 | + 1039 | + // if cfo tern, add two items to agenda and ignore rest of body 1040 | + if (tern_true_res.Success && tern_false_res.Success) 1041 | + { 1042 | + agenda.Enqueue(new TraceNode(node, node.Outgoing.Single().Target, tern_true_res.Effect, aitem) { last_tern = true } ); 1043 | + agenda.Enqueue(new TraceNode(node, node.Outgoing.Single().Target, tern_false_res.Effect, aitem)); 1044 | + cf_handled = true; 1045 | + 1046 | + // TODO: if there were more CFO instructions after the tern, i would need to not break here and continue all further CF sim on both tern_true_res and tern_false_res 1047 | + break; 1048 | + } 1049 | + } 1050 | + 1051 | + var eval = EvalExp(inst, working_state); 1052 | + if (eval.Success) 1053 | + working_state = eval.Effect; 1054 | + } 1055 | + 1056 | + // if normal control flow, add outgoing edges 1057 | + if (!cf_handled) 1058 | + foreach (var og in node.Outgoing) 1059 | + agenda.Enqueue(new TraceNode(node, og.Target, working_state, aitem)); 1060 | + } 1061 | + 1062 | + // remove all nodes that come from switch and connect them directly 1063 | + var result = new List(); 1064 | + for (int i = 0; i < visited.Count; i++) 1065 | + { 1066 | + var node = visited[i]; 1067 | + 1068 | + if (cf_switches.Contains(node.src)) // node: switch -> code2 1069 | + { 1070 | + foreach (var prev in node.prevs) // prev: code1 -> switch 1071 | + { 1072 | + result.Add(new TraceNode(prev.src, node.dst, node.states) { last_tern = prev.last_tern }); 1073 | + } 1074 | + } 1075 | + else if (!cf_switches.Contains(node.dst)) // don't add the other part of the two nodes we replaced with one 1076 | + { 1077 | + result.Add(node); 1078 | + } 1079 | + } 1080 | + 1081 | + // there may now be duplicate edges that were previously masked by the switch! 1082 | + // cfo variable states are no longer relevant for equality 1083 | + result = result.Distinct(new TraceNode.StatelessEqualityComparer()).ToList(); 1084 | + 1085 | + return result; 1086 | + } 1087 | + 1088 | + // misnomer: is more of an edge, really 1089 | + private class TraceNode 1090 | + { 1091 | + public class StatelessEqualityComparer : EqualityComparer 1092 | + { 1093 | + public override bool Equals(TraceNode x, TraceNode y) 1094 | + { 1095 | + return x.src == y.src && x.dst == y.dst && x.last_tern == y.last_tern; 1096 | + } 1097 | + 1098 | + public override int GetHashCode(TraceNode obj) 1099 | + { 1100 | + return obj.src.BlockIndex ^ (obj.dst.BlockIndex << 8) ^ (obj.last_tern ? 1 : 0); 1101 | + } 1102 | + } 1103 | + 1104 | + public TraceNode(ControlFlowNode s, ControlFlowNode d, VarStates st, TraceNode p = null) 1105 | + { 1106 | + src = s; dst = d; 1107 | + states = new VarStates(st); 1108 | + 1109 | + if (p != null) 1110 | + prevs.Add(p); 1111 | + } 1112 | + 1113 | + // somewhat whacky predecessor tracking but it does the job 1114 | + public HashSet prevs = new HashSet(); 1115 | + 1116 | + public ControlFlowNode src, dst; 1117 | + public VarStates states; 1118 | + public bool last_tern; 1119 | + 1120 | + public override int GetHashCode() 1121 | + { 1122 | + return states.GetHashCode() ^ src.BlockIndex ^ (dst.BlockIndex << 8) ^ (last_tern ? 1 : 0); 1123 | + } 1124 | + 1125 | + public override bool Equals(object obj) 1126 | + { 1127 | + var other = obj as TraceNode; 1128 | + if (other == null) 1129 | + return false; 1130 | + 1131 | + if(src != other.src || dst != other.dst || last_tern != other.last_tern || !other.states.Equals(states)) 1132 | + return false; 1133 | + 1134 | + return true; 1135 | + } 1136 | + } 1137 | + 1138 | + private EvalResult EvalExp(object expression, VarStates states, bool? tern_solution = null) 1139 | + { 1140 | + var result = new EvalResult(states) { Success = false }; 1141 | + unchecked 1142 | + { 1143 | + if (expression is int) 1144 | + { 1145 | + result.Success = true; 1146 | + result.Value = (int)expression; 1147 | + return result; 1148 | + } 1149 | + if (expression is ILVariable) 1150 | + { 1151 | + if (!states.Vars.ContainsKey(expression as ILVariable)) 1152 | + return result; 1153 | + 1154 | + result.Success = true; 1155 | + result.Value = states[expression as ILVariable]; 1156 | + return result; 1157 | + } 1158 | + if (!(expression is ILExpression)) 1159 | + return result; 1160 | + 1161 | + var exp = expression as ILExpression; 1162 | + 1163 | + if (exp.Arguments.Count == 2 && exp.Operand == null) 1164 | + { 1165 | + var left = EvalExp(exp.Arguments[0], states, tern_solution); 1166 | + if (!left.Success) 1167 | + return result; 1168 | + var right = EvalExp(exp.Arguments[1], left.Effect, tern_solution); 1169 | + 1170 | + if (result.Success = right.Success) 1171 | + { 1172 | + result.Effect = right.Effect; 1173 | + switch(exp.Code) 1174 | + { 1175 | + case ILCode.Xor: 1176 | + result.Value = left.Value ^ right.Value; 1177 | + break; 1178 | + 1179 | + case ILCode.Rem_Un: 1180 | + result.Value = left.Value % right.Value; 1181 | + break; 1182 | + 1183 | + case ILCode.Mul: 1184 | + result.Value = left.Value * right.Value; 1185 | + break; 1186 | + 1187 | + default: 1188 | + result.Effect = null; 1189 | + result.Success = false; 1190 | + break; 1191 | + } 1192 | + } 1193 | + 1194 | + return result; 1195 | + } 1196 | + 1197 | + switch (exp.Code) 1198 | + { 1199 | + case ILCode.TernaryOp: 1200 | + if (tern_solution == null || !tern_solution.HasValue) 1201 | + return result; 1202 | + 1203 | + var tern_result = EvalExp(tern_solution.Value ? exp.Arguments[1] : exp.Arguments[2], states); // currently, we intentionally dont forward the ternary solution so nested terns will fail 1204 | + if (tern_result.Success) 1205 | + { 1206 | + result = tern_result; 1207 | + } 1208 | + 1209 | + return result; 1210 | + 1211 | + case ILCode.Stloc: 1212 | + var operand = exp.Operand as ILVariable; 1213 | + 1214 | + if (!states.Vars.ContainsKey(operand)) 1215 | + return result; 1216 | + 1217 | + var eval_arg = EvalExp(exp.Arguments.Single(), states, tern_solution); 1218 | + if (result.Success = eval_arg.Success) 1219 | + { 1220 | + result.Effect[operand] = eval_arg.Value; 1221 | + result.Value = eval_arg.Value; 1222 | + } 1223 | + return result; 1224 | + 1225 | + case ILCode.Ldloc: 1226 | + case ILCode.Ldc_I4: 1227 | + return EvalExp(exp.Operand, states, tern_solution); 1228 | + } 1229 | + } 1230 | + 1231 | + return result; 1232 | + } 1233 | + 1234 | + private class VarStates 1235 | + { 1236 | + public VarStates() 1237 | + { 1238 | + Vars = new Dictionary(); 1239 | + } 1240 | + 1241 | + public VarStates(VarStates other) 1242 | + { 1243 | + Vars = new Dictionary(other.Vars); 1244 | + } 1245 | + 1246 | + public Dictionary Vars; 1247 | + public int this[ILVariable key] 1248 | + { 1249 | + get 1250 | + { 1251 | + return Vars[key]; 1252 | + } 1253 | + set 1254 | + { 1255 | + Vars[key] = value; 1256 | + } 1257 | + } 1258 | + 1259 | + public override int GetHashCode() 1260 | + { 1261 | + return Vars.Values.Aggregate(0, (a, b) => a ^ b); 1262 | + } 1263 | + 1264 | + public override bool Equals(object obj) 1265 | + { 1266 | + var other = obj as VarStates; 1267 | + if (other == null || other.Vars.Count != Vars.Count) 1268 | + return false; 1269 | + 1270 | + foreach (var mine in Vars) 1271 | + if (!other.Vars.ContainsKey(mine.Key) || other.Vars[mine.Key] != mine.Value) 1272 | + return false; 1273 | + 1274 | + return true; 1275 | + } 1276 | + } 1277 | + 1278 | + private class EvalResult 1279 | + { 1280 | + public EvalResult() { } 1281 | + 1282 | + public EvalResult(VarStates states) 1283 | + { 1284 | + Effect = new VarStates(states); 1285 | + } 1286 | + 1287 | + public bool Success; 1288 | + public int Value; 1289 | + public VarStates Effect; 1290 | + } 1291 | + 1292 | + readonly ControlFlowGraph cached_ControlFlowGraph = new ControlFlowGraph(); 1293 | + ControlFlowGraph BuildGraph(List nodes, ILLabel entryLabel) 1294 | + { 1295 | + cached_ControlFlowGraph.Nodes.Clear(); 1296 | + int index = 0; 1297 | + var cfNodes = cached_ControlFlowGraph.Nodes; 1298 | + ControlFlowNode entryPoint = new ControlFlowNode(index++, 0, ControlFlowNodeType.EntryPoint); 1299 | + cfNodes.Add(entryPoint); 1300 | + ControlFlowNode regularExit = new ControlFlowNode(index++, null, ControlFlowNodeType.RegularExit); 1301 | + cfNodes.Add(regularExit); 1302 | + ControlFlowNode exceptionalExit = new ControlFlowNode(index++, null, ControlFlowNodeType.ExceptionalExit); 1303 | + cfNodes.Add(exceptionalExit); 1304 | + 1305 | + // Create graph nodes 1306 | + labelToCfNode.Clear(); 1307 | + Dictionary astNodeToCfNode = new Dictionary(); 1308 | + List listLabels = null; 1309 | + foreach (ILBasicBlock node in nodes) 1310 | + { 1311 | + ControlFlowNode cfNode = new ControlFlowNode(index++, null, ControlFlowNodeType.Normal); 1312 | + cfNodes.Add(cfNode); 1313 | + astNodeToCfNode[node] = cfNode; 1314 | + cfNode.UserData = node; 1315 | + 1316 | + // Find all contained labels 1317 | + foreach (ILLabel label in node.GetSelfAndChildrenRecursive(listLabels ?? (listLabels = new List()))) 1318 | + { 1319 | + labelToCfNode[label] = cfNode; 1320 | + } 1321 | + } 1322 | + 1323 | + // Entry endge 1324 | + ControlFlowNode entryNode = labelToCfNode[entryLabel]; 1325 | + ControlFlowEdge entryEdge = new ControlFlowEdge(entryPoint, entryNode, JumpType.Normal); 1326 | + entryPoint.Outgoing.Add(entryEdge); 1327 | + entryNode.Incoming.Add(entryEdge); 1328 | + 1329 | + // Create edges 1330 | + List listExpressions = null; 1331 | + foreach (ILBasicBlock node in nodes) 1332 | + { 1333 | + ControlFlowNode source = astNodeToCfNode[node]; 1334 | + 1335 | + // Find all branches 1336 | + foreach (ILLabel target in node.GetSelfAndChildrenRecursive(listExpressions ?? (listExpressions = new List()), e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) 1337 | + { 1338 | + ControlFlowNode destination; 1339 | + // Labels which are out of out scope will not be in the collection 1340 | + // Insert self edge only if we are sure we are a loop 1341 | + if (labelToCfNode.TryGetValue(target, out destination) && (destination != source || target == node.Body.FirstOrDefault())) 1342 | + { 1343 | + ControlFlowEdge edge = new ControlFlowEdge(source, destination, JumpType.Normal); 1344 | + source.Outgoing.Add(edge); 1345 | + destination.Incoming.Add(edge); 1346 | + } 1347 | + } 1348 | + } 1349 | + 1350 | + return cached_ControlFlowGraph; 1351 | + } 1352 | + 1353 | + List FindLoops(HashSet scope, ControlFlowNode entryPoint, bool excludeEntryPoint) 1354 | + { 1355 | + List result = new List(); 1356 | + 1357 | + // Do not modify entry data 1358 | + scope = new HashSet(scope); 1359 | + 1360 | + Queue agenda = new Queue(); 1361 | + agenda.Enqueue(entryPoint); 1362 | + while (agenda.Count > 0) 1363 | + { 1364 | + ControlFlowNode node = agenda.Dequeue(); 1365 | + 1366 | + // If the node is a loop header 1367 | + if (scope.Contains(node) 1368 | + && node.DominanceFrontier.Contains(node) 1369 | + && (node != entryPoint || !excludeEntryPoint)) 1370 | + { 1371 | + HashSet loopContents = FindLoopContent(scope, node); 1372 | + 1373 | + // If the first expression is a loop condition 1374 | + ILBasicBlock basicBlock = (ILBasicBlock)node.UserData; 1375 | + ILExpression condExpr; 1376 | + ILLabel trueLabel; 1377 | + ILLabel falseLabel; 1378 | + // It has to be just brtrue - any preceding code would introduce goto 1379 | + if (basicBlock.MatchSingleAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) 1380 | + { 1381 | + ControlFlowNode trueTarget; 1382 | + labelToCfNode.TryGetValue(trueLabel, out trueTarget); 1383 | + ControlFlowNode falseTarget; 1384 | + labelToCfNode.TryGetValue(falseLabel, out falseTarget); 1385 | + 1386 | + // If one point inside the loop and the other outside 1387 | + if ((!loopContents.Contains(trueTarget) && loopContents.Contains(falseTarget)) || 1388 | + (loopContents.Contains(trueTarget) && !loopContents.Contains(falseTarget))) 1389 | + { 1390 | + loopContents.RemoveOrThrow(node); 1391 | + scope.RemoveOrThrow(node); 1392 | + 1393 | + // If false means enter the loop 1394 | + if (loopContents.Contains(falseTarget) || falseTarget == node) 1395 | + { 1396 | + // Negate the condition 1397 | + condExpr = new ILExpression(ILCode.LogicNot, null, condExpr); 1398 | + ILLabel tmp = trueLabel; 1399 | + trueLabel = falseLabel; 1400 | + falseLabel = tmp; 1401 | + } 1402 | + 1403 | + ControlFlowNode postLoopTarget; 1404 | + labelToCfNode.TryGetValue(falseLabel, out postLoopTarget); 1405 | + if (postLoopTarget != null) 1406 | + { 1407 | + // Pull more nodes into the loop 1408 | + HashSet postLoopContents = FindDominatedNodes(scope, postLoopTarget); 1409 | + var pullIn = scope.Except(postLoopContents).Where(n => node.Dominates(n)); 1410 | + loopContents.UnionWith(pullIn); 1411 | + } 1412 | + 1413 | + // Use loop to implement the brtrue 1414 | + var tail = basicBlock.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); 1415 | + ILWhileLoop whileLoop; 1416 | + basicBlock.Body.Add(whileLoop = new ILWhileLoop() 1417 | + { 1418 | + Condition = condExpr, 1419 | + BodyBlock = new ILBlock(CodeBracesRangeFlags.LoopBraces) 1420 | + { 1421 | + EntryGoto = new ILExpression(ILCode.Br, trueLabel), 1422 | + Body = FindLoops(loopContents, node, false) 1423 | + } 1424 | + }); 1425 | + if (context.CalculateBinSpans) 1426 | + { 1427 | + whileLoop.BinSpans.AddRange(tail[0].BinSpans); // no recursive add 1428 | + tail[1].AddSelfAndChildrenRecursiveBinSpans(whileLoop.BinSpans); 1429 | + } 1430 | + basicBlock.Body.Add(new ILExpression(ILCode.Br, falseLabel)); 1431 | + result.Add(basicBlock); 1432 | + 1433 | + scope.ExceptWith(loopContents); 1434 | + } 1435 | + } 1436 | + 1437 | + // Fallback method: while(true) 1438 | + if (scope.Contains(node)) 1439 | + { 1440 | + result.Add(new ILBasicBlock() 1441 | + { 1442 | + Body = new List() { 1443 | + new ILLabel() { Name = "Loop_" + (nextLabelIndex++).ToString() }, 1444 | + new ILWhileLoop() { 1445 | + BodyBlock = new ILBlock(CodeBracesRangeFlags.LoopBraces) { 1446 | + EntryGoto = new ILExpression(ILCode.Br, (ILLabel)basicBlock.Body.First()), 1447 | + Body = FindLoops(loopContents, node, true) 1448 | + } 1449 | + }, 1450 | + }, 1451 | + }); 1452 | + 1453 | + scope.ExceptWith(loopContents); 1454 | + } 1455 | + } 1456 | + 1457 | + // Using the dominator tree should ensure we find the the widest loop first 1458 | + foreach (var child in node.DominatorTreeChildren) 1459 | + { 1460 | + agenda.Enqueue(child); 1461 | + } 1462 | + } 1463 | + 1464 | + // Add whatever is left 1465 | + foreach (var node in scope) 1466 | + { 1467 | + result.Add((ILNode)node.UserData); 1468 | + } 1469 | + scope.Clear(); 1470 | + 1471 | + return result; 1472 | + } 1473 | + 1474 | + List FindConditions(HashSet scope, ControlFlowNode entryNode) 1475 | + { 1476 | + List result = new List(); 1477 | + 1478 | + // Do not modify entry data 1479 | + scope = new HashSet(scope); 1480 | + 1481 | + Stack agenda = new Stack(); 1482 | + agenda.Push(entryNode); 1483 | + while (agenda.Count > 0) 1484 | + { 1485 | + ControlFlowNode node = agenda.Pop(); 1486 | + 1487 | + // Find a block that represents a simple condition 1488 | + if (scope.Contains(node)) 1489 | + { 1490 | + 1491 | + ILBasicBlock block = (ILBasicBlock)node.UserData; 1492 | + 1493 | + { 1494 | + // Switch 1495 | + ILLabel[] caseLabels; 1496 | + ILExpression switchArg; 1497 | + ILLabel fallLabel; 1498 | + if (block.MatchLastAndBr(ILCode.Switch, out caseLabels, out switchArg, out fallLabel)) 1499 | + { 1500 | + 1501 | + // Replace the switch code with ILSwitch 1502 | + ILSwitch ilSwitch = new ILSwitch() { Condition = switchArg }; 1503 | + var tail = block.Body.RemoveTail(ILCode.Switch, ILCode.Br); 1504 | + if (context.CalculateBinSpans) 1505 | + { 1506 | + ilSwitch.BinSpans.AddRange(tail[0].BinSpans); // no recursive add 1507 | + tail[1].AddSelfAndChildrenRecursiveBinSpans(ilSwitch.BinSpans); 1508 | + } 1509 | + block.Body.Add(ilSwitch); 1510 | + block.Body.Add(new ILExpression(ILCode.Br, fallLabel)); 1511 | + result.Add(block); 1512 | + 1513 | + // Remove the item so that it is not picked up as content 1514 | + scope.RemoveOrThrow(node); 1515 | + 1516 | + // Find the switch offset 1517 | + int addValue = 0; 1518 | + List subArgs; 1519 | + if (ilSwitch.Condition.Match(ILCode.Sub, out subArgs) && subArgs[1].Match(ILCode.Ldc_I4, out addValue)) 1520 | + { 1521 | + var old = ilSwitch.Condition; 1522 | + ilSwitch.Condition = subArgs[0]; 1523 | + if (context.CalculateBinSpans) 1524 | + { 1525 | + ilSwitch.Condition.BinSpans.AddRange(old.BinSpans); // no recursive add 1526 | + for (int i = 1; i < subArgs.Count; i++) 1527 | + subArgs[i].AddSelfAndChildrenRecursiveBinSpans(ilSwitch.Condition.BinSpans); 1528 | + } 1529 | + } 1530 | + 1531 | + // Pull in code of cases 1532 | + ControlFlowNode fallTarget = null; 1533 | + labelToCfNode.TryGetValue(fallLabel, out fallTarget); 1534 | + 1535 | + HashSet frontiers = new HashSet(); 1536 | + if (fallTarget != null) 1537 | + frontiers.UnionWith(fallTarget.DominanceFrontier.Except(new[] { fallTarget })); 1538 | + 1539 | + foreach (ILLabel condLabel in caseLabels) 1540 | + { 1541 | + ControlFlowNode condTarget; 1542 | + labelToCfNode.TryGetValue(condLabel, out condTarget); 1543 | + if (condTarget != null) 1544 | + frontiers.UnionWith(condTarget.DominanceFrontier.Except(new[] { condTarget })); 1545 | + } 1546 | + 1547 | + bool includedDefault = false; 1548 | + for (int i = 0; i < caseLabels.Length; i++) 1549 | + { 1550 | + ILLabel condLabel = caseLabels[i]; 1551 | + 1552 | + // Find or create new case block 1553 | + ILSwitch.CaseBlock caseBlock = ilSwitch.CaseBlocks.FirstOrDefault(b => b.EntryGoto.Operand == condLabel); 1554 | + if (caseBlock == null) 1555 | + { 1556 | + caseBlock = new ILSwitch.CaseBlock() 1557 | + { 1558 | + Values = new List(), 1559 | + EntryGoto = new ILExpression(ILCode.Br, condLabel) 1560 | + }; 1561 | + ilSwitch.CaseBlocks.Add(caseBlock); 1562 | + if (!includedDefault && condLabel == fallLabel) 1563 | + { 1564 | + includedDefault = true; 1565 | + block.Body.RemoveTail(ILCode.Br); 1566 | + caseBlock.Values = null; 1567 | + } 1568 | + 1569 | + ControlFlowNode condTarget = null; 1570 | + labelToCfNode.TryGetValue(condLabel, out condTarget); 1571 | + if (condTarget != null && !frontiers.Contains(condTarget)) 1572 | + { 1573 | + HashSet content = FindDominatedNodes(scope, condTarget); 1574 | + scope.ExceptWith(content); 1575 | + caseBlock.Body.AddRange(FindConditions(content, condTarget)); 1576 | + // Add explicit break which should not be used by default, but the goto removal might decide to use it 1577 | + caseBlock.Body.Add(new ILBasicBlock() 1578 | + { 1579 | + Body = { 1580 | + new ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++).ToString() }, 1581 | + new ILExpression(ILCode.LoopOrSwitchBreak, null) 1582 | + } 1583 | + }); 1584 | + } 1585 | + } 1586 | + caseBlock.Values?.Add(i + addValue); 1587 | + } 1588 | + 1589 | + // Heuristis to determine if we want to use fallthough as default case 1590 | + if (!includedDefault && fallTarget != null && !frontiers.Contains(fallTarget)) 1591 | + { 1592 | + HashSet content = FindDominatedNodes(scope, fallTarget); 1593 | + if (content.Any()) 1594 | + { 1595 | + var caseBlock = new ILSwitch.CaseBlock() { EntryGoto = new ILExpression(ILCode.Br, fallLabel) }; 1596 | + ilSwitch.CaseBlocks.Add(caseBlock); 1597 | + tail = block.Body.RemoveTail(ILCode.Br); 1598 | + if (context.CalculateBinSpans) 1599 | + tail[0].AddSelfAndChildrenRecursiveBinSpans(caseBlock.BinSpans); 1600 | + 1601 | + scope.ExceptWith(content); 1602 | + caseBlock.Body.AddRange(FindConditions(content, fallTarget)); 1603 | + // Add explicit break which should not be used by default, but the goto removal might decide to use it 1604 | + caseBlock.Body.Add(new ILBasicBlock() 1605 | + { 1606 | + Body = { 1607 | + new ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++).ToString() }, 1608 | + new ILExpression(ILCode.LoopOrSwitchBreak, null) 1609 | + } 1610 | + }); 1611 | + } 1612 | + } 1613 | + } 1614 | + 1615 | + // Two-way branch 1616 | + ILExpression condExpr; 1617 | + ILLabel trueLabel; 1618 | + ILLabel falseLabel; 1619 | + if (block.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) 1620 | + { 1621 | + 1622 | + // Swap bodies since that seems to be the usual C# order 1623 | + ILLabel temp = trueLabel; 1624 | + trueLabel = falseLabel; 1625 | + falseLabel = temp; 1626 | + condExpr = new ILExpression(ILCode.LogicNot, null, condExpr); 1627 | + 1628 | + // Convert the brtrue to ILCondition 1629 | + ILCondition ilCond = new ILCondition() 1630 | + { 1631 | + Condition = condExpr, 1632 | + TrueBlock = new ILBlock(CodeBracesRangeFlags.ConditionalBraces) { EntryGoto = new ILExpression(ILCode.Br, trueLabel) }, 1633 | + FalseBlock = new ILBlock(CodeBracesRangeFlags.ConditionalBraces) { EntryGoto = new ILExpression(ILCode.Br, falseLabel) } 1634 | + }; 1635 | + var tail = block.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); 1636 | + if (context.CalculateBinSpans) 1637 | + { 1638 | + condExpr.BinSpans.AddRange(tail[0].BinSpans); // no recursive add 1639 | + tail[1].AddSelfAndChildrenRecursiveBinSpans(ilCond.FalseBlock.BinSpans); 1640 | + } 1641 | + block.Body.Add(ilCond); 1642 | + result.Add(block); 1643 | + 1644 | + // Remove the item immediately so that it is not picked up as content 1645 | + scope.RemoveOrThrow(node); 1646 | + 1647 | + ControlFlowNode trueTarget = null; 1648 | + labelToCfNode.TryGetValue(trueLabel, out trueTarget); 1649 | + ControlFlowNode falseTarget = null; 1650 | + labelToCfNode.TryGetValue(falseLabel, out falseTarget); 1651 | + 1652 | + // Pull in the conditional code 1653 | + if (trueTarget != null && HasSingleEdgeEnteringBlock(trueTarget)) 1654 | + { 1655 | + HashSet content = FindDominatedNodes(scope, trueTarget); 1656 | + scope.ExceptWith(content); 1657 | + ilCond.TrueBlock.Body.AddRange(FindConditions(content, trueTarget)); 1658 | + } 1659 | + if (falseTarget != null && HasSingleEdgeEnteringBlock(falseTarget)) 1660 | + { 1661 | + HashSet content = FindDominatedNodes(scope, falseTarget); 1662 | + scope.ExceptWith(content); 1663 | + ilCond.FalseBlock.Body.AddRange(FindConditions(content, falseTarget)); 1664 | + } 1665 | + } 1666 | + } 1667 | + 1668 | + // Add the node now so that we have good ordering 1669 | + if (scope.Contains(node)) 1670 | + { 1671 | + result.Add((ILNode)node.UserData); 1672 | + scope.Remove(node); 1673 | + } 1674 | + } 1675 | + 1676 | + // depth-first traversal of dominator tree 1677 | + for (int i = node.DominatorTreeChildren.Count - 1; i >= 0; i--) 1678 | + { 1679 | + agenda.Push(node.DominatorTreeChildren[i]); 1680 | + } 1681 | + } 1682 | + 1683 | + // Add whatever is left 1684 | + foreach (var node in scope) 1685 | + { 1686 | + result.Add((ILNode)node.UserData); 1687 | + } 1688 | + 1689 | + return result; 1690 | + } 1691 | + 1692 | + static bool HasSingleEdgeEnteringBlock(ControlFlowNode node) 1693 | + { 1694 | + return node.Incoming.Count(edge => !node.Dominates(edge.Source)) == 1; 1695 | + } 1696 | + 1697 | + static HashSet FindDominatedNodes(HashSet scope, ControlFlowNode head) 1698 | + { 1699 | + HashSet agenda = new HashSet(); 1700 | + HashSet result = new HashSet(); 1701 | + agenda.Add(head); 1702 | + 1703 | + while (agenda.Count > 0) 1704 | + { 1705 | + ControlFlowNode addNode = agenda.First(); 1706 | + agenda.Remove(addNode); 1707 | + 1708 | + if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) 1709 | + { 1710 | + for (int i = 0; i < addNode.Outgoing.Count; i++) 1711 | + { 1712 | + agenda.Add(addNode.Outgoing[i].Target); 1713 | + } 1714 | + } 1715 | + } 1716 | + 1717 | + return result; 1718 | + } 1719 | + 1720 | + static HashSet FindLoopContent(HashSet scope, ControlFlowNode head) 1721 | + { 1722 | + HashSet agenda = new HashSet(); 1723 | + for (int i = 0; i < head.Incoming.Count; i++) 1724 | + { 1725 | + var p = head.Incoming[i].Source; 1726 | + if (head.Dominates(p)) 1727 | + agenda.Add(p); 1728 | + } 1729 | + HashSet result = new HashSet(); 1730 | + 1731 | + while (agenda.Count > 0) 1732 | + { 1733 | + ControlFlowNode addNode = agenda.First(); 1734 | + agenda.Remove(addNode); 1735 | + 1736 | + if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) 1737 | + { 1738 | + for (int i = 0; i < addNode.Incoming.Count; i++) 1739 | + agenda.Add(addNode.Incoming[i].Source); 1740 | + } 1741 | + } 1742 | + if (scope.Contains(head)) 1743 | + result.Add(head); 1744 | + 1745 | + return result; 1746 | + } 1747 | + } 1748 | } 1749 | diff --git a/ICSharpCode.Decompiler/ILAst/MonoYieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/MonoYieldReturnDecompiler.cs 1750 | index 93cb81d..567fa7f 100644 1751 | --- a/ICSharpCode.Decompiler/ILAst/MonoYieldReturnDecompiler.cs 1752 | +++ b/ICSharpCode.Decompiler/ILAst/MonoYieldReturnDecompiler.cs 1753 | @@ -34,6 +34,7 @@ sealed class MonoYieldReturnDecompiler : YieldReturnDecompiler { 1754 | var yrd = new MonoYieldReturnDecompiler(context, autoPropertyProvider); 1755 | if (!yrd.MatchEnumeratorCreationPattern(method)) 1756 | return null; 1757 | + 1758 | yrd.enumeratorType = yrd.enumeratorCtor.DeclaringType; 1759 | return yrd; 1760 | } 1761 | @@ -49,8 +50,12 @@ sealed class MonoYieldReturnDecompiler : YieldReturnDecompiler { 1762 | 1763 | int i = 0; 1764 | 1765 | - // Check if it could be an IEnumerator method with just a yield break in it 1766 | - if (body.Count == 1) { 1767 | + // super sketchy, but if the enumerator creation is actually long it does get obfuscated and we have some relict branches that havent been removed yet.. 1768 | + if (body.Count >= 1 && body.First() is ILBasicBlock) 1769 | + body = body.Cast().SelectMany(n => n.Body.Where(nn => !(nn is ILLabel) && !(nn.Match(ILCode.Br)))).ToList(); 1770 | + 1771 | + // Check if it could be an IEnumerator method with just a yield break in it 1772 | + if (body.Count == 1) { 1773 | if (!body[i].Match(ILCode.Ret, out newobj)) 1774 | return false; 1775 | enumVar = null; 1776 | @@ -68,11 +73,11 @@ sealed class MonoYieldReturnDecompiler : YieldReturnDecompiler { 1777 | if (!IsCompilerGeneratorEnumerator(enumeratorCtor.DeclaringType)) 1778 | return false; 1779 | 1780 | - if (method.Body.Count == 1) 1781 | + if (body.Count == 1) 1782 | return true; 1783 | 1784 | i++; 1785 | - if (!InitializeFieldToParameterMap(method, enumVar, ref i)) 1786 | + if (!InitializeFieldToParameterMap(body, enumVar, ref i, body.Count)) 1787 | return false; 1788 | 1789 | ILExpression ldloc; 1790 | @@ -108,7 +113,14 @@ sealed class MonoYieldReturnDecompiler : YieldReturnDecompiler { 1791 | protected override void AnalyzeCtor() { 1792 | ILBlock method = CreateILAst(enumeratorCtor); 1793 | var body = method.Body; 1794 | - if (body.Count != 2) 1795 | + 1796 | + if (body.Count < 1) 1797 | + throw new SymbolicAnalysisFailedException(); 1798 | + 1799 | + if (body[0] is ILBasicBlock) 1800 | + body = (body[0] as ILBasicBlock).Body.SkipWhile(n => n is ILLabel).ToList(); 1801 | + 1802 | + if (body.Count != 2) 1803 | throw new SymbolicAnalysisFailedException(); 1804 | IMethod m; 1805 | ILExpression ldthis; 1806 | @@ -123,6 +135,8 @@ sealed class MonoYieldReturnDecompiler : YieldReturnDecompiler { 1807 | } 1808 | 1809 | void InitializeDisposeMethod(ILBlock method) { 1810 | + method.Body = DeBB(method); 1811 | + 1812 | FieldDef localStateField = null; 1813 | foreach (var n in method.Body) { 1814 | var expr = n as ILExpression; 1815 | @@ -178,15 +192,20 @@ sealed class MonoYieldReturnDecompiler : YieldReturnDecompiler { 1816 | protected override void AnalyzeMoveNext() { 1817 | var moveNextMethod = MethodUtils.GetMethod_MoveNext(enumeratorType).FirstOrDefault(); 1818 | var ilMethod = CreateILAst(moveNextMethod); 1819 | - var body = ilMethod.Body; 1820 | + 1821 | + var body = DeBB(ilMethod); 1822 | if (body.Count == 0) 1823 | throw new SymbolicAnalysisFailedException(); 1824 | 1825 | // If it's an IEnumerator method with just a yield break in it, we haven't found the state field yet 1826 | if (stateField == null) { 1827 | - const int index = 1; 1828 | + int index = 1; 1829 | IField f; 1830 | List args; 1831 | + 1832 | + while (index < body.Count && body[index] is ILLabel) 1833 | + index++; 1834 | + 1835 | if (index + 1 >= body.Count || !body[index].Match(ILCode.Stfld, out f, out args) || args.Count != 2 || !args[0].MatchThis() || !args[1].MatchLdcI4(-1)) 1836 | throw new SymbolicAnalysisFailedException(); 1837 | var field = GetFieldDefinition(f); 1838 | @@ -197,68 +216,148 @@ sealed class MonoYieldReturnDecompiler : YieldReturnDecompiler { 1839 | 1840 | disposeInFinallyVar = MonoStateMachineUtils.FindDisposeLocal(ilMethod); 1841 | 1842 | - int bodyLength; 1843 | - if (!FindReturnLabels(body, out bodyLength, out returnFalseLabel, out returnTrueLabel)) 1844 | + List strippedbody; 1845 | + int bodyLength; 1846 | + bool labelsfound; 1847 | + 1848 | + if (ilMethod.Body != body) // if BB shenanigans happened 1849 | + { 1850 | + labelsfound = FindReturnLabelsBB(ilMethod.Body, out returnFalseLabel, out returnTrueLabel, out strippedbody); 1851 | + bodyLength = strippedbody.Count; 1852 | + } 1853 | + else 1854 | + { 1855 | + labelsfound = FindReturnLabels(body, out bodyLength, out returnFalseLabel, out returnTrueLabel); 1856 | + strippedbody = body; 1857 | + } 1858 | + 1859 | + if (!labelsfound) 1860 | throw new SymbolicAnalysisFailedException(); 1861 | 1862 | - var rangeAnalysis = new MonoStateRangeAnalysis(body[0], StateRangeAnalysisMode.IteratorMoveNext, stateField, disposingField, disposeInFinallyVar); 1863 | - int pos = rangeAnalysis.AssignStateRanges(body, bodyLength); 1864 | - rangeAnalysis.EnsureLabelAtPos(body, ref pos, ref bodyLength); 1865 | - 1866 | - labels = rangeAnalysis.CreateLabelRangeMapping(body, pos, bodyLength); 1867 | + // this whole thing doesnt handle a few scenarioes I encountered - some not even obfuscated. 1868 | + // example: 1869 | + // switch (statefield, ..) 1870 | + // ret 0 <- default case of the switch, throws the original off since it demands a return 0 label 1871 | + // [body] 1872 | + // [last_instruction_falls_through_to_retOneLabel_without_branch] // <- expects a br retOneLabel here 1873 | + // retOneLabel: 1874 | + // ret 1 1875 | + // 1876 | + // this seems hard to fix without a complete rewrite of the yrd/staterangeanalysis. 1877 | + var rangeAnalysis = new MonoStateRangeAnalysis(strippedbody[0], StateRangeAnalysisMode.IteratorMoveNext, stateField, disposingField, disposeInFinallyVar); 1878 | + int pos = rangeAnalysis.AssignStateRanges(strippedbody, bodyLength); 1879 | + rangeAnalysis.EnsureLabelAtPos(strippedbody, ref pos, ref bodyLength); 1880 | + 1881 | + labels = rangeAnalysis.CreateLabelRangeMapping(strippedbody, pos, bodyLength); 1882 | stateVariables = rangeAnalysis.StateVariables; 1883 | - ConvertBody(body, pos, bodyLength); 1884 | - } 1885 | + ConvertBody(strippedbody, pos, bodyLength); 1886 | + } 1887 | List stateVariables; 1888 | ILLabel returnFalseLabel, returnTrueLabel; 1889 | List> labels; 1890 | ILVariable disposeInFinallyVar; 1891 | 1892 | - bool FindReturnLabels(List body, out int bodyLength, out ILLabel retZeroLabel, out ILLabel retOneLabel) { 1893 | - bodyLength = 0; 1894 | - retZeroLabel = null; 1895 | - retOneLabel = null; 1896 | - // MoveNext ends with 'lbl1: return 0; lbl2: return 1;' or 'lbl1: return 0;' 1897 | - 1898 | - ILLabel lbl; 1899 | - int val; 1900 | - int pos = body.Count - 2; 1901 | - if (!GetReturnValueLabel(body, pos, out lbl, out val)) 1902 | - return false; 1903 | - if (val == 0) { 1904 | - retZeroLabel = lbl; 1905 | - bodyLength = pos; 1906 | - return true; 1907 | - } 1908 | - else if (val == 1) { 1909 | - retOneLabel = lbl; 1910 | - pos -= 2; 1911 | - if (!GetReturnValueLabel(body, pos, out lbl, out val)) 1912 | - return false; 1913 | - if (val != 0) 1914 | - return false; 1915 | - retZeroLabel = lbl; 1916 | - bodyLength = pos; 1917 | - return true; 1918 | - } 1919 | - else 1920 | - return false; 1921 | - } 1922 | - 1923 | - bool GetReturnValueLabel(List body, int pos, out ILLabel lbl, out int val) { 1924 | - lbl = null; 1925 | - val = 0; 1926 | - if (pos < 0) 1927 | - return false; 1928 | - lbl = body[pos] as ILLabel; 1929 | - if (lbl == null) 1930 | - return false; 1931 | - ILExpression ldci4; 1932 | - return body[pos + 1].Match(ILCode.Ret, out ldci4) && 1933 | - ldci4.Match(ILCode.Ldc_I4, out val); 1934 | - } 1935 | - 1936 | - ILExpression CreateYieldReturn(ILExpression arg) => new ILExpression(ILCode.YieldReturn, null, arg); 1937 | + bool FindReturnLabelsBB(List body, out ILLabel retZeroLabel, out ILLabel retOneLabel, out List strippedbody) { 1938 | + retZeroLabel = null; 1939 | + retOneLabel = null; 1940 | + 1941 | + ILExpression retexp; 1942 | + var rbbs = body.Cast().Where(bb => bb.Body.Count == 2 && bb.Body[1].Match(ILCode.Ret, out retexp) && retexp.Match(ILCode.Ldc_I4)).ToList(); 1943 | + var ret_lbls = rbbs.Select(bb => new System.Tuple((int)(bb.Body[1] as ILExpression).Arguments[0].Operand, bb.Body[0] as ILLabel)).ToList(); 1944 | + 1945 | + strippedbody = DeBB(body.Except(rbbs).ToList()); 1946 | + 1947 | + if (ret_lbls.Count() == 1) 1948 | + { 1949 | + if (ret_lbls.Single().Item1 != 0) 1950 | + return false; 1951 | + 1952 | + retZeroLabel = ret_lbls.Single().Item2; 1953 | + return true; 1954 | + } 1955 | + else if (ret_lbls.Count() == 2) 1956 | + { 1957 | + if (ret_lbls.Where(l => l.Item1 == 0).Count() != 1 || ret_lbls.Where(l => l.Item1 == 1).Count() != 1) 1958 | + return false; 1959 | + 1960 | + retZeroLabel = ret_lbls.Single(l => l.Item1 == 0).Item2; 1961 | + retOneLabel = ret_lbls.Single(l => l.Item1 == 1).Item2; 1962 | + return true; 1963 | + } 1964 | + 1965 | + return false; 1966 | + } 1967 | + 1968 | + bool FindReturnLabels(List body, out int bodyLength, out ILLabel retZeroLabel, out ILLabel retOneLabel) 1969 | + { 1970 | + bodyLength = body.Count; 1971 | + retZeroLabel = null; 1972 | + retOneLabel = null; 1973 | + // MoveNext ends with 'lbl1: return 0; lbl2: return 1;' or 'lbl1: return 0;' 1974 | + // ^ except when obfuscated. in our case it still observes the label: return x pattern, but position in the body may vary 1975 | + 1976 | + for (int pos = body.Count - 1; pos > 0; pos--) // skip first instruction 1977 | + { 1978 | + int val; 1979 | + ILLabel lbl; 1980 | + if (GetReturnValueLabel(body, pos, out lbl, out val)) 1981 | + { 1982 | + if (val == 0 && retZeroLabel == null) 1983 | + retZeroLabel = lbl; 1984 | + else if (val == 1 && retOneLabel == null) 1985 | + retOneLabel = lbl; 1986 | + else 1987 | + return false; 1988 | + 1989 | + body.RemoveAt(pos); 1990 | + body.RemoveAt(pos); 1991 | + bodyLength -= 2; 1992 | + } 1993 | + } 1994 | + 1995 | + return retZeroLabel != null || retOneLabel != null; // this may not be proper 1996 | + /*ILLabel lbl; 1997 | + int val; 1998 | + int pos = body.Count - 2; 1999 | + if (!GetReturnValueLabel(body, pos, out lbl, out val)) 2000 | + return false; 2001 | + if (val == 0) 2002 | + { 2003 | + retZeroLabel = lbl; 2004 | + bodyLength = pos; 2005 | + return true; 2006 | + } 2007 | + else if (val == 1) 2008 | + { 2009 | + retOneLabel = lbl; 2010 | + pos -= 2; 2011 | + if (!GetReturnValueLabel(body, pos, out lbl, out val)) 2012 | + return false; 2013 | + if (val != 0) 2014 | + return false; 2015 | + retZeroLabel = lbl; 2016 | + bodyLength = pos; 2017 | + return true; 2018 | + } 2019 | + else 2020 | + return false;*/ 2021 | + } 2022 | + 2023 | + bool GetReturnValueLabel(List body, int pos, out ILLabel lbl, out int val) 2024 | + { 2025 | + lbl = null; 2026 | + val = 0; 2027 | + if (pos < 0) 2028 | + return false; 2029 | + lbl = body[pos] as ILLabel; 2030 | + if (lbl == null) 2031 | + return false; 2032 | + ILExpression ldci4; 2033 | + return body[pos + 1].Match(ILCode.Ret, out ldci4) && 2034 | + ldci4.Match(ILCode.Ldc_I4, out val); 2035 | + } 2036 | + 2037 | + ILExpression CreateYieldReturn(ILExpression arg) => new ILExpression(ILCode.YieldReturn, null, arg); 2038 | ILExpression CreateYieldBreak() => new ILExpression(ILCode.YieldBreak, null); 2039 | 2040 | void ConvertBody(List body, int startPos, int bodyLength) { 2041 | diff --git a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs 2042 | index dbfe65f..9426b3a 100644 2043 | --- a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs 2044 | +++ b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs 2045 | @@ -350,9 +350,11 @@ void CachedDelegateInitializationWithField(ILBlock block, ref int i) 2046 | return; 2047 | if (newObj.Arguments[1].Code != ILCode.Ldftn) 2048 | return; 2049 | - MethodDef anonymousMethod = ((IMethod)newObj.Arguments[1].Operand).ResolveMethodWithinSameModule(); // method is defined in current assembly 2050 | + 2051 | + // this seems plain wrong, delegate (and thus its ctor) isnt necessarily anonymous! could instead argue for a check against field actually being a delegate, though. 2052 | + /*MethodDef anonymousMethod = ((IMethod)newObj.Arguments[1].Operand).ResolveMethodWithinSameModule(); // method is defined in current assembly 2053 | if (!Ast.Transforms.DelegateConstruction.IsAnonymousMethod(context, anonymousMethod)) 2054 | - return; 2055 | + return;*/ 2056 | 2057 | ILNode followingNode = block.Body.ElementAtOrDefault(i + 1); 2058 | if (followingNode != null && followingNode.GetSelfAndChildrenRecursive(Optimize_List_ILExpression).Count( 2059 | @@ -428,9 +430,9 @@ void CachedDelegateInitializationWithLocal(ILBlock block, ref int i) 2060 | return; 2061 | if (newObj.Arguments[1].Code != ILCode.Ldftn) 2062 | return; 2063 | - MethodDef anonymousMethod = ((IMethod)newObj.Arguments[1].Operand).ResolveMethodWithinSameModule(); // method is defined in current assembly 2064 | + /*MethodDef anonymousMethod = ((IMethod)newObj.Arguments[1].Operand).ResolveMethodWithinSameModule(); // method is defined in current assembly 2065 | if (!Ast.Transforms.DelegateConstruction.IsAnonymousMethod(context, anonymousMethod)) 2066 | - return; 2067 | + return;*/ 2068 | 2069 | ILNode followingNode = block.Body.ElementAtOrDefault(i + 1); 2070 | if (followingNode != null && followingNode.GetSelfAndChildrenRecursive().Count( 2071 | diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs 2072 | index db39e93..4330ec3 100644 2073 | --- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs 2074 | +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs 2075 | @@ -17,6 +17,7 @@ 2076 | // DEALINGS IN THE SOFTWARE. 2077 | 2078 | using System; 2079 | +using System.Linq; 2080 | using System.Collections.Generic; 2081 | using System.Diagnostics; 2082 | using dnlib.DotNet; 2083 | @@ -94,6 +95,13 @@ abstract class YieldReturnDecompiler { 2084 | } 2085 | #endregion 2086 | 2087 | + public static TypeDef YieldReturnType(DecompilerContext context, ILBlock method) 2088 | + { 2089 | + var yrd = TryCreate(context, method, null); 2090 | + 2091 | + return yrd?.enumeratorType; 2092 | + } 2093 | + 2094 | public static bool IsCompilerGeneratorEnumerator(TypeDef type) { 2095 | if (!(type.DeclaringType != null && type.IsCompilerGenerated())) 2096 | return false; 2097 | @@ -139,15 +147,33 @@ abstract class YieldReturnDecompiler { 2098 | return ilMethod; 2099 | } 2100 | 2101 | + internal static List DeBB(ILBlock method) => DeBB(method.Body); 2102 | + 2103 | + internal static List DeBB(List body) 2104 | + { 2105 | + var nodes = new List(); 2106 | + 2107 | + if (body.Count < 1 || !(body[0] is ILBasicBlock)) 2108 | + return body; 2109 | + 2110 | + foreach (var bb in body.Cast()) 2111 | + nodes.AddRange(bb.Body); 2112 | + 2113 | + return nodes; 2114 | + } 2115 | + 2116 | protected bool InitializeFieldToParameterMap(ILBlock method, ILVariable enumVar, ref int i) => 2117 | - InitializeFieldToParameterMap(method, enumVar, ref i, method.Body.Count); 2118 | + InitializeFieldToParameterMap(method.Body, enumVar, ref i, method.Body.Count); 2119 | + 2120 | + protected bool InitializeFieldToParameterMap(ILBlock method, ILVariable enumVar, ref int i, int end) => 2121 | + InitializeFieldToParameterMap(method.Body, enumVar, ref i, end); 2122 | 2123 | - protected bool InitializeFieldToParameterMap(ILBlock method, ILVariable enumVar, ref int i, int end) { 2124 | + protected bool InitializeFieldToParameterMap(List body, ILVariable enumVar, ref int i, int end) { 2125 | for (; i < end; i++) { 2126 | // stfld(..., ldloc(var_1), ldloc(parameter)) 2127 | IField storedField; 2128 | ILExpression ldloc, loadParameter; 2129 | - if (!method.Body[i].Match(ILCode.Stfld, out storedField, out ldloc, out loadParameter)) 2130 | + if (!body[i].Match(ILCode.Stfld, out storedField, out ldloc, out loadParameter)) 2131 | break; 2132 | ILVariable loadedVar, loadedArg; 2133 | if (!ldloc.Match(ILCode.Ldloc, out loadedVar)) 2134 | @@ -182,32 +208,41 @@ abstract class YieldReturnDecompiler { 2135 | void AnalyzeCurrentProperty() { 2136 | foreach (var getCurrentMethod in MethodUtils.GetMethod_get_Current(enumeratorType)) { 2137 | ILBlock method = CreateILAst(getCurrentMethod); 2138 | - if (method.Body.Count == 1) { 2139 | - // release builds directly return the current field 2140 | - ILExpression retExpr; 2141 | - IField field; 2142 | - ILExpression ldFromObj; 2143 | - if (method.Body[0].Match(ILCode.Ret, out retExpr) && 2144 | - retExpr.Match(ILCode.Ldfld, out field, out ldFromObj) && 2145 | - ldFromObj.MatchThis()) { 2146 | - currentField = GetFieldDefinition(field); 2147 | - } 2148 | - } 2149 | - else if (method.Body.Count == 2) { 2150 | - ILVariable v, v2; 2151 | - ILExpression stExpr; 2152 | - IField field; 2153 | - ILExpression ldFromObj; 2154 | - ILExpression retExpr; 2155 | - if (method.Body[0].Match(ILCode.Stloc, out v, out stExpr) && 2156 | - stExpr.Match(ILCode.Ldfld, out field, out ldFromObj) && 2157 | - ldFromObj.MatchThis() && 2158 | - method.Body[1].Match(ILCode.Ret, out retExpr) && 2159 | - retExpr.Match(ILCode.Ldloc, out v2) && 2160 | - v == v2) { 2161 | - currentField = GetFieldDefinition(field); 2162 | - } 2163 | - } 2164 | + 2165 | + var body = method.Body; 2166 | + if (body.Count == 1 && body[0] is ILBasicBlock) 2167 | + body = (body[0] as ILBasicBlock).Body.SkipWhile(n => n is ILLabel).ToList(); 2168 | + 2169 | + if (body.Count == 1) 2170 | + { 2171 | + // release builds directly return the current field 2172 | + ILExpression retExpr; 2173 | + IField field; 2174 | + ILExpression ldFromObj; 2175 | + if (body[0].Match(ILCode.Ret, out retExpr) && 2176 | + retExpr.Match(ILCode.Ldfld, out field, out ldFromObj) && 2177 | + ldFromObj.MatchThis()) 2178 | + { 2179 | + currentField = GetFieldDefinition(field); 2180 | + } 2181 | + } 2182 | + else if (body.Count == 2) 2183 | + { 2184 | + ILVariable v, v2; 2185 | + ILExpression stExpr; 2186 | + IField field; 2187 | + ILExpression ldFromObj; 2188 | + ILExpression retExpr; 2189 | + if (body[0].Match(ILCode.Stloc, out v, out stExpr) && 2190 | + stExpr.Match(ILCode.Ldfld, out field, out ldFromObj) && 2191 | + ldFromObj.MatchThis() && 2192 | + body[1].Match(ILCode.Ret, out retExpr) && 2193 | + retExpr.Match(ILCode.Ldloc, out v2) && 2194 | + v == v2) 2195 | + { 2196 | + currentField = GetFieldDefinition(field); 2197 | + } 2198 | + } 2199 | if (currentField != null) 2200 | break; 2201 | } 2202 | @@ -221,6 +256,8 @@ abstract class YieldReturnDecompiler { 2203 | foreach (var getEnumeratorMethod in MethodUtils.GetMethod_GetEnumerator(enumeratorType)) { 2204 | bool found = false; 2205 | ILBlock method = CreateILAst(getEnumeratorMethod); 2206 | + method.Body = DeBB(method); 2207 | + 2208 | foreach (ILNode node in method.Body) { 2209 | IField stField; 2210 | ILExpression stToObj; 2211 | --------------------------------------------------------------------------------