/* * Copyright (c) 2018-2023 Beebyte Limited. All rights reserved. */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; #if UNITY_2017_3_OR_NEWER using UnityEditor.Compilation; #endif using UnityEngine; namespace Beebyte.Obfuscator.Assembly { public class AssemblySelector { private readonly HashSet _compiledAssemblyPaths = new HashSet(); private readonly HashSet _assemblyPaths = new HashSet(); public AssemblySelector(Options options) { if (options == null) throw new ArgumentException("options must not be null", "options"); if (options.compiledAssemblies == null) throw new ArgumentException( "options.compiledAssemblies must not be null", "options"); if (options.assemblies == null) throw new ArgumentException( "options.assemblies must not be null", "options"); if (Application.dataPath == null) throw new ArgumentException("Application.dataPath must not be null"); foreach (string assemblyName in options.compiledAssemblies) { string location = FindDllLocation(assemblyName); if (location != null) { _compiledAssemblyPaths.Add(location); } } foreach (string assemblyName in options.assemblies) { string location = FindDllLocation(assemblyName); if (location != null) { _assemblyPaths.Add(location); } } #if UNITY_2017_3_OR_NEWER if (!options.includeCompilationPipelineAssemblies) return; string projectDir = Path.GetDirectoryName(Application.dataPath); #if UNITY_2021_3_OR_NEWER string[] nativeCompiledFileNames = GetNativeCompiledFileNames(); string[] packageDllFileNames = GetPackageDllFileNames(); string[] additionalAssembliesToObfuscate = Directory.GetFiles( #if UNITY_2022_1_OR_NEWER projectDir + "/Library/Bee/PlayerScriptAssemblies/") #else projectDir + "/Library/PlayerScriptAssemblies/") #endif .Where(path => path.EndsWith(".dll")) .Where(path => !Path.GetFileName(path).Contains("-firstpass")) .Where(path => !Path.GetFileName(path).StartsWith("Unity.")) .Where(path => !Path.GetFileName(path).StartsWith("UnityEngine.")) .Where(path => !nativeCompiledFileNames.Contains(Path.GetFileName(path))) .Where(path => !packageDllFileNames.Contains(Path.GetFileName(path))) .Select(Path.GetFullPath) .ToArray(); _assemblyPaths = Enumerable.ToHashSet(_assemblyPaths.Select(Path.GetFullPath)); _assemblyPaths.UnionWith(additionalAssembliesToObfuscate); #else #if UNITY_2018_1_OR_NEWER #if UNITY_2019_3_OR_NEWER foreach (UnityEditor.Compilation.Assembly assembly in CompilationPipeline.GetAssemblies(AssembliesType .PlayerWithoutTestAssemblies)) #else foreach (UnityEditor.Compilation.Assembly assembly in CompilationPipeline.GetAssemblies(AssembliesType.Player)) #endif { #else foreach (UnityEditor.Compilation.Assembly assembly in CompilationPipeline.GetAssemblies()) { if ((assembly.flags & AssemblyFlags.EditorAssembly) != 0) { continue; } #endif if (assembly.name.Contains("-firstpass")) { continue; } if (assembly.sourceFiles.Length == 0) { continue; } if (assembly.sourceFiles[0].StartsWith("Packages")) { continue; } string scriptDllLocation = Path.Combine(projectDir, assembly.outputPath).Replace('\\', '/'); #if !UNITY_2019_2_OR_NEWER || UNITY_2019_2_0 || UNITY_2019_2_1 || UNITY_2019_2_2 || UNITY_2019_2_3 || UNITY_2019_2_4 || UNITY_2019_2_5 || UNITY_2019_2_6 || UNITY_2019_2_7 string dllLocation = scriptDllLocation; #else string dllLocation = ConvertToPlayerAssemblyLocationIfPresentOrElseNull(scriptDllLocation) ?? scriptDllLocation; #endif // If the assembly is for a different build target platform, oddly it will still be in the compilation // pipeline, however the file won't actually exist. if (dllLocation != null && File.Exists(dllLocation)) { _assemblyPaths.Add(dllLocation); } } #endif #endif } private string[] GetNativeCompiledFileNames() { return CompilationPipeline.GetAssemblies() .Where(assembly => (assembly.sourceFiles.Length == 0)) .Select(assembly => assembly.outputPath) .Select(Path.GetFileName) .ToArray(); } private string[] GetPackageDllFileNames() { return CompilationPipeline.GetAssemblies() .Where(assembly => assembly.sourceFiles.Length != 0) .Where(assembly => assembly.sourceFiles[0].StartsWith("Packages")) .Select(assembly => assembly.outputPath) .Select(Path.GetFileName) .ToArray(); } public ICollection GetCompiledAssemblyPaths() { return _compiledAssemblyPaths; } public ICollection GetAssemblyPaths() { return _assemblyPaths; } private static string FindDllLocation(string suffix) { if (string.IsNullOrEmpty(suffix)) { throw new ArgumentException( "Empty or null DLL names are forbidden (check Obfuscator Options assemblies / compiled assemblies list)"); } string compiledAssemblyLocation = GetCompiledAssemblyLocation(suffix); #if !UNITY_2019_2_OR_NEWER || UNITY_2019_2_0 || UNITY_2019_2_1 || UNITY_2019_2_2 || UNITY_2019_2_3 || UNITY_2019_2_4 || UNITY_2019_2_5 || UNITY_2019_2_6 || UNITY_2019_2_7 return compiledAssemblyLocation; #else return ConvertToPlayerAssemblyLocationIfPresentOrElseNull(compiledAssemblyLocation) ?? compiledAssemblyLocation; #endif } private static string GetCompiledAssemblyLocation(string suffix) { if (IsAPath(suffix)) { var path = GetFileOnKnownPath(suffix); if (path != null) { return path; } } foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { try { if (assembly.Location.Equals(string.Empty)) { DisplayFailedAssemblyParseWarning(assembly); } else if (assembly.Location.EndsWith(suffix)) { return assembly.Location.Replace('\\', '/'); } } catch (NotSupportedException) { DisplayFailedAssemblyParseWarning(assembly); } } Debug.LogWarning( suffix + " was not found (check Obfuscator Options assemblies / compiled assemblies list)"); return null; } private static bool IsAPath(string s) { return s.Contains("/") || s.Contains("\\"); } private static string GetFileOnKnownPath(string path) { var fromAssetFolder = Application.dataPath + Path.DirectorySeparatorChar.ToString() + path; if (File.Exists(fromAssetFolder)) { return fromAssetFolder; } if (File.Exists(path)) { return path; } return null; } private static string ConvertToPlayerAssemblyLocationIfPresentOrElseNull(string location) { if (location == null) { return null; } string beeScriptAssemblyLocation = Regex.Replace(location, "/ScriptAssemblies/", "/Bee/PlayerScriptAssemblies/"); string playerScriptAssemblyLocation = Regex.Replace(location, "/ScriptAssemblies/", "/PlayerScriptAssemblies/"); return File.Exists(beeScriptAssemblyLocation) ? beeScriptAssemblyLocation : File.Exists(playerScriptAssemblyLocation) ? playerScriptAssemblyLocation : null; } private static void DisplayFailedAssemblyParseWarning(System.Reflection.Assembly assembly) { Debug.LogWarning("Could not parse dynamically created assembly (string.Empty location) " + assembly.FullName + ". If you extend classes from within this assembly that in turn extend from " + "MonoBehaviour you will need to manually annotate these classes with [Skip]"); } } }