/// <summary> /// Gets the <see cref="TargetFramework"/> for the specified <paramref name="module"/>. /// </summary> /// <param name="module">The module to get the target framework description for. Cannot be null.</param> /// <returns>A new instance of the <see cref="TargetFramework"/> class that describes the specified <paramref name="module"/>. /// </returns> public static TargetFramework DetectTargetFramework(PEFile module) { if (module is null) { throw new ArgumentNullException(nameof(module)); } int versionNumber; switch (module.GetRuntime()) { case TargetRuntime.Net_1_0: versionNumber = 100; break; case TargetRuntime.Net_1_1: versionNumber = 110; break; case TargetRuntime.Net_2_0: versionNumber = 200; // TODO: Detect when .NET 3.0/3.5 is required break; default: versionNumber = 400; break; } string targetFrameworkIdentifier = null; string targetFrameworkProfile = null; string targetFramework = module.DetectTargetFrameworkId(); if (!string.IsNullOrEmpty(targetFramework)) { string[] frameworkParts = targetFramework.Split(','); targetFrameworkIdentifier = frameworkParts.FirstOrDefault(a => !a.StartsWith(VersionToken, StringComparison.OrdinalIgnoreCase) && !a.StartsWith(ProfileToken, StringComparison.OrdinalIgnoreCase)); string frameworkVersion = frameworkParts.FirstOrDefault(a => a.StartsWith(VersionToken, StringComparison.OrdinalIgnoreCase)); if (frameworkVersion != null) { versionNumber = int.Parse(frameworkVersion.Substring(VersionToken.Length + 1).Replace(".", "")); if (versionNumber < 100) { versionNumber *= 10; } } string frameworkProfile = frameworkParts.FirstOrDefault(a => a.StartsWith(ProfileToken, StringComparison.OrdinalIgnoreCase)); if (frameworkProfile != null) { targetFrameworkProfile = frameworkProfile.Substring(ProfileToken.Length); } } return(new TargetFramework(targetFrameworkIdentifier, versionNumber, targetFrameworkProfile)); }
private TargetFramework DetectTargetFramework(PEFile module) { TargetFramework result = default; switch (module.GetRuntime()) { case Metadata.TargetRuntime.Net_1_0: result.VersionNumber = 100; result.TargetFrameworkVersion = "v1.0"; break; case Metadata.TargetRuntime.Net_1_1: result.VersionNumber = 110; result.TargetFrameworkVersion = "v1.1"; break; case Metadata.TargetRuntime.Net_2_0: result.VersionNumber = 200; result.TargetFrameworkVersion = "v2.0"; // TODO: Detect when .NET 3.0/3.5 is required break; default: result.VersionNumber = 400; result.TargetFrameworkVersion = "v4.0"; break; } string targetFramework = module.DetectTargetFrameworkId(); if (!string.IsNullOrEmpty(targetFramework)) { string[] frameworkParts = targetFramework.Split(','); result.TargetFrameworkIdentifier = frameworkParts.FirstOrDefault(a => !a.StartsWith("Version=", StringComparison.OrdinalIgnoreCase) && !a.StartsWith("Profile=", StringComparison.OrdinalIgnoreCase)); string frameworkVersion = frameworkParts.FirstOrDefault(a => a.StartsWith("Version=", StringComparison.OrdinalIgnoreCase)); if (frameworkVersion != null) { result.TargetFrameworkVersion = frameworkVersion.Substring("Version=".Length); result.VersionNumber = int.Parse(frameworkVersion.Substring("Version=v".Length).Replace(".", "")); if (result.VersionNumber < 100) { result.VersionNumber *= 10; } } string frameworkProfile = frameworkParts.FirstOrDefault(a => a.StartsWith("Profile=", StringComparison.OrdinalIgnoreCase)); if (frameworkProfile != null) { result.TargetFrameworkProfile = frameworkProfile.Substring("Profile=".Length); } } return(result); }
public CodeDecompiler(string assemblyName, Stream stream) { // Necessary to create the decompiler. var settings = new DecompilerSettings(); var file = new PEFile(assemblyName, stream); // Creates instance of CSharpDecompiler. _decompiler = new CSharpDecompiler( file, new UniversalAssemblyResolver( assemblyName, settings.ThrowOnAssemblyResolveErrors, file.DetectTargetFrameworkId(), null, settings.LoadInMemory ? PEStreamOptions.PrefetchMetadata : PEStreamOptions.Default, settings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections : MetadataReaderOptions.None ), settings ); }
static void Main(string[] args) { // args[0] is the path to Opus Magnum EXE string exe = args[0]; // args[1] is the path to mappings CSV string mappingsLoc = args[1]; // if there's a third string, its the path out.csv if(args.Length > 2) { Console.WriteLine("Reading strings..."); string[] lines = File.ReadAllLines(args[2]); bool hadSplit = true; // multi-line strings int lastIndex = 0; foreach(string line in lines) { string[] split = line.Split("~,~"); if(split.Length > 1) { // if we *can* split on this line, then we're definitely at the first line of a string hadSplit = true; try { lastIndex = int.Parse(split[0]); Strings.Add(lastIndex, split[1]); } catch(ArgumentException) { } } else if(!hadSplit) { // if this line isn't blank (or even if it is), then we're continuing a previous multi-line string, so append Strings[lastIndex] = Strings[lastIndex] + "\n" + line; } } // these are ridden with special characters // we can't just trim normally, see "fmt " breaking WAV loading // so we manually regex replace: [^a-zA-Z0-9_.:\n;'*()+<>\\{}# ,~/$\[\]\-©!"?&’\t=—@%●●●●…—……] is removed // this kills other languages, a better solution is needed in the future foreach(int key in Strings.Keys.ToList()) Strings[key] = Regex.Replace(Strings[key], "[^a-zA-Z0-9_.:\n;'*()+<>\\\\{}# ,~/$\\[\\]\\-©!\" ? &’\t =—@%●●●●…—……]", ""); } Console.WriteLine("Reading mappings..."); // for every line that doesn't start with a "#", split by "," and add to mappings string[] mappingsFile = File.ReadAllLines(mappingsLoc); foreach(var line in mappingsFile){ if(!line.StartsWith("#") && !line.Trim().Equals("")){ string[] split = line.Split(",", 2); mappings.Add(split[0], split[1]); } } var module = new PEFile(exe); var decompiler = new CSharpDecompiler(exe, new UniversalAssemblyResolver(exe, false, module.DetectTargetFrameworkId()), new DecompilerSettings() { NamedArguments = false }); // decompile Console.WriteLine("Decompiling..."); var ast = decompiler.DecompileWholeModuleAsSingleFile(); // we now have a syntax tree // we just need to walk it, modify class and member name references, and then output to files Console.WriteLine("Collecting intermediary names..."); ast.AcceptVisitor(new IdentifierCollectingVisitor()); Console.WriteLine("Remapping..."); ast.AcceptVisitor(new RemappingVisitor()); Console.WriteLine("Cleaning up invalid code..."); ast.AcceptVisitor(new CleanupVisitor()); // some params are in the wrong place... //Console.WriteLine("Adding modded entry point..."); //ast.AcceptVisitor(new EntrypointAddingVisitor()); Console.WriteLine("Writing nonsense -> intermediary..."); using StreamWriter intermediaryFile = new StreamWriter("./intermediary.txt"); foreach(KeyValuePair<string, string> kv in IdentifierCollectingVisitor.intermediary) { intermediaryFile.WriteLine(kv.Key + " -> " + kv.Value); } foreach(KeyValuePair<KeyValuePair<string, string>, string> kv in IdentifierCollectingVisitor.paramIntermediary) { intermediaryFile.WriteLine(kv.Key.Key + ", " + kv.Key.Value + " -> " + kv.Value); } string code = ast.ToString(); // if there's a fourth string, its the path to patch.diff // apply patch and compile if(args.Length > 3) { Console.WriteLine("Applying compilation patch..."); string patchFile = File.ReadAllText(args[3]); var diff = DiffParserHelper.Parse(patchFile); code = PatchHelper.Patch(code, diff.First().Chunks, "\n"); Console.WriteLine("Recompiling..."); SyntaxTree syntax = CSharpSyntaxTree.ParseText(code, new CSharpParseOptions()); var options = new CSharpCompilationOptions( OutputKind.WindowsApplication, optimizationLevel: OptimizationLevel.Release, allowUnsafe: true, warningLevel: 1, platform: Platform.X86 ); // File/Process/Directory doesn't exist var compilation = CSharpCompilation.Create("ModdedLightning", options: options) .AddReferences(new MetadataReference[]{ // add libs MetadataReference.CreateFromFile(typeof(string).Assembly.Location), MetadataReference.CreateFromFile(typeof(HashSet<object>).Assembly.Location), MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), MetadataReference.CreateFromFile(Assembly.Load("mscorlib").Location), MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location), MetadataReference.CreateFromFile(Assembly.Load("System.Runtime.Extensions").Location), MetadataReference.CreateFromFile(Assembly.Load("System.IO").Location), MetadataReference.CreateFromFile(Assembly.Load("System.IO.FileSystem").Location), MetadataReference.CreateFromFile(Assembly.Load("System.IO.FileSystem.Watcher").Location), MetadataReference.CreateFromFile(Assembly.Load("System.Net.Requests").Location), MetadataReference.CreateFromFile(Assembly.Load("System.Diagnostics.Process").Location), MetadataReference.CreateFromFile(Assembly.Load("System.Private.Uri").Location), MetadataReference.CreateFromFile(Assembly.Load("System.ComponentModel.Primitives").Location), MetadataReference.CreateFromFile(Assembly.Load("System.Console").Location), MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location), MetadataReference.CreateFromFile(typeof(Steamworks.CSteamID).Assembly.Location), MetadataReference.CreateFromFile(typeof(Ionic.Zip.ZipEntry).Assembly.Location) }) .AddSyntaxTrees(syntax); using FileStream outputAssembly = new FileStream("./ModdedLightning.exe", FileMode.Create); var res = compilation.Emit(outputAssembly); if(res.Success) { Console.WriteLine("Successfully recompiled!"); Console.WriteLine("(Press any key to continue.)"); Console.ReadKey(); Console.WriteLine("Writing runtime config & running batch..."); string runtimeConfig = @"{ ""runtimeOptions"": { ""tfm"": ""netcoreapp3.1"", ""framework"": { ""name"": ""Microsoft.NETCore.App"", ""version"": ""3.1.0"" }}}"; using StreamWriter configFile = new StreamWriter("./ModdedLightning.runtimeconfig.json"); configFile.WriteLine(runtimeConfig); string runBatch = @"""C:\Program Files (x86)\dotnet\dotnet.exe"" ModdedLightning.exe"; using StreamWriter batchFile = new StreamWriter("./runModded.bat"); batchFile.WriteLine(runBatch); } else { Console.WriteLine("Recompilation failed with " + res.Diagnostics.Length + " errors."); foreach(var error in res.Diagnostics) { Console.WriteLine("Location: " + error.Location.GetLineSpan()); Console.WriteLine(" " + error.GetMessage()); Console.WriteLine("(Press any key to continue.)"); Console.ReadKey(); } } } Console.WriteLine("Writing code output..."); using StreamWriter outputFile = new StreamWriter("./decomp.cs"); outputFile.WriteLine(code); Console.WriteLine("Done!"); }
/// <summary> /// Saves this object as an Inno Setup formatted script /// </summary> /// <param name="textWriter"><see cref="TextWriter"/> with which script is written</param> public virtual void Save(TextWriter textWriter) { var thisType = typeof(Installation); var derivedType = GetType(); var entriesBuilder = new ParameterizedEntriesBuilder(textWriter); // list of methods referenced by constants var constReferencedMethods = new HashSet <InstallationMethod>(); _constantReferencedMethod = methodInfo => constReferencedMethods.Add(new InstallationMethod(methodInfo)); try { using (new Section(textWriter, "Setup")) { var setupProperties = thisType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.GetCustomAttribute <SetupDirectiveAttribute>() != null) .Select(p => new { Alias = p.GetCustomAttribute <AliasAttribute>()?.Name, Property = derivedType.GetProperty(p.Name), TypeConverter = p.GetCustomAttribute <TypeConverterAttribute>() }) .Where(p => p.Property.DeclaringType != thisType) .Select(p => new { p.Property, Name = p.Alias ?? p.Property.Name, p.TypeConverter }) .OrderBy(p => p.Name) .ToList(); WriteDirectives(textWriter, setupProperties.Select(p => (p.Name, p.Property, p.TypeConverter))); } var langOptionsProperties = thisType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.GetCustomAttribute <LanguageDirectiveAttribute>() != null) .Select(p => new { Alias = p.GetCustomAttribute <AliasAttribute>()?.Name, Property = derivedType.GetProperty(p.Name), TypeConverter = p.GetCustomAttribute <TypeConverterAttribute>() }) .Where(p => derivedType.GetProperty(p.Property.Name).DeclaringType != thisType) .Select(p => new { p.Property, Name = p.Alias ?? p.Property.Name, p.TypeConverter }) .OrderBy(p => p.Name) .ToList(); if (langOptionsProperties.Count > 0) { using (new Section(textWriter, "LanguageOptions")) { WriteDirectives(textWriter, langOptionsProperties.Select(p => (p.Name, p.Property, p.TypeConverter))); } } ParameterizedEntriesBuilderHandler?.Invoke(entriesBuilder); } finally { _constantReferencedMethod = null; } using (new Section(textWriter, "Code")) { textWriter.WriteLine("const"); textWriter.WriteLine(" MB_ICONWARNING = $30;"); textWriter.WriteLine(" MB_ICONINFORMATION = $40;"); textWriter.WriteLine(" MB_ICONQUESTION = $20;"); textWriter.WriteLine(" MB_ICONERROR = $10;"); textWriter.WriteBlankLine(); var decompilers = new Dictionary <string, CSharpDecompiler>(); SyntaxTree GetSyntaxTree(string assemblyLocation, int metadataToken) { var settings = new DecompilerSettings { UseDebugSymbols = true, NamedArguments = false, SwitchExpressions = false, OutVariables = false, SeparateLocalVariableDeclarations = true, LoadInMemory = true, ShowXmlDocumentation = false, Discards = false }; var assemblyName = assemblyLocation; var peFile = new PEFile( assemblyName, new FileStream(assemblyName, FileMode.Open, FileAccess.Read), streamOptions: settings.LoadInMemory ? PEStreamOptions.PrefetchEntireImage : PEStreamOptions.Default, metadataOptions: settings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections : MetadataReaderOptions.None ); var resolver = new UniversalAssemblyResolver(assemblyName, settings.ThrowOnAssemblyResolveErrors, peFile.DetectTargetFrameworkId(), settings.LoadInMemory ? PEStreamOptions.PrefetchMetadata : PEStreamOptions.Default, settings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections : MetadataReaderOptions.None); var typeSystem = new DecompilerTypeSystem(peFile, resolver); var decompiler = decompilers.Set( assemblyName, new CSharpDecompiler(typeSystem, settings) { DebugInfoProvider = new MetadataDebugInfoProvider(assemblyName) } ); var method = MetadataTokenHelpers.TryAsEntityHandle(metadataToken); return(decompiler.Decompile(new List <EntityHandle>() { method.Value })); } var eventHandlers = thisType.GetMethods(BindingFlags.Instance | BindingFlags.Public).Where(m => m.GetCustomAttribute <EventHandlerAttribute>() != null) .Select(m => new { DerivedMethod = derivedType.GetMethod(m.Name, BindingFlags.Public | BindingFlags.Instance), InterfaceMethod = m }) .Select(m => { if (m.DerivedMethod.DeclaringType != typeof(Installation)) { return(new InstallationMethod(m.DerivedMethod, m.InterfaceMethod)); } return(null); }) .Where(m => m != null) .ToList(); var allMethods = entriesBuilder.Methods .Concat(eventHandlers) .Concat(constReferencedMethods) .ToList(); var aliasFactory = new Func <string, string>(name => { var method = allMethods.Single(m => m.Name == name); return(method.GetAttribute <AliasAttribute>()?.Name); }); bool encounteredInitializeSetup = false; bool encounteredInitializeUninstall = false; var referencedGlobalVariables = new Dictionary <FieldInfo, string>(); var definedMethods = new HashSet <string>(); var declaredMembers = new HashSet <MemberInfo>(); var namespaces = new HashSet <string>(); using var typeDefinitions = new Snippet(); var type = derivedType; while (type != null && type != typeof(Installation)) { namespaces.Add(type.Namespace); type = type.BaseType; } using (var snippet = new Snippet()) { foreach (var installationMethod in allMethods) { switch (installationMethod.Name) { case nameof(Installation.InitializeSetup): encounteredInitializeSetup = true; break; case nameof(Installation.InitializeUninstall): encounteredInitializeUninstall = true; break; } if (!definedMethods.Add(installationMethod.UniqueId)) { // already defined, probably via recursion when one method called another continue; } var ast = GetSyntaxTree(installationMethod.AssemblyLocation, installationMethod.MetadataToken); //ast.VisitChildren(new DiagnosticVisitor(Console.Out)); var methodDecl = ast.Children.OfType <MethodDeclaration>().Single(); using (var masterWriter = new TextCodeWriter(snippet, null, true)) { var codeWriterFactory = new NestedCodeWriterFactory(masterWriter); using (var methodWriter = codeWriterFactory.New()) { var context = new PascalScriptVisitorContext( this, methodWriter, null, namespaces, mi => GetSyntaxTree(mi.DeclaringType.Assembly.Location, mi.MetadataToken), referencedGlobalVariables, aliasFactory, new TextCodeWriter(typeDefinitions, null, false), declaredMembers, () => codeWriterFactory.New(), derivedType.BaseType, definedMethods); ast.VisitChildren(new PascalScriptVisitor(context)); methodWriter.WriteBlankLine(); } } } using (var codeWriter = new TextCodeWriter(textWriter, null, true)) { typeDefinitions.CopyTo(codeWriter); var usedSwitches = new HashSet <string>(); var variableInitialization = new List <(string name, string rhs)>(); // write global variables if (referencedGlobalVariables.Count > 0) { codeWriter.WriteLine("var"); using (codeWriter.Indent()) { foreach (var globalVariable in referencedGlobalVariables) { codeWriter.WriteLine($"{globalVariable.Value}: {globalVariable.Key.FieldType.ToPascal()};"); var defaultValue = globalVariable.Key.GetValue(this); var formattedDefaultValue = defaultValue == null || globalVariable.Key.FieldType.IsStruct() ? null : PascalScriptVisitor.FormatPrimitive(defaultValue); var cmdLineAttr = (globalVariable.Key.GetCustomAttribute <CommandLineParameterAttribute>()); if (cmdLineAttr != null) { var name = cmdLineAttr.SwitchName ?? globalVariable.Key.Name; var initialization = $"ExpandConstant('{{param:{name.ToLower()}"; if (defaultValue != null) { defaultValue = defaultValue.GetType() == typeof(bool) ? ((bool)defaultValue ? "1" : "0") : formattedDefaultValue; initialization += $"|{defaultValue}"; } initialization += "}')"; if (globalVariable.Key.FieldType == typeof(bool)) { initialization = $"({initialization} = '1')"; } if (!usedSwitches.Add(name)) { throw new NotSupportedException($"The command line parameter name {name} can be used only once"); } variableInitialization.Add((globalVariable.Key.Name, initialization)); } else if (formattedDefaultValue != null) { // FormatPrimitive() won't single quote the string if (defaultValue is string) { formattedDefaultValue = $"'{formattedDefaultValue}'"; } variableInitialization.Add((globalVariable.Key.Name, formattedDefaultValue)); } } } } codeWriter.WriteBlankLine(); // write body of code snippet.CopyTo(codeWriter); using (var globalVariableSnippet = new Snippet()) { using (var globalVariableWriter = new TextCodeWriter(globalVariableSnippet, null, true)) { variableInitialization.ForEach(init => globalVariableWriter.WriteLine($"{init.name} := {init.rhs};")); } if (encounteredInitializeSetup || variableInitialization.Count > 0) { // write InitializeSetup codeWriter.WriteLine($"function {nameof(Installation.InitializeSetup)}: Boolean;"); codeWriter.WriteBegin(); using (codeWriter.Indent()) { // write variable initialization globalVariableSnippet.CopyTo(codeWriter); codeWriter.WriteBlankLine(); codeWriter.WriteLine(encounteredInitializeSetup ? "Result := this_InitializeSetup();" : "Result := True;"); } codeWriter.WriteEnd(); } if (encounteredInitializeUninstall || variableInitialization.Count > 0) { // write InitializeSetup codeWriter.WriteLine($"function {nameof(Installation.InitializeUninstall)}: Boolean;"); codeWriter.WriteBegin(); using (codeWriter.Indent()) { // write variable initialization globalVariableSnippet.CopyTo(codeWriter); codeWriter.WriteBlankLine(); codeWriter.WriteLine(encounteredInitializeUninstall ? "Result := this_InitializeUninstall();" : "Result := True;"); } codeWriter.WriteEnd(); } } } } } }