Ejemplo n.º 1
0
        public bool ConvertAssemblyReferences(string dllPath)
        {
            VortexPatcher.Logger.Info($"Attempting to convert {Path.GetFileName(dllPath)}");
            if (!File.Exists(dllPath))
            {
                VortexPatcher.Logger.Error($"File is missing: { dllPath }");
                return(false);
            }

            string tempFile         = dllPath + Constants.VORTEX_TEMP_SUFFIX;
            string instructionsFile = null;

            try
            {
                string backUpFile = dllPath + Constants.VORTEX_BACKUP;
                if (!File.Exists(backUpFile))
                {
                    Util.Backup(dllPath);
                }

                // Create a temporary file for us to manipulate.
                File.Copy(dllPath, tempFile, true);

                // UMM mods need to have an assembly reference to the VortexUnity.dll file
                //  containing UI specific functionality.
                AssemblyDefinition    vigoDef  = AssemblyDefinition.ReadAssembly(Path.Combine(VortexPatcher.CurrentDataPath, Constants.VIGO_FILENAME));
                AssemblyNameReference vigoRef  = AssemblyNameReference.Parse(vigoDef.FullName);
                string             vigoName    = vigoDef.Name.Name;
                AssemblyDefinition modAssembly = AssemblyDefinition.ReadAssembly(tempFile);

                // Add the reference to VIGO.
                modAssembly.MainModule.ImportReference(vigoRef.GetType());
                modAssembly.MainModule.AssemblyReferences.Add(vigoRef);
                var references = modAssembly.MainModule.AssemblyReferences;

                // Find the UMM reference
                AssemblyNameReference ummRef = references.FirstOrDefault(res => res.Name == Constants.UMM_ASSEMBLY_REF_NAME);
                if (ummRef == null)
                {
                    m_ModAssembly = Assembly.LoadFile(dllPath);
                    throw new Exceptions.AssemblyIsInjectedException(dllPath);
                }

                AssemblyNameReference newRef = AssemblyNameReference.Parse(VortexPatcher.InstallerAssembly.FullName);
                int idx = modAssembly.MainModule.AssemblyReferences.IndexOf(ummRef);
                modAssembly.MainModule.AssemblyReferences.Insert(idx, newRef);
                modAssembly.MainModule.AssemblyReferences.Remove(ummRef);

                modAssembly.Write(dllPath);
                modAssembly.Dispose();
                vigoDef.Dispose();

                File.Copy(dllPath, tempFile, true);

                // We're going to re-assemble the mod file; to do this we need to find out which
                //  .NET assembler to use (version is important)
                Version assemblerVersion = Util.GetFrameworkVer(dllPath);

                // Disassemble and extract any embedded resource files we can find.
                string disassembled = VortexHarmonyInstaller.Util.Disassembler.DisassembleFile(tempFile, true);

                // Find the reference id for VIGO within the assembly, we're
                //  going to use this value to replace the existing UMM refId.
                string pattern = @"(.assembly extern )(\/.*\/)( VortexUnity)";
                string refId   = Regex.Match(disassembled, pattern).Groups[2].Value;

                // UMM distributes 2 nearly identical Harmony assemblies
                //  one which seems to reference GAC assemblies directly (Harmony12),
                //  and the normally distributed assembly which will look for assemlies
                //  locally before resorting to GAC (An optimization?)
                //  Either way, we're going to use the assembly we distribute instead and
                //  remove Harmony12 if we find it.
                disassembled = disassembled.Replace("Harmony12", "Harmony");

                // This is one UGLY pattern but will have to do in the meantime.
                //  We use regex to find all UI calls which will now point to VortexHarmonyInstaller
                //  as we replaced the UMM reference, and re-point the instruction to VortexUnity
                //  TODO: There must be a better way of doing this - more research is needed.
                pattern          = @"(VortexHarmonyInstaller)(\/\*.*?\*\/)(\]UnityModManagerNet\.UnityModManager\/\*.*?\*\/\/UI)";
                disassembled     = Regex.Replace(disassembled, pattern, m => vigoName + refId + m.Groups[3].Value);
                instructionsFile = tempFile + Constants.IL_EXT;

                // Write the data to an IL file and delete the current dll file
                //  as we want to replace it. We backed up this file earlier so we should be fine.
                File.WriteAllText(instructionsFile, disassembled);
                File.Delete(dllPath);

                // Generate the new Assembly!
                VortexHarmonyInstaller.Util.Assembler.AssembleFile(instructionsFile, dllPath, assemblerVersion);

                // Ensure that the mod is aware of its assembly.
                m_ModAssembly = Assembly.LoadFile(dllPath);
                VortexPatcher.Logger.Info($"Assembly {Path.GetFileName(dllPath)} converted successfully.");
                return(true);
            }
            catch (Exceptions.AssemblyIsInjectedException)
            {
                // We already dealt with this mod, no need to hijack its calls again.
                VortexPatcher.Logger.Info($"Assembly {Path.GetFileName(dllPath)} is already patched.");
                return(true);
            }
            catch (Exception exc)
            {
                Util.RestoreBackup(dllPath);
                VortexPatcher.Logger.Error("Assembly conversion failed", exc);
                return(false);
            }
            finally
            {
                // Cleanup
                AssemblyDefinition modAssembly = AssemblyDefinition.ReadAssembly(tempFile);
                if (modAssembly.MainModule.HasResources)
                {
                    Resource[] resources = modAssembly.MainModule.Resources.Where(res => res.ResourceType == ResourceType.Embedded).ToArray();
                    foreach (Resource res in resources)
                    {
                        string possiblePath = Path.Combine(Path.GetDirectoryName(dllPath), res.Name);
                        if (File.Exists(possiblePath))
                        {
                            File.Delete(possiblePath);
                        }
                    }
                }

                modAssembly.Dispose();
                modAssembly = null;

                // We have no guarantee that the file reference has been released yet
                //  but we can try to delete anyway.
                Util.TryDelete(tempFile);

                if ((instructionsFile != null) && (File.Exists(instructionsFile)))
                {
                    Util.TryDelete(instructionsFile);
                }
            }
        }