├── .DS_Store ├── .gitignore ├── Assets ├── .DS_Store ├── IFix.meta ├── IFix │ ├── .DS_Store │ ├── Editor.meta │ └── Editor │ │ ├── Configure.cs │ │ ├── Configure.cs.meta │ │ ├── ILFixEditor.cs │ │ ├── ILFixEditor.cs.meta │ │ ├── InterpertConfig.cs │ │ └── InterpertConfig.cs.meta ├── NewBehaviourScript.cs ├── NewBehaviourScript.cs.meta ├── Plugins.meta ├── Plugins │ ├── IFix.Core.dll │ └── IFix.Core.dll.meta ├── Scenes.meta ├── Scenes │ ├── SampleScene.unity │ └── SampleScene.unity.meta ├── StreamingAssets.meta └── StreamingAssets │ ├── Assembly-CSharp.patch.bytes │ └── Assembly-CSharp.patch.bytes.meta ├── Docs ├── .DS_Store ├── 1_Introduction.md ├── 2_Inject.md ├── 3_Fix.md ├── 4_VirtualMachine.md └── 5_LoadPatch.md ├── IFixToolKit ├── IFix.exe ├── Mono.Cecil.Mdb.dll ├── Mono.Cecil.Pdb.dll └── Mono.Cecil.dll ├── Packages ├── manifest.json └── packages-lock.json ├── ProjectSettings ├── AudioManager.asset ├── ClusterInputManager.asset ├── DynamicsManager.asset ├── EditorBuildSettings.asset ├── EditorSettings.asset ├── GraphicsSettings.asset ├── InputManager.asset ├── NavMeshAreas.asset ├── PackageManagerSettings.asset ├── Physics2DSettings.asset ├── PresetManager.asset ├── ProjectSettings.asset ├── ProjectVersion.txt ├── QualitySettings.asset ├── TagManager.asset ├── TimeManager.asset ├── UnityConnectSettings.asset ├── VFXManager.asset └── XRSettings.asset └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandin/InjectFixSample/bf7f93dcef1d9982f406d1b266925aef29883682/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Uu]ser[Ss]ettings/ 12 | 13 | # MemoryCaptures can get excessive in size. 14 | # They also could contain extremely sensitive data 15 | /[Mm]emoryCaptures/ 16 | 17 | # Asset meta data should only be ignored when the corresponding asset is also ignored 18 | !/[Aa]ssets/**/*.meta 19 | 20 | # Uncomment this line if you wish to ignore the asset store tools plugin 21 | # /[Aa]ssets/AssetStoreTools* 22 | 23 | # Autogenerated Jetbrains Rider plugin 24 | /[Aa]ssets/Plugins/Editor/JetBrains* 25 | 26 | # Visual Studio cache directory 27 | .vs/ 28 | 29 | # Gradle cache directory 30 | .gradle/ 31 | 32 | # Autogenerated VS/MD/Consulo solution and project files 33 | ExportedObj/ 34 | .consulo/ 35 | *.csproj 36 | *.unityproj 37 | *.sln 38 | *.suo 39 | *.tmp 40 | *.user 41 | *.userprefs 42 | *.pidb 43 | *.booproj 44 | *.svd 45 | *.pdb 46 | *.mdb 47 | *.opendb 48 | *.VC.db 49 | 50 | # Unity3D generated meta files 51 | *.pidb.meta 52 | *.pdb.meta 53 | *.mdb.meta 54 | 55 | # Unity3D generated file on crash reports 56 | sysinfo.txt 57 | 58 | # Builds 59 | *.apk 60 | *.aab 61 | *.unitypackage 62 | 63 | # Crashlytics generated file 64 | crashlytics-build.properties 65 | 66 | # Packed Addressables 67 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* 68 | 69 | # Temporary auto-generated Android Assets 70 | /[Aa]ssets/[Ss]treamingAssets/aa.meta 71 | /[Aa]ssets/[Ss]treamingAssets/aa/* 72 | -------------------------------------------------------------------------------- /Assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandin/InjectFixSample/bf7f93dcef1d9982f406d1b266925aef29883682/Assets/.DS_Store -------------------------------------------------------------------------------- /Assets/IFix.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 506674df56cd34e16b6a3f83d2cea2c9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/IFix/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandin/InjectFixSample/bf7f93dcef1d9982f406d1b266925aef29883682/Assets/IFix/.DS_Store -------------------------------------------------------------------------------- /Assets/IFix/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 89d3afd1ac6383a4bb803e86127d5ed3 3 | folderAsset: yes 4 | timeCreated: 1514965388 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/IFix/Editor/Configure.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making InjectFix available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * InjectFix is licensed under the MIT License, except for the third-party components listed in the file 'LICENSE' which may be subject to their corresponding license terms. 5 | * This file is subject to the terms and conditions defined in file 'LICENSE', which is part of this source code package. 6 | */ 7 | 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Reflection; 11 | using System.Collections; 12 | using System; 13 | 14 | /************************************************************************************************ 15 | * 配置 16 | * 1、IFix、Interpret、ReverseWrapper须放到一个打了Configure标签的类里; 17 | * 2、IFix、Interpret、ReverseWrapper均用打了相应标签的属性来表示; 18 | * 3、IFix、Interpret、ReverseWrapper配置须放到Editor目录下; 19 | *************************************************************************************************/ 20 | 21 | namespace IFix 22 | { 23 | //放置配置的 24 | [AttributeUsage(AttributeTargets.Class)] 25 | public class ConfigureAttribute : Attribute 26 | { 27 | 28 | } 29 | 30 | //默认执行原生代码,能切换到解析执行,必须放在标记了Configure的类里 31 | [AttributeUsage(AttributeTargets.Property)] 32 | public class IFixAttribute : Attribute 33 | { 34 | } 35 | 36 | //生成反向(解析调用原生)封装器,加速调用性能 37 | [AttributeUsage(AttributeTargets.Property)] 38 | public class ReverseWrapperAttribute : Attribute 39 | { 40 | } 41 | 42 | [AttributeUsage(AttributeTargets.Method)] 43 | public class FilterAttribute : Attribute 44 | { 45 | } 46 | 47 | public static class Configure 48 | { 49 | // 50 | public static Dictionary>> GetConfigureByTags(List tags) 51 | { 52 | var types = from assembly in AppDomain.CurrentDomain.GetAssemblies() 53 | where !(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder) 54 | from type in assembly.GetTypes() 55 | where type.IsDefined(typeof(ConfigureAttribute), false) 56 | select type; 57 | var tagsMap = tags.ToDictionary(t => t, t => new List>()); 58 | 59 | foreach(var type in types) 60 | { 61 | foreach (var prop in type.GetProperties(BindingFlags.Static | BindingFlags.Public 62 | | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) 63 | { 64 | if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType)) 65 | { 66 | foreach (var ca in prop.GetCustomAttributes(false)) 67 | { 68 | int flag = 0; 69 | var fp = ca.GetType().GetProperty("Flag"); 70 | if (fp != null) 71 | { 72 | flag = (int)fp.GetValue(ca, null); 73 | } 74 | List> infos; 75 | if (tagsMap.TryGetValue(ca.GetType().ToString(), out infos)) 76 | { 77 | foreach (var applyTo in prop.GetValue(null, null) as IEnumerable) 78 | { 79 | infos.Add(new KeyValuePair(applyTo, flag)); 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | return tagsMap; 87 | } 88 | 89 | public static List GetFilters() 90 | { 91 | var types = from assembly in AppDomain.CurrentDomain.GetAssemblies() 92 | where !(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder) 93 | from type in assembly.GetTypes() 94 | where type.IsDefined(typeof(ConfigureAttribute), false) 95 | select type; 96 | 97 | List filters = new List(); 98 | foreach (var type in types) 99 | { 100 | foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public 101 | | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) 102 | { 103 | if(method.IsDefined(typeof(IFix.FilterAttribute), false)) 104 | { 105 | filters.Add(method); 106 | } 107 | } 108 | } 109 | return filters; 110 | } 111 | 112 | public static IEnumerable GetTagMethods(Type tagType, string searchAssembly) 113 | { 114 | return (from assembly in AppDomain.CurrentDomain.GetAssemblies() 115 | where !(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder) 116 | && (assembly.GetName().Name == searchAssembly) 117 | where assembly.CodeBase.IndexOf("ScriptAssemblies") != -1 118 | from type in assembly.GetTypes() 119 | from method in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public 120 | | BindingFlags.NonPublic) 121 | where method.IsDefined(tagType, false) 122 | select method); 123 | } 124 | public static IEnumerable GetTagClasses(Type tagType, string searchAssembly) 125 | { 126 | return (from assembly in AppDomain.CurrentDomain.GetAssemblies() 127 | where !(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder) 128 | && (assembly.GetName().Name == searchAssembly) 129 | where assembly.CodeBase.IndexOf("ScriptAssemblies") != -1 130 | from type in assembly.GetTypes() 131 | where type.IsDefined(tagType, false) 132 | select type 133 | ); 134 | 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Assets/IFix/Editor/Configure.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aca776d82af199a4b9018dcc5c8cceec 3 | timeCreated: 1514363894 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/IFix/Editor/ILFixEditor.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making InjectFix available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * InjectFix is licensed under the MIT License, except for the third-party components listed in the file 'LICENSE' which may be subject to their corresponding license terms. 5 | * This file is subject to the terms and conditions defined in file 'LICENSE', which is part of this source code package. 6 | */ 7 | 8 | using UnityEngine; 9 | using UnityEditor; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System; 13 | using System.Linq; 14 | using System.Diagnostics; 15 | using System.Text.RegularExpressions; 16 | using System.Text; 17 | using System.Reflection; 18 | #if UNITY_2018_3_OR_NEWER 19 | using UnityEditor.Build.Player; 20 | #endif 21 | 22 | namespace IFix.Editor 23 | { 24 | //版本选择窗口 25 | public class VersionSelector : EditorWindow 26 | { 27 | public string buttonText = "Patch"; 28 | public string[] options = new string[] {}; 29 | public int index = 0; 30 | public Action callback = null; 31 | 32 | public static void Show(string[] options, Action callback, string buttonText = "Patch") 33 | { 34 | VersionSelector window = GetWindow(); 35 | window.options = options; 36 | window.callback = callback; 37 | window.buttonText = buttonText; 38 | window.Show(); 39 | } 40 | 41 | void OnGUI() 42 | { 43 | index = EditorGUILayout.Popup("Please select a version: ", index, options); 44 | if (GUILayout.Button(buttonText)) 45 | doPatch(); 46 | } 47 | 48 | void doPatch() 49 | { 50 | if (callback != null) 51 | { 52 | callback(index); 53 | } 54 | Close(); 55 | } 56 | } 57 | 58 | public class IFixEditor 59 | { 60 | //备份目录 61 | const string BACKUP_PATH = "./IFixDllBackup"; 62 | //备份文件的时间戳生成格式 63 | const string TIMESTAMP_FORMAT = "yyyyMMddHHmmss"; 64 | 65 | //注入的目标文件夹 66 | private static string targetAssembliesFolder = ""; 67 | 68 | //system("mono ifix.exe [args]") 69 | public static void CallIFix(List args) 70 | { 71 | #if UNITY_EDITOR_OSX 72 | var mono_path = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName), 73 | "../MonoBleedingEdge/bin/mono"); 74 | if(!File.Exists(mono_path)) 75 | { 76 | mono_path = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName), 77 | "../../MonoBleedingEdge/bin/mono"); 78 | } 79 | #elif UNITY_EDITOR_WIN 80 | var mono_path = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), 81 | "Data/MonoBleedingEdge/bin/mono.exe"); 82 | #endif 83 | if (!File.Exists(mono_path)) 84 | { 85 | UnityEngine.Debug.LogError("can not find mono!"); 86 | } 87 | var inject_tool_path = "./IFixToolKit/IFix.exe"; 88 | //"--runtime = v4.0.30319" 89 | if (!File.Exists(inject_tool_path)) 90 | { 91 | UnityEngine.Debug.LogError("please install the ToolKit"); 92 | return; 93 | } 94 | 95 | Process hotfix_injection = new Process(); 96 | hotfix_injection.StartInfo.FileName = mono_path; 97 | #if UNITY_5_6_OR_NEWER 98 | hotfix_injection.StartInfo.Arguments = "--debug --runtime=v4.0.30319 \"" + inject_tool_path + "\" \"" 99 | #else 100 | hotfix_injection.StartInfo.Arguments = "--debug \"" + inject_tool_path + "\" \"" 101 | #endif 102 | + string.Join("\" \"", args.ToArray()) + "\""; 103 | hotfix_injection.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; 104 | hotfix_injection.StartInfo.RedirectStandardOutput = true; 105 | hotfix_injection.StartInfo.UseShellExecute = false; 106 | hotfix_injection.StartInfo.CreateNoWindow = true; 107 | hotfix_injection.Start(); 108 | 109 | //UnityEngine.Debug.Log(hotfix_injection.StartInfo.FileName); 110 | //UnityEngine.Debug.Log(hotfix_injection.StartInfo.Arguments); 111 | 112 | StringBuilder exceptionInfo = null; 113 | while(!hotfix_injection.StandardOutput.EndOfStream) 114 | { 115 | string line = hotfix_injection.StandardOutput.ReadLine(); 116 | if (exceptionInfo != null) 117 | { 118 | exceptionInfo.AppendLine(line); 119 | } 120 | else 121 | { 122 | if (line.StartsWith("Warning:")) 123 | { 124 | UnityEngine.Debug.LogWarning(line); 125 | } 126 | else if (line.StartsWith("Error:")) 127 | { 128 | UnityEngine.Debug.LogError(line); 129 | } 130 | else if (line.StartsWith("Unhandled Exception:")) 131 | { 132 | exceptionInfo = new StringBuilder(line); 133 | } 134 | else 135 | { 136 | UnityEngine.Debug.Log(line); 137 | } 138 | } 139 | } 140 | hotfix_injection.WaitForExit(); 141 | if (exceptionInfo != null) 142 | { 143 | UnityEngine.Debug.LogError(exceptionInfo); 144 | } 145 | } 146 | 147 | [MenuItem("InjectFix/Inject", false, 1)] 148 | public static void InjectAssemblys() 149 | { 150 | if (EditorApplication.isCompiling || Application.isPlaying) 151 | { 152 | UnityEngine.Debug.LogError("compiling or playing"); 153 | return; 154 | } 155 | EditorUtility.DisplayProgressBar("Inject", "injecting...", 0); 156 | try 157 | { 158 | InjectAllAssemblys(); 159 | } 160 | catch(Exception e) 161 | { 162 | UnityEngine.Debug.LogError(e); 163 | } 164 | EditorUtility.ClearProgressBar(); 165 | } 166 | 167 | public static bool AutoInject = true; //可以在外部禁用掉自动注入 168 | 169 | public static bool InjectOnce = false; //AutoInjectAssemblys只调用一次,可以防止自动化打包时,很多场景导致AutoInjectAssemblys被多次调用 170 | 171 | static bool injected = false; 172 | 173 | [UnityEditor.Callbacks.PostProcessScene] 174 | public static void AutoInjectAssemblys() 175 | { 176 | if (AutoInject && !injected) 177 | { 178 | InjectAllAssemblys(); 179 | if (InjectOnce) 180 | { 181 | injected = true; 182 | } 183 | } 184 | } 185 | 186 | //获取备份文件信息 187 | public static void GetBackupInfo(out string[] backups, out string[] timestamps) 188 | { 189 | string pattern = @"Assembly-CSharp-(\d{14})\.dll$"; 190 | Regex r = new Regex(pattern); 191 | 192 | var allBackup = Directory.GetFiles(BACKUP_PATH).Where(path => r.Match(path).Success) 193 | .Select(path => path.Replace('\\', '/')).ToList(); 194 | allBackup.Sort(); 195 | 196 | backups = allBackup.Select(path => r.Match(path).Groups[1].Captures[0].Value).ToArray(); 197 | timestamps = allBackup.Select(path => DateTime.ParseExact(r.Match(path).Groups[1].Captures[0].Value, 198 | TIMESTAMP_FORMAT, System.Globalization.CultureInfo.InvariantCulture) 199 | .ToString("yyyy-MM-dd hh:mm:ss tt")).ToArray(); 200 | } 201 | 202 | //选择备份 203 | public static void SelectBackup(string buttonText, Action cb) 204 | { 205 | string[] backups; 206 | string[] timestamps; 207 | GetBackupInfo(out backups, out timestamps); 208 | 209 | VersionSelector.Show(timestamps.ToArray(), index => 210 | { 211 | cb(backups[index]); 212 | }, buttonText); 213 | } 214 | 215 | /// 216 | /// 对指定的程序集注入 217 | /// 218 | /// 程序集路径 219 | public static void InjectAssembly(string assembly) 220 | { 221 | var configure = Configure.GetConfigureByTags(new List() { 222 | "IFix.IFixAttribute", 223 | "IFix.InterpretAttribute", 224 | "IFix.ReverseWrapperAttribute", 225 | }); 226 | 227 | var filters = Configure.GetFilters(); 228 | 229 | var processCfgPath = "./process_cfg"; 230 | 231 | //该程序集是否有配置了些类,如果没有就跳过注入操作 232 | bool hasSomethingToDo = false; 233 | 234 | var blackList = new List(); 235 | 236 | using (BinaryWriter writer = new BinaryWriter(new FileStream(processCfgPath, FileMode.Create, 237 | FileAccess.Write))) 238 | { 239 | writer.Write(configure.Count); 240 | 241 | foreach (var kv in configure) 242 | { 243 | writer.Write(kv.Key); 244 | 245 | var typeList = kv.Value.Where(item => item.Key is Type) 246 | .Select(item => new KeyValuePair(item.Key as Type, item.Value)) 247 | .Where(item => item.Key.Assembly.GetName().Name == assembly) 248 | .ToList(); 249 | writer.Write(typeList.Count); 250 | 251 | if (typeList.Count > 0) 252 | { 253 | hasSomethingToDo = true; 254 | } 255 | 256 | foreach (var cfgItem in typeList) 257 | { 258 | writer.Write(GetCecilTypeName(cfgItem.Key)); 259 | writer.Write(cfgItem.Value); 260 | if (filters.Count > 0 && kv.Key == "IFix.IFixAttribute") 261 | { 262 | foreach(var method in cfgItem.Key.GetMethods(BindingFlags.Instance 263 | | BindingFlags.Static | BindingFlags.Public 264 | | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) 265 | { 266 | foreach(var filter in filters) 267 | { 268 | if ((bool)filter.Invoke(null, new object[] 269 | { 270 | method 271 | })) 272 | { 273 | blackList.Add(method); 274 | } 275 | } 276 | } 277 | } 278 | } 279 | } 280 | 281 | writeMethods(writer, blackList); 282 | } 283 | 284 | if (hasSomethingToDo) 285 | { 286 | 287 | var core_path = "./Assets/Plugins/IFix.Core.dll"; 288 | var assembly_path = string.Format("./Library/{0}/{1}.dll", targetAssembliesFolder, assembly); 289 | var patch_path = string.Format("./{0}.ill.bytes", assembly); 290 | List args = new List() { "-inject", core_path, assembly_path, 291 | processCfgPath, patch_path, assembly_path }; 292 | 293 | foreach (var path in 294 | (from asm in AppDomain.CurrentDomain.GetAssemblies() 295 | select Path.GetDirectoryName(asm.ManifestModule.FullyQualifiedName)).Distinct()) 296 | { 297 | try 298 | { 299 | //UnityEngine.Debug.Log("searchPath:" + path); 300 | args.Add(path); 301 | } 302 | catch { } 303 | } 304 | 305 | CallIFix(args); 306 | } 307 | 308 | File.Delete(processCfgPath); 309 | } 310 | 311 | /// 312 | /// 对injectAssemblys里的程序集进行注入,然后备份 313 | /// 314 | public static void InjectAllAssemblys() 315 | { 316 | if (EditorApplication.isCompiling || Application.isPlaying) 317 | { 318 | return; 319 | } 320 | 321 | targetAssembliesFolder = GetScriptAssembliesFolder(); 322 | 323 | foreach (var assembly in injectAssemblys) 324 | { 325 | InjectAssembly(assembly); 326 | } 327 | 328 | //doBackup(DateTime.Now.ToString(TIMESTAMP_FORMAT)); 329 | 330 | AssetDatabase.Refresh(); 331 | } 332 | 333 | private static string GetScriptAssembliesFolder() 334 | { 335 | var assembliesFolder = "PlayerScriptAssemblies"; 336 | if (!Directory.Exists(string.Format("./Library/{0}/", assembliesFolder))) 337 | { 338 | assembliesFolder = "ScriptAssemblies"; 339 | } 340 | return assembliesFolder; 341 | } 342 | 343 | //默认的注入及备份程序集 344 | //另外可以直接调用InjectAssembly对其它程序集进行注入。 345 | static string[] injectAssemblys = new string[] 346 | { 347 | "Assembly-CSharp", 348 | "Assembly-CSharp-firstpass" 349 | }; 350 | 351 | /// 352 | /// 把注入后的程序集备份 353 | /// 354 | /// 时间戳 355 | static void doBackup(string ts) 356 | { 357 | if (!Directory.Exists(BACKUP_PATH)) 358 | { 359 | Directory.CreateDirectory(BACKUP_PATH); 360 | } 361 | 362 | var scriptAssembliesDir = string.Format("./Library/{0}/", targetAssembliesFolder); 363 | 364 | foreach (var assembly in injectAssemblys) 365 | { 366 | var dllFile = string.Format("{0}{1}.dll", scriptAssembliesDir, assembly); 367 | if (!File.Exists(dllFile)) 368 | { 369 | continue; 370 | } 371 | File.Copy(dllFile, string.Format("{0}/{1}-{2}.dll", BACKUP_PATH, assembly, ts), true); 372 | 373 | var mdbFile = string.Format("{0}{1}.dll.mdb", scriptAssembliesDir, assembly); 374 | if (File.Exists(mdbFile)) 375 | { 376 | File.Copy(mdbFile, string.Format("{0}/{1}-{2}.dll.mdb", BACKUP_PATH, assembly, ts), true); 377 | } 378 | 379 | var pdbFile = string.Format("{0}{1}.dll.pdb", scriptAssembliesDir, assembly); 380 | if (File.Exists(pdbFile)) 381 | { 382 | File.Copy(pdbFile, string.Format("{0}/{1}-{2}.dll.pdb", BACKUP_PATH, assembly, ts), true); 383 | } 384 | } 385 | } 386 | 387 | /// 388 | /// 恢复某个选定的备份 389 | /// 390 | /// 时间戳 391 | static void doRestore(string ts) 392 | { 393 | var scriptAssembliesDir = string.Format("./Library/{0}/", targetAssembliesFolder); 394 | 395 | foreach (var assembly in injectAssemblys) 396 | { 397 | var dllFile = string.Format("{0}/{1}-{2}.dll", BACKUP_PATH, assembly, ts); 398 | if (!File.Exists(dllFile)) 399 | { 400 | continue; 401 | } 402 | File.Copy(dllFile, string.Format("{0}{1}.dll", scriptAssembliesDir, assembly), true); 403 | UnityEngine.Debug.Log("Revert to: " + dllFile); 404 | 405 | var mdbFile = string.Format("{0}/{1}-{2}.dll.mdb", BACKUP_PATH, assembly, ts); 406 | if (File.Exists(mdbFile)) 407 | { 408 | File.Copy(mdbFile, string.Format("{0}{1}.dll.mdb", scriptAssembliesDir, assembly), true); 409 | UnityEngine.Debug.Log("Revert to: " + mdbFile); 410 | } 411 | 412 | var pdbFile = string.Format("{0}/{1}-{2}.dll.pdb", BACKUP_PATH, assembly, ts); 413 | if (File.Exists(pdbFile)) 414 | { 415 | File.Copy(pdbFile, string.Format("{0}{1}.dll.pdb", scriptAssembliesDir, assembly), true); 416 | UnityEngine.Debug.Log("Revert to: " + pdbFile); 417 | } 418 | } 419 | } 420 | 421 | //cecil里的类名表示和.net标准并不一样,这里做些转换 422 | static string GetCecilTypeName(Type type) 423 | { 424 | if (type.IsByRef && type.GetElementType().IsGenericType) 425 | { 426 | return GetCecilTypeName(type.GetElementType()) + "&"; 427 | } 428 | else if (type.IsGenericType) 429 | { 430 | if (type.IsGenericTypeDefinition) 431 | { 432 | return type.ToString().Replace('+', '/').Replace('[', '<').Replace(']', '>'); 433 | } 434 | else 435 | { 436 | return Regex.Replace(type.ToString().Replace('+', '/'), @"(`\d).+", "$1") 437 | + "<" + string.Join(",", type.GetGenericArguments().Select(t => GetCecilTypeName(t)) 438 | .ToArray()) + ">"; 439 | } 440 | } 441 | else 442 | { 443 | return type.FullName.Replace('+', '/'); 444 | } 445 | } 446 | 447 | //目前支持的平台编译 448 | public enum Platform 449 | { 450 | android, 451 | ios, 452 | standalone 453 | } 454 | 455 | //缓存:解析好的编译参数 456 | private static Dictionary compileTemplates = new Dictionary(); 457 | 458 | //解析unity的编译参数 459 | private static string parseCompileTemplate(string path) 460 | { 461 | return string.Join("\n", File.ReadAllLines(path).Where(line => !line.StartsWith("Assets/") 462 | && !line.StartsWith("\"Assets/") 463 | && !line.StartsWith("'Assets/") 464 | && !line.StartsWith("-r:Assets/") 465 | && !line.StartsWith("-r:\"Assets/") 466 | && !line.StartsWith("-r:'Assets/") 467 | && !line.StartsWith("-out") 468 | ).ToArray()); 469 | } 470 | 471 | //对路径预处理,然后添加到StringBuilder 472 | //规则:如果路径含空格,则加上双引号 473 | static void appendFile(StringBuilder sb, string path) 474 | { 475 | if (path.IndexOf(' ') > 0) 476 | { 477 | sb.Append('"'); 478 | sb.Append(path); 479 | sb.Append('"'); 480 | sb.AppendLine(); 481 | } 482 | else 483 | { 484 | sb.AppendLine(path); 485 | } 486 | } 487 | 488 | //自动加入源码 489 | private static void appendDirectory(StringBuilder src, string dir) 490 | { 491 | foreach (var file in Directory.GetFiles(dir)) 492 | { 493 | //排除调Editor下的东西 494 | if (file.IndexOf(Path.DirectorySeparatorChar + "Editor" + Path.DirectorySeparatorChar) > 0 ) 495 | { 496 | continue; 497 | } 498 | //排除Assembly-CSharp-firstpass 499 | if (file.Substring(file.Length - 3).ToLower() == ".cs") 500 | { 501 | if (file.StartsWith("Assets" + Path.DirectorySeparatorChar + "Plugins") || 502 | file.StartsWith("Assets" + Path.DirectorySeparatorChar + "Standard Assets") || 503 | file.StartsWith("Assets" + Path.DirectorySeparatorChar + "Pro Standard Assets")) 504 | { 505 | continue; 506 | } 507 | appendFile(src, file); 508 | } 509 | } 510 | 511 | foreach(var subDir in Directory.GetDirectories(dir)) 512 | { 513 | appendDirectory(src, subDir); 514 | } 515 | } 516 | 517 | //通过模板文件,获取编译参数 518 | private static string getCompileArguments(Platform platform, string output) 519 | { 520 | string compileTemplate; 521 | if (!compileTemplates.TryGetValue(platform, out compileTemplate)) 522 | { 523 | #if UNITY_EDITOR_WIN 524 | var hostPlatform = "win"; 525 | #elif UNITY_EDITOR_OSX 526 | var hostPlatform = "osx"; 527 | #else 528 | var hostPlatform = "linux"; 529 | #endif 530 | var path = "IFixToolKit/" + platform + "." + hostPlatform + ".tpl"; 531 | if (!File.Exists(path)) 532 | { 533 | path = "IFixToolKit/" + platform + ".tpl"; 534 | if (!File.Exists(path)) 535 | { 536 | throw new InvalidOperationException("please put template file for " + platform 537 | + " in IFixToolKit directory!"); 538 | } 539 | } 540 | compileTemplate = parseCompileTemplate(path); 541 | compileTemplates.Add(platform, compileTemplate); 542 | } 543 | StringBuilder cmd = new StringBuilder(); 544 | StringBuilder src = new StringBuilder(); 545 | StringBuilder dll = new StringBuilder(); 546 | 547 | appendDirectory(src, "Assets"); 548 | var projectDir = Application.dataPath.Replace(Path.DirectorySeparatorChar, '/'); 549 | foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 550 | { 551 | try 552 | { 553 | #if (UNITY_EDITOR || XLUA_GENERAL) && !NET_STANDARD_2_0 554 | if (!(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder)) 555 | { 556 | #endif 557 | var assemblyPath = assembly.ManifestModule.FullyQualifiedName 558 | .Replace(Path.DirectorySeparatorChar, '/'); 559 | if (assemblyPath.StartsWith(projectDir)) 560 | { 561 | dll.Append("-r:"); 562 | appendFile(dll, assemblyPath.Replace(projectDir, "Assets")); 563 | } 564 | #if (UNITY_EDITOR || XLUA_GENERAL) && !NET_STANDARD_2_0 565 | } 566 | #endif 567 | } catch { } 568 | } 569 | 570 | cmd.AppendLine(compileTemplate); 571 | cmd.Append(dll.ToString()); 572 | cmd.Append(src.ToString()); 573 | cmd.AppendLine("-sdk:2"); 574 | cmd.Append("-out:"); 575 | appendFile(cmd, output); 576 | 577 | return cmd.ToString(); 578 | } 579 | 580 | //1、解析编译参数(处理元文件之外的编译参数) 581 | //2、搜索工程的c#源码,加上编译参数编译 582 | //3、编译Assembly-CSharp.dll 583 | //TODO: 只支持Assembly-CSharp.dll,但较新版本Unity已经支持多dll拆分 584 | //TODO: 目前的做法挺繁琐的,需要用户去获取Unity的编译命令文件,更好的做法应该是直接 585 | public static void Compile(string compileArgFile) 586 | { 587 | #if UNITY_EDITOR_OSX 588 | var monoPath = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName), 589 | "../MonoBleedingEdge/bin/mono"); 590 | var mcsPath = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName), 591 | "../MonoBleedingEdge/lib/mono/4.5/mcs.exe"); 592 | if(!File.Exists(monoPath)) 593 | { 594 | monoPath = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName), 595 | "../../MonoBleedingEdge/bin/mono"); 596 | mcsPath = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName), 597 | "../../MonoBleedingEdge/lib/mono/4.5/mcs.exe"); 598 | } 599 | #elif UNITY_EDITOR_WIN 600 | var monoPath = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), 601 | "Data/MonoBleedingEdge/bin/mono.exe"); 602 | var mcsPath = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), 603 | "Data/MonoBleedingEdge/lib/mono/4.5/mcs.exe"); 604 | #endif 605 | if (!File.Exists(monoPath)) 606 | { 607 | UnityEngine.Debug.LogError("can not find mono!"); 608 | } 609 | 610 | Process compileProcess = new Process(); 611 | compileProcess.StartInfo.FileName = monoPath; 612 | compileProcess.StartInfo.Arguments = "\"" + mcsPath + "\" " + "@" + compileArgFile; 613 | compileProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; 614 | compileProcess.StartInfo.RedirectStandardOutput = true; 615 | compileProcess.StartInfo.RedirectStandardError = true; 616 | compileProcess.StartInfo.UseShellExecute = false; 617 | compileProcess.StartInfo.CreateNoWindow = true; 618 | compileProcess.Start(); 619 | 620 | //UnityEngine.Debug.Log(hotfix_injection.StartInfo.FileName); 621 | //UnityEngine.Debug.Log(hotfix_injection.StartInfo.Arguments); 622 | 623 | while (!compileProcess.StandardError.EndOfStream) 624 | { 625 | UnityEngine.Debug.LogError(compileProcess.StandardError.ReadLine()); 626 | } 627 | 628 | while (!compileProcess.StandardOutput.EndOfStream) 629 | { 630 | UnityEngine.Debug.Log(compileProcess.StandardOutput.ReadLine()); 631 | } 632 | 633 | compileProcess.WaitForExit(); 634 | } 635 | 636 | //生成特定平台的patch 637 | public static void GenPlatformPatch(Platform platform, string patchOutputDir, 638 | string corePath = "./Assets/Plugins/IFix.Core.dll") 639 | { 640 | var outputDir = "Temp/ifix"; 641 | Directory.CreateDirectory("Temp"); 642 | Directory.CreateDirectory(outputDir); 643 | #if UNITY_2018_3_OR_NEWER 644 | ScriptCompilationSettings scriptCompilationSettings = new ScriptCompilationSettings(); 645 | if (platform == Platform.android) 646 | { 647 | scriptCompilationSettings.group = BuildTargetGroup.Android; 648 | scriptCompilationSettings.target = BuildTarget.Android; 649 | } 650 | else if(platform == Platform.ios) 651 | { 652 | scriptCompilationSettings.group = BuildTargetGroup.iOS; 653 | scriptCompilationSettings.target = BuildTarget.iOS; 654 | } 655 | else 656 | { 657 | scriptCompilationSettings.group = BuildTargetGroup.Standalone; 658 | scriptCompilationSettings.target = BuildTarget.StandaloneWindows; 659 | } 660 | 661 | ScriptCompilationResult scriptCompilationResult = PlayerBuildInterface.CompilePlayerScripts(scriptCompilationSettings, outputDir); 662 | 663 | foreach (var assembly in injectAssemblys) 664 | { 665 | GenPatch(assembly, string.Format("{0}/{1}.dll", outputDir, assembly), 666 | "./Assets/Plugins/IFix.Core.dll", string.Format("{0}{1}.patch.bytes", patchOutputDir, assembly)); 667 | } 668 | #else 669 | throw new NotImplementedException(); 670 | //var compileArgFile = "Temp/ifix/unity_" + platform + "_compile_argument"; 671 | //var tmpDllPath = "Temp/ifix/Assembly-CSharp.dll"; 672 | //File.WriteAllText(compileArgFile, getCompileArguments(platform, tmpDllPath)); 673 | //先编译dll到Temp目录下 674 | //Compile(compileArgFile); 675 | //对编译后的dll生成补丁 676 | //GenPatch("Assembly-CSharp", tmpDllPath, corePath, patchPath); 677 | 678 | //File.Delete(compileArgFile); 679 | //File.Delete(tmpDllPath); 680 | //File.Delete(tmpDllPath + ".mdb"); 681 | #endif 682 | } 683 | 684 | //把方法签名写入文件 685 | //由于目前不支持泛型函数的patch,所以函数签名为方法名+参数类型 686 | static void writeMethods(BinaryWriter writer, List methods) 687 | { 688 | var methodGroups = methods.GroupBy(m => m.DeclaringType).ToList(); 689 | writer.Write(methodGroups.Count); 690 | foreach (var methodGroup in methodGroups) 691 | { 692 | writer.Write(GetCecilTypeName(methodGroup.Key)); 693 | writer.Write(methodGroup.Count()); 694 | foreach (var method in methodGroup) 695 | { 696 | writer.Write(method.Name); 697 | writer.Write(GetCecilTypeName(method.ReturnType)); 698 | writer.Write(method.GetParameters().Length); 699 | foreach (var parameter in method.GetParameters()) 700 | { 701 | writer.Write(parameter.IsOut); 702 | writer.Write(GetCecilTypeName(parameter.ParameterType)); 703 | } 704 | } 705 | } 706 | } 707 | 708 | static void writeClasses(BinaryWriter writer, List classes) 709 | { 710 | writer.Write(classes.Count); 711 | foreach (var classGroup in classes) 712 | { 713 | writer.Write(GetCecilTypeName(classGroup)); 714 | } 715 | } 716 | 717 | static bool hasGenericParameter(Type type) 718 | { 719 | if (type.IsByRef || type.IsArray) 720 | { 721 | return hasGenericParameter(type.GetElementType()); 722 | } 723 | if (type.IsGenericType) 724 | { 725 | foreach (var typeArg in type.GetGenericArguments()) 726 | { 727 | if (hasGenericParameter(typeArg)) 728 | { 729 | return true; 730 | } 731 | } 732 | return false; 733 | } 734 | return type.IsGenericParameter; 735 | } 736 | 737 | static bool hasGenericParameter(MethodBase method) 738 | { 739 | if (method.IsGenericMethodDefinition || method.IsGenericMethod) return true; 740 | if (!method.IsConstructor && hasGenericParameter((method as MethodInfo).ReturnType)) return true; 741 | 742 | foreach (var param in method.GetParameters()) 743 | { 744 | if (hasGenericParameter(param.ParameterType)) 745 | { 746 | return true; 747 | } 748 | } 749 | return false; 750 | 751 | } 752 | 753 | /// 754 | /// 生成patch 755 | /// 756 | /// 程序集名,用来过滤配置 757 | /// 程序集路径 758 | /// IFix.Core.dll所在路径 759 | /// 生成的patch的保存路径 760 | public static void GenPatch(string assembly, string assemblyCSharpPath 761 | = "./Library/ScriptAssemblies/Assembly-CSharp.dll", 762 | string corePath = "./Assets/Plugins/IFix.Core.dll", string patchPath = "Assembly-CSharp.patch.bytes") 763 | { 764 | var patchMethods = Configure.GetTagMethods(typeof(PatchAttribute), assembly).ToList(); 765 | var genericMethod = patchMethods.FirstOrDefault(m => hasGenericParameter(m)); 766 | if (genericMethod != null) 767 | { 768 | throw new InvalidDataException("not support generic method: " + genericMethod); 769 | } 770 | 771 | if (patchMethods.Count == 0) 772 | { 773 | return; 774 | } 775 | 776 | var newMethods = Configure.GetTagMethods(typeof(InterpretAttribute), assembly).ToList(); 777 | var newClasses = Configure.GetTagClasses(typeof(InterpretAttribute), assembly).ToList(); 778 | genericMethod = newMethods.FirstOrDefault(m => hasGenericParameter(m)); 779 | if (genericMethod != null) 780 | { 781 | throw new InvalidDataException("not support generic method: " + genericMethod); 782 | } 783 | 784 | var processCfgPath = "./process_cfg"; 785 | 786 | using (BinaryWriter writer = new BinaryWriter(new FileStream(processCfgPath, FileMode.Create, 787 | FileAccess.Write))) 788 | { 789 | writeMethods(writer, patchMethods); 790 | writeMethods(writer, newMethods); 791 | writeClasses(writer, newClasses); 792 | } 793 | 794 | List args = new List() { "-patch", corePath, assemblyCSharpPath, "null", 795 | processCfgPath, patchPath }; 796 | 797 | foreach (var path in 798 | (from asm in AppDomain.CurrentDomain.GetAssemblies() 799 | select Path.GetDirectoryName(asm.ManifestModule.FullyQualifiedName)).Distinct()) 800 | { 801 | try 802 | { 803 | //UnityEngine.Debug.Log("searchPath:" + path); 804 | args.Add(path); 805 | } 806 | catch { } 807 | } 808 | 809 | CallIFix(args); 810 | 811 | File.Delete(processCfgPath); 812 | 813 | AssetDatabase.Refresh(); 814 | } 815 | 816 | [MenuItem("InjectFix/Fix", false, 2)] 817 | public static void Patch() 818 | { 819 | EditorUtility.DisplayProgressBar("Generate Patch for Edtior", "patching...", 0); 820 | try 821 | { 822 | foreach (var assembly in injectAssemblys) 823 | { 824 | var assembly_path = string.Format("./Library/{0}/{1}.dll", GetScriptAssembliesFolder(), assembly); 825 | GenPatch(assembly, assembly_path, "./Assets/Plugins/IFix.Core.dll", 826 | string.Format("{0}.patch.bytes", assembly)); 827 | } 828 | } 829 | catch (Exception e) 830 | { 831 | UnityEngine.Debug.LogError(e); 832 | } 833 | EditorUtility.ClearProgressBar(); 834 | } 835 | 836 | #if UNITY_2018_3_OR_NEWER 837 | [MenuItem("InjectFix/Fix(Android)", false, 3)] 838 | public static void CompileToAndroid() 839 | { 840 | EditorUtility.DisplayProgressBar("Generate Patch for Android", "patching...", 0); 841 | try 842 | { 843 | GenPlatformPatch(Platform.android, ""); 844 | } 845 | catch(Exception e) 846 | { 847 | UnityEngine.Debug.LogError(e); 848 | } 849 | EditorUtility.ClearProgressBar(); 850 | } 851 | 852 | [MenuItem("InjectFix/Fix(IOS)", false, 4)] 853 | public static void CompileToIOS() 854 | { 855 | EditorUtility.DisplayProgressBar("Generate Patch for IOS", "patching...", 0); 856 | try 857 | { 858 | GenPlatformPatch(Platform.ios, ""); 859 | } 860 | catch(Exception e) 861 | { 862 | UnityEngine.Debug.LogError(e); 863 | } 864 | EditorUtility.ClearProgressBar(); 865 | } 866 | #endif 867 | } 868 | } 869 | -------------------------------------------------------------------------------- /Assets/IFix/Editor/ILFixEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ea1d006806821c945ad3dfda116f04f7 3 | timeCreated: 1514863799 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/IFix/Editor/InterpertConfig.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using IFix; 6 | 7 | [Configure] 8 | public class InterpertConfig 9 | { 10 | [IFix] 11 | static IEnumerable ToProcess 12 | { 13 | get 14 | { 15 | return new List() { 16 | typeof(NewBehaviourScript), 17 | }; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Assets/IFix/Editor/InterpertConfig.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 203fdc2fa68e349eea7001a827a4f8f1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NewBehaviourScript.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using IFix; 6 | using IFix.Core; 7 | using UnityEngine; 8 | 9 | public class NewBehaviourScript : MonoBehaviour 10 | { 11 | // Start is called before the first frame update 12 | void Start() 13 | { 14 | string text = Path.Combine(Application.streamingAssetsPath, "Assembly-CSharp.patch.bytes"); 15 | bool flag = File.Exists(text); 16 | if (flag) 17 | { 18 | Debug.Log("Load HotFix, patchPath=" + text); 19 | PatchManager.Load(new FileStream(text, FileMode.Open), true); 20 | } 21 | } 22 | 23 | // Update is called once per frame 24 | void Update() 25 | { 26 | 27 | } 28 | 29 | void OnGUI() 30 | { 31 | if (GUI.Button(new Rect((Screen.width - 200) / 2, 20, 200, 100), "Call FuncA")) 32 | { 33 | Debug.Log("Button, Call FuncA, result=" + FuncA()); 34 | } 35 | } 36 | 37 | [Patch] 38 | public string FuncA() 39 | { 40 | return "New"; 41 | } 42 | 43 | public void callFunc() 44 | { 45 | string obj = "instance"; 46 | 47 | var method = typeof(object).GetMethod("ToString"); 48 | var ftn = method.MethodHandle.GetFunctionPointer(); 49 | var func = (Func)Activator.CreateInstance(typeof(Func), obj, ftn); 50 | 51 | var result = func(); 52 | Debug.Log("result:" + result); 53 | } 54 | 55 | public void main(string[] args) 56 | { 57 | Console.WriteLine("Hello World!" + args); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Assets/NewBehaviourScript.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a0e6c315ab1cc4dc18b631e52669f0c5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 88a91c8c287514f998da1e8665c9c833 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Plugins/IFix.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandin/InjectFixSample/bf7f93dcef1d9982f406d1b266925aef29883682/Assets/Plugins/IFix.Core.dll -------------------------------------------------------------------------------- /Assets/Plugins/IFix.Core.dll.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7c6cf326f60d243479929e308c3123fb 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | defineConstraints: [] 9 | isPreloaded: 0 10 | isOverridable: 0 11 | isExplicitlyReferenced: 0 12 | validateReferences: 1 13 | platformData: 14 | - first: 15 | Any: 16 | second: 17 | enabled: 1 18 | settings: {} 19 | - first: 20 | Editor: Editor 21 | second: 22 | enabled: 0 23 | settings: 24 | DefaultValueInitialized: true 25 | - first: 26 | Windows Store Apps: WindowsStoreApps 27 | second: 28 | enabled: 0 29 | settings: 30 | CPU: AnyCPU 31 | userData: 32 | assetBundleName: 33 | assetBundleVariant: 34 | -------------------------------------------------------------------------------- /Assets/Scenes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 92f6c4202bb9f4c3cb1a428c9b01d32d 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Scenes/SampleScene.unity: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!29 &1 4 | OcclusionCullingSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_OcclusionBakeSettings: 8 | smallestOccluder: 5 9 | smallestHole: 0.25 10 | backfaceThreshold: 100 11 | m_SceneGUID: 00000000000000000000000000000000 12 | m_OcclusionCullingData: {fileID: 0} 13 | --- !u!104 &2 14 | RenderSettings: 15 | m_ObjectHideFlags: 0 16 | serializedVersion: 9 17 | m_Fog: 0 18 | m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} 19 | m_FogMode: 3 20 | m_FogDensity: 0.01 21 | m_LinearFogStart: 0 22 | m_LinearFogEnd: 300 23 | m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} 24 | m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} 25 | m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} 26 | m_AmbientIntensity: 1 27 | m_AmbientMode: 0 28 | m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} 29 | m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} 30 | m_HaloStrength: 0.5 31 | m_FlareStrength: 1 32 | m_FlareFadeSpeed: 3 33 | m_HaloTexture: {fileID: 0} 34 | m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} 35 | m_DefaultReflectionMode: 0 36 | m_DefaultReflectionResolution: 128 37 | m_ReflectionBounces: 1 38 | m_ReflectionIntensity: 1 39 | m_CustomReflection: {fileID: 0} 40 | m_Sun: {fileID: 705507994} 41 | m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} 42 | m_UseRadianceAmbientProbe: 0 43 | --- !u!157 &3 44 | LightmapSettings: 45 | m_ObjectHideFlags: 0 46 | serializedVersion: 11 47 | m_GIWorkflowMode: 1 48 | m_GISettings: 49 | serializedVersion: 2 50 | m_BounceScale: 1 51 | m_IndirectOutputScale: 1 52 | m_AlbedoBoost: 1 53 | m_EnvironmentLightingMode: 0 54 | m_EnableBakedLightmaps: 1 55 | m_EnableRealtimeLightmaps: 0 56 | m_LightmapEditorSettings: 57 | serializedVersion: 12 58 | m_Resolution: 2 59 | m_BakeResolution: 40 60 | m_AtlasSize: 1024 61 | m_AO: 0 62 | m_AOMaxDistance: 1 63 | m_CompAOExponent: 1 64 | m_CompAOExponentDirect: 0 65 | m_ExtractAmbientOcclusion: 0 66 | m_Padding: 2 67 | m_LightmapParameters: {fileID: 0} 68 | m_LightmapsBakeMode: 1 69 | m_TextureCompression: 1 70 | m_FinalGather: 0 71 | m_FinalGatherFiltering: 1 72 | m_FinalGatherRayCount: 256 73 | m_ReflectionCompression: 2 74 | m_MixedBakeMode: 2 75 | m_BakeBackend: 1 76 | m_PVRSampling: 1 77 | m_PVRDirectSampleCount: 32 78 | m_PVRSampleCount: 500 79 | m_PVRBounces: 2 80 | m_PVREnvironmentSampleCount: 500 81 | m_PVREnvironmentReferencePointCount: 2048 82 | m_PVRFilteringMode: 2 83 | m_PVRDenoiserTypeDirect: 0 84 | m_PVRDenoiserTypeIndirect: 0 85 | m_PVRDenoiserTypeAO: 0 86 | m_PVRFilterTypeDirect: 0 87 | m_PVRFilterTypeIndirect: 0 88 | m_PVRFilterTypeAO: 0 89 | m_PVREnvironmentMIS: 0 90 | m_PVRCulling: 1 91 | m_PVRFilteringGaussRadiusDirect: 1 92 | m_PVRFilteringGaussRadiusIndirect: 5 93 | m_PVRFilteringGaussRadiusAO: 2 94 | m_PVRFilteringAtrousPositionSigmaDirect: 0.5 95 | m_PVRFilteringAtrousPositionSigmaIndirect: 2 96 | m_PVRFilteringAtrousPositionSigmaAO: 1 97 | m_ExportTrainingData: 0 98 | m_TrainingDataDestination: TrainingData 99 | m_LightProbeSampleCountMultiplier: 4 100 | m_LightingDataAsset: {fileID: 0} 101 | m_UseShadowmask: 1 102 | --- !u!196 &4 103 | NavMeshSettings: 104 | serializedVersion: 2 105 | m_ObjectHideFlags: 0 106 | m_BuildSettings: 107 | serializedVersion: 2 108 | agentTypeID: 0 109 | agentRadius: 0.5 110 | agentHeight: 2 111 | agentSlope: 45 112 | agentClimb: 0.4 113 | ledgeDropHeight: 0 114 | maxJumpAcrossDistance: 0 115 | minRegionArea: 2 116 | manualCellSize: 0 117 | cellSize: 0.16666667 118 | manualTileSize: 0 119 | tileSize: 256 120 | accuratePlacement: 0 121 | debug: 122 | m_Flags: 0 123 | m_NavMeshData: {fileID: 0} 124 | --- !u!1 &705507993 125 | GameObject: 126 | m_ObjectHideFlags: 0 127 | m_CorrespondingSourceObject: {fileID: 0} 128 | m_PrefabInstance: {fileID: 0} 129 | m_PrefabAsset: {fileID: 0} 130 | serializedVersion: 6 131 | m_Component: 132 | - component: {fileID: 705507995} 133 | - component: {fileID: 705507994} 134 | m_Layer: 0 135 | m_Name: Directional Light 136 | m_TagString: Untagged 137 | m_Icon: {fileID: 0} 138 | m_NavMeshLayer: 0 139 | m_StaticEditorFlags: 0 140 | m_IsActive: 1 141 | --- !u!108 &705507994 142 | Light: 143 | m_ObjectHideFlags: 0 144 | m_CorrespondingSourceObject: {fileID: 0} 145 | m_PrefabInstance: {fileID: 0} 146 | m_PrefabAsset: {fileID: 0} 147 | m_GameObject: {fileID: 705507993} 148 | m_Enabled: 1 149 | serializedVersion: 10 150 | m_Type: 1 151 | m_Shape: 0 152 | m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} 153 | m_Intensity: 1 154 | m_Range: 10 155 | m_SpotAngle: 30 156 | m_InnerSpotAngle: 21.802082 157 | m_CookieSize: 10 158 | m_Shadows: 159 | m_Type: 2 160 | m_Resolution: -1 161 | m_CustomResolution: -1 162 | m_Strength: 1 163 | m_Bias: 0.05 164 | m_NormalBias: 0.4 165 | m_NearPlane: 0.2 166 | m_CullingMatrixOverride: 167 | e00: 1 168 | e01: 0 169 | e02: 0 170 | e03: 0 171 | e10: 0 172 | e11: 1 173 | e12: 0 174 | e13: 0 175 | e20: 0 176 | e21: 0 177 | e22: 1 178 | e23: 0 179 | e30: 0 180 | e31: 0 181 | e32: 0 182 | e33: 1 183 | m_UseCullingMatrixOverride: 0 184 | m_Cookie: {fileID: 0} 185 | m_DrawHalo: 0 186 | m_Flare: {fileID: 0} 187 | m_RenderMode: 0 188 | m_CullingMask: 189 | serializedVersion: 2 190 | m_Bits: 4294967295 191 | m_RenderingLayerMask: 1 192 | m_Lightmapping: 1 193 | m_LightShadowCasterMode: 0 194 | m_AreaSize: {x: 1, y: 1} 195 | m_BounceIntensity: 1 196 | m_ColorTemperature: 6570 197 | m_UseColorTemperature: 0 198 | m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} 199 | m_UseBoundingSphereOverride: 0 200 | m_ShadowRadius: 0 201 | m_ShadowAngle: 0 202 | --- !u!4 &705507995 203 | Transform: 204 | m_ObjectHideFlags: 0 205 | m_CorrespondingSourceObject: {fileID: 0} 206 | m_PrefabInstance: {fileID: 0} 207 | m_PrefabAsset: {fileID: 0} 208 | m_GameObject: {fileID: 705507993} 209 | m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} 210 | m_LocalPosition: {x: 0, y: 3, z: 0} 211 | m_LocalScale: {x: 1, y: 1, z: 1} 212 | m_Children: [] 213 | m_Father: {fileID: 0} 214 | m_RootOrder: 1 215 | m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} 216 | --- !u!1 &963194225 217 | GameObject: 218 | m_ObjectHideFlags: 0 219 | m_CorrespondingSourceObject: {fileID: 0} 220 | m_PrefabInstance: {fileID: 0} 221 | m_PrefabAsset: {fileID: 0} 222 | serializedVersion: 6 223 | m_Component: 224 | - component: {fileID: 963194228} 225 | - component: {fileID: 963194227} 226 | - component: {fileID: 963194226} 227 | - component: {fileID: 963194229} 228 | m_Layer: 0 229 | m_Name: Main Camera 230 | m_TagString: MainCamera 231 | m_Icon: {fileID: 0} 232 | m_NavMeshLayer: 0 233 | m_StaticEditorFlags: 0 234 | m_IsActive: 1 235 | --- !u!81 &963194226 236 | AudioListener: 237 | m_ObjectHideFlags: 0 238 | m_CorrespondingSourceObject: {fileID: 0} 239 | m_PrefabInstance: {fileID: 0} 240 | m_PrefabAsset: {fileID: 0} 241 | m_GameObject: {fileID: 963194225} 242 | m_Enabled: 1 243 | --- !u!20 &963194227 244 | Camera: 245 | m_ObjectHideFlags: 0 246 | m_CorrespondingSourceObject: {fileID: 0} 247 | m_PrefabInstance: {fileID: 0} 248 | m_PrefabAsset: {fileID: 0} 249 | m_GameObject: {fileID: 963194225} 250 | m_Enabled: 1 251 | serializedVersion: 2 252 | m_ClearFlags: 1 253 | m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} 254 | m_projectionMatrixMode: 1 255 | m_GateFitMode: 2 256 | m_FOVAxisMode: 0 257 | m_SensorSize: {x: 36, y: 24} 258 | m_LensShift: {x: 0, y: 0} 259 | m_FocalLength: 50 260 | m_NormalizedViewPortRect: 261 | serializedVersion: 2 262 | x: 0 263 | y: 0 264 | width: 1 265 | height: 1 266 | near clip plane: 0.3 267 | far clip plane: 1000 268 | field of view: 60 269 | orthographic: 0 270 | orthographic size: 5 271 | m_Depth: -1 272 | m_CullingMask: 273 | serializedVersion: 2 274 | m_Bits: 4294967295 275 | m_RenderingPath: -1 276 | m_TargetTexture: {fileID: 0} 277 | m_TargetDisplay: 0 278 | m_TargetEye: 3 279 | m_HDR: 1 280 | m_AllowMSAA: 1 281 | m_AllowDynamicResolution: 0 282 | m_ForceIntoRT: 0 283 | m_OcclusionCulling: 1 284 | m_StereoConvergence: 10 285 | m_StereoSeparation: 0.022 286 | --- !u!4 &963194228 287 | Transform: 288 | m_ObjectHideFlags: 0 289 | m_CorrespondingSourceObject: {fileID: 0} 290 | m_PrefabInstance: {fileID: 0} 291 | m_PrefabAsset: {fileID: 0} 292 | m_GameObject: {fileID: 963194225} 293 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 294 | m_LocalPosition: {x: 0, y: 1, z: -10} 295 | m_LocalScale: {x: 1, y: 1, z: 1} 296 | m_Children: [] 297 | m_Father: {fileID: 0} 298 | m_RootOrder: 0 299 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 300 | --- !u!114 &963194229 301 | MonoBehaviour: 302 | m_ObjectHideFlags: 0 303 | m_CorrespondingSourceObject: {fileID: 0} 304 | m_PrefabInstance: {fileID: 0} 305 | m_PrefabAsset: {fileID: 0} 306 | m_GameObject: {fileID: 963194225} 307 | m_Enabled: 1 308 | m_EditorHideFlags: 0 309 | m_Script: {fileID: 11500000, guid: a0e6c315ab1cc4dc18b631e52669f0c5, type: 3} 310 | m_Name: 311 | m_EditorClassIdentifier: 312 | -------------------------------------------------------------------------------- /Assets/Scenes/SampleScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9fc0d4010bbf28b4594072e72b8655ab 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Assets/StreamingAssets.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 741c7538e19a94c53b4fb10778c6d2bf 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/StreamingAssets/Assembly-CSharp.patch.bytes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandin/InjectFixSample/bf7f93dcef1d9982f406d1b266925aef29883682/Assets/StreamingAssets/Assembly-CSharp.patch.bytes -------------------------------------------------------------------------------- /Assets/StreamingAssets/Assembly-CSharp.patch.bytes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b35ccd636023046d4a4f74f3b8fdbba3 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandin/InjectFixSample/bf7f93dcef1d9982f406d1b266925aef29883682/Docs/.DS_Store -------------------------------------------------------------------------------- /Docs/1_Introduction.md: -------------------------------------------------------------------------------- 1 | # InjectFix实现原理(一) - 如何使用 2 | 3 | ​ 4 | 5 | # 简介 6 | 7 | InjectFix是腾讯开源的Unity C#热更新解决方案。本文主要介绍InjectFix的相关内容,从手把手的一个例子来介绍如何使用InjectFix,一直到阅读源码来分析它的内部实现原理。 8 | 9 | 项目主页: 10 | 11 | [https://github.com/Tencent/InjectFix](https://github.com/Tencent/InjectFix) 12 | 13 | 原理介绍(原作者): 14 | 15 | [https://www.oschina.net/news/109803/injectfix-opensource](https://www.oschina.net/news/109803/injectfix-opensource) 16 | 17 | ​ 18 | 19 | # 如何使用InjectFix 20 | 21 | 这里我们会从一个空项目开始,介绍如何使用InjectFix。并根据这个例子做引子来进行它的原理分析。 22 | 23 | 本文的例子的源码都在:[https://github.com/sandin/InjectFixSample](https://github.com/sandin/InjectFixSample) 24 | 25 | 这里InjectFix的使用说明主要是参考Github上面的官方帮忙文档: 26 | 27 | [https://github.com/Tencent/InjectFix/blob/master/Doc/quick_start.md](https://github.com/Tencent/InjectFix/blob/master/Doc/quick_start.md) 28 | 29 | 本例中使用的开发环境如下: 30 | 31 | - macOS Big Sur 11.1 32 | - Unity 2019.4.17f1c1 (安装目录:/Applications/Unity/Hub/Editor/2019.4.17f1c1/Unity.app) 33 | 34 | ​ 35 | 36 | ## 接入InjectFix 37 | 38 | 第一步就是讲InjectFix的源码clone到本地: 39 | 40 | ```csharp 41 | $ git clone git@github.com:Tencent/InjectFix.git 42 | ``` 43 | 44 | 然后准备开始编译源码,windows环境的编译脚本为 build_for_unity.bat ,Mac环境为 build_for_unity.sh ,需要先修改该编译脚本的UNITY_HOME值,将其修改为本机Unity编辑器的安装目录。 45 | 46 | 例如本例中我们修改 build_for_unity.sh 中的 47 | `UNITY_HOME="/Applications/Unity/Hub/Editor/2019.4.17f1c1/Unity.app"` 48 | 49 | 然后执行编译脚本即可开始编译。 50 | 51 | ```csharp 52 | $ cd InjectFix/VsProj 53 | $ ./build_for_unity.sh 54 | ``` 55 | 56 | 这个编译脚本会使用Unity自带的Mono编译器,将源码中的一些CS脚本进行编译,并生成一些CS脚本,最后编译出IFix的核心库 `IFix.Core.dll` ,这个库就是唯一需要接入到项目中去的热更新库。 57 | 58 | 编译成功后会生成如下几个文件: 59 | 60 | - Source/UnityProj/Assets/Plugins/IFix.Core.dll 61 | - Source/UnityProj/IFixToolkit/IFix.exe 62 | - Source/UnityProj/IFixToolkit/IFix.exe.mdb 63 | - Source/VSProj/Instruction.cs 64 | - Source/VSProj/ShuffleInstruction.exe 65 | 66 | 接下来我们创建一个新的项目,并将InjectFix的如下文件夹拷贝到我们的项目根目录。 67 | 68 | - 项目根目录 69 | - IFixToolKit ← InjectFix/Source/UnityProj/IFixToolKit 70 | - Assets 71 | - IFix ← InjectFix/Source/UnityProj/Assets/IFix 72 | - Plugins ← InjectFix/Source/UnityProj/Assets/Plugins 73 | 74 | 拷贝后则会发现Unity编辑器的菜单栏增加了 【InjectFix】菜单。 75 | 76 | 然后我们新建一个C#脚本文件,作为热更新的实验,代码如下: 77 | 78 | ```csharp 79 | using System.Collections; 80 | using System.Collections.Generic; 81 | using System.IO; 82 | using IFix; 83 | using IFix.Core; 84 | using UnityEngine; 85 | 86 | public class NewBehaviourScript : MonoBehaviour 87 | { 88 | void Start() 89 | { 90 | string text = Path.Combine(Application.streamingAssetsPath, "Assembly-CSharp.patch.bytes"); 91 | bool flag = File.Exists(text); 92 | if (flag) 93 | { 94 | Debug.Log("Load HotFix, patchPath=" + text); 95 | PatchManager.Load(new FileStream(text, FileMode.Open), true); 96 | } 97 | } 98 | 99 | void Update() 100 | { 101 | } 102 | 103 | void OnGUI() 104 | { 105 | if (GUI.Button(new Rect((Screen.width - 200) / 2, 20, 200, 100), "Call FuncA")) 106 | { 107 | Debug.Log("Button, Call FuncA, result=" + FuncA()); 108 | } 109 | } 110 | 111 | public string FuncA() 112 | { 113 | return "Old"; 114 | } 115 | } 116 | ``` 117 | 118 | 然后通过提供Config文件,告诉IFix我们可能需要热更新的类有哪些(必须放到Editor目录下)。 119 | 120 | ```csharp 121 | using System; 122 | using System.Collections.Generic; 123 | using IFix; 124 | 125 | [Configure] 126 | public class InterpertConfig 127 | { 128 | [IFix] 129 | static IEnumerable ToProcess 130 | { 131 | get 132 | { 133 | return new List() { 134 | typeof(NewBehaviourScript), 135 | }; 136 | } 137 | } 138 | } 139 | ``` 140 | 141 | 正常运行程序,点击按钮,会看到控制台输出 `FuncA` 的返回值为字符串 `Old` . 142 | 143 | Unity会将我们的C#代码编译成DLL文件,路径为:`\Library\ScriptAssemblies\Assembly-CSharp.dll`。此时这个DLL文件是还未进行任何插桩修改的,也就是暂时还没有热更新能力的。 144 | 145 | 146 | 147 | 在正式打包之前需要运行编辑器菜单 【InjectFix】-【Inject】来对我们的DLL进行自动插桩。(注意编辑器需要处在非运行状态才可进行注入)。 148 | 149 | 运行这个菜单工具后,这时IFix会根据我们提供的Config文件去给这些注册的类里面的每个方法插桩,它会直接修改 `\Library\ScriptAssemblies\Assembly-CSharp.dll` 这个文件,正常注入后即可得到一个拥有热更新能力的DLL文件。 150 | 151 | ​ 152 | 153 | ## 生成补丁 154 | 155 | 在打包完成后,例如需要对某个函数进行热修复,那么我们需要来制作补丁。 156 | 157 | 例如我们如下函数进行修复,将FuncA的返回值从 "Old" 修改为 ”New“,那么需要将需要打补丁的函数打上 `[Patch]` 的注解来告诉IFix我们希望给该函数打补丁。 158 | 159 | ```csharp 160 | public class NewBehaviourScript : MonoBehaviour 161 | { 162 | [Patch] 163 | public string FuncA() 164 | { 165 | return "New"; 166 | } 167 | } 168 | ``` 169 | 170 | 然后运行编辑器菜单 【InjectFix】-【Fix】来对生成补丁,生成的补丁会保存在项目根目录的,文件名为: `Assembly-CSharp.patch.bytes`, 这是一个二进制的il字节码。 171 | 172 | 将补丁文件移动到我们想要放置补丁的目录下,使用如下代码即可自动加载和应用这些补丁: 173 | 174 | ```csharp 175 | string text = Path.Combine(Application.streamingAssetsPath, "Assembly-CSharp.patch.bytes"); 176 | bool flag = File.Exists(text); 177 | if (flag) 178 | { 179 | Debug.Log("Load HotFix, patchPath=" + text); 180 | PatchManager.Load(new FileStream(text, FileMode.Open), true); 181 | } 182 | ``` 183 | 184 | 为了在编辑器里面实验,这里我们需要把代码回滚一下,回复到补丁之前的版本来验证热更新是否有效,如下: 185 | 186 | ```csharp 187 | public class NewBehaviourScript : MonoBehaviour 188 | { 189 | public string FuncA() 190 | { 191 | return "Old"; 192 | } 193 | } 194 | ``` 195 | 196 | 这时在编辑器里运行,我们会发现控制台输出 FuncA 函数的输出值为 `Old` 。 197 | 198 | 然后我们再次点击菜单 【InjectFix】- 【Inject】 来进行插桩,再次运行则会发现控制台的输出会变成 `New` : 199 | 200 | ```csharp 201 | Load HotFix, patchPath=/Users/liudingsan/project/unity/IFixTest/IFixTest/Assets/StreamingAssets/Assembly-CSharp.patch.bytes 202 | Button, Call FuncA, result=New 203 | ``` 204 | 205 | 这里我们就成功的使用InjectFix进行了C#代码的热更新。接下来我们会深入源码中来了解InjectFix的具体实现原理。 206 | -------------------------------------------------------------------------------- /Docs/2_Inject.md: -------------------------------------------------------------------------------- 1 | # InjectFix实现原理(二) - 自动插桩 2 | 3 | ​ 4 | 5 | # 原理分析 6 | 7 | IFix的原理主要包括两个部分: 8 | 9 | 1. 自动插桩,首先在代码里面插桩,进入这些的函数的时候判断是否需要热更新,如果需要则直接跳转去执行热更新补丁中的IL指令。 10 | 2. 生成补丁,将需要热更新的代码生成为IL指令。 11 | 12 | 技术难点在于去实现一个IL运行时的虚拟机,支持所有的IL指令。 13 | 14 | ​ 15 | 16 | # 自动插桩 17 | 18 | 插桩的入口在菜单 【InjectFix】-【Inject】,源码在:Source/UnityProj/Assets/IFix/Editor/ILFixEditor.cs 19 | 20 | ```cpp 21 | [MenuItem("InjectFix/Inject", false, 1)] 22 | public static void InjectAssemblys() 23 | { 24 | if (EditorApplication.isCompiling || Application.isPlaying) 25 | { 26 | UnityEngine.Debug.LogError("compiling or playing"); 27 | return; 28 | } 29 | EditorUtility.DisplayProgressBar("Inject", "injecting...", 0); 30 | try 31 | { 32 | InjectAllAssemblys(); 33 | } 34 | catch(Exception e) 35 | { 36 | UnityEngine.Debug.LogError(e); 37 | } 38 | EditorUtility.ClearProgressBar(); 39 | } 40 | ``` 41 | 42 | `InjectAllAssemblys` 对 `./Library/ScriptAssemblies` 目录下的两个dll文件进行注入: 43 | 44 | - Assembly-CSharp.dll 45 | - Assembly-CSharp-firstpass.dll 46 | 47 | ```csharp 48 | /// 49 | /// 对指定的程序集注入 50 | /// 51 | /// 程序集路径 52 | public static void InjectAssembly(string assembly) 53 | { 54 | } 55 | ``` 56 | 57 | 反编译它可以看到它给原代码进行了插桩,修改如下: 58 | 59 | ```csharp 60 | public class NewBehaviourScript2 : MonoBehaviour 61 | { 62 | private void Start() 63 | { 64 | if (WrappersManagerImpl.IsPatched(16)) 65 | { 66 | WrappersManagerImpl.GetPatch(16).__Gen_Wrap_0(this); 67 | return; 68 | } 69 | string text = Path.Combine(Application.streamingAssetsPath, "Assembly-CSharp.patch.bytes"); 70 | bool flag = File.Exists(text); 71 | if (flag) 72 | { 73 | Debug.Log("Load HotFix, patchPath=" + text); 74 | PatchManager.Load(new FileStream(text, FileMode.Open), true); 75 | } 76 | } 77 | 78 | private void Update() 79 | { 80 | if (WrappersManagerImpl.IsPatched(17)) 81 | { 82 | WrappersManagerImpl.GetPatch(17).__Gen_Wrap_0(this); 83 | return; 84 | } 85 | } 86 | 87 | private void OnGUI() 88 | { 89 | if (WrappersManagerImpl.IsPatched(18)) 90 | { 91 | WrappersManagerImpl.GetPatch(18).__Gen_Wrap_0(this); 92 | return; 93 | } 94 | bool flag = GUI.Button(new Rect((float)((Screen.width - 200) / 2), 20f, 200f, 100f), "Call FuncA"); 95 | if (flag) 96 | { 97 | Debug.Log("Button, Call FuncA, result=" + this.FuncA()); 98 | } 99 | } 100 | 101 | public string FuncA() 102 | { 103 | if (WrappersManagerImpl.IsPatched(19)) 104 | { 105 | return WrappersManagerImpl.GetPatch(19).__Gen_Wrap_5(this); 106 | } 107 | return "Old"; 108 | } 109 | } 110 | ``` 111 | 112 | 可以看到每个函数都增加一个if判断的插桩,用来判断这个方法是否需要热更新的版本,如果有则直接跳转去执行热更新的代码,否则正常执行该方法的原代码。 113 | 114 | ```csharp 115 | if (WrappersManagerImpl.IsPatched(19)) 116 | { 117 | return WrappersManagerImpl.GetPatch(19).__Gen_Wrap_5(this); 118 | } 119 | ``` 120 | 121 | 其中判断是否有patch以及获取patch都是由IFix生成的代码来实现的,如下:(生成这段代码的源码在:[https://github.com/Tencent/InjectFix/blob/master/Source/VSProj/Src/Tools/CodeTranslator.cs](https://github.com/Tencent/InjectFix/blob/master/Source/VSProj/Src/Tools/CodeTranslator.cs)) 122 | 123 | ```csharp 124 | namespace IFix 125 | { 126 | public class WrappersManagerImpl : WrappersManager 127 | { 128 | public static bool IsPatched(int id) 129 | { 130 | return id < ILFixDynamicMethodWrapper.wrapperArray.Length && ILFixDynamicMethodWrapper.wrapperArray[id] != null; 131 | } 132 | 133 | public static ILFixDynamicMethodWrapper GetPatch(int id) 134 | { 135 | return ILFixDynamicMethodWrapper.wrapperArray[id]; 136 | } 137 | } 138 | } 139 | ``` 140 | 141 | 调用patch的代码,实现如下: 142 | 143 | ```csharp 144 | namespace IFix 145 | { 146 | public class ILFixDynamicMethodWrapper 147 | { 148 | public string __Gen_Wrap_5(object P0) 149 | { 150 | Call call = Call.Begin(); 151 | if (this.anonObj != null) 152 | { 153 | call.PushObject(this.anonObj); 154 | } 155 | call.PushObject(P0); 156 | this.virtualMachine.Execute(this.methodId, ref call, (this.anonObj != null) ? 2 : 1, 0); 157 | return call.GetAsType(0); 158 | } 159 | 160 | private VirtualMachine virtualMachine; 161 | } 162 | } 163 | ``` 164 | 165 | 这里我们看到热更新的逻辑就是将参数入栈,然后调用IFix实现的il虚拟机( `VirtualMachine` ) 来执行这个函数。 166 | 167 | 这里的`VirtualMachine`是由接入项目中的 `Assets\Plugins\IFix.Core.dll` 提供的,源码在:[https://github.com/Tencent/InjectFix/blob/master/Source/VSProj/Src/Core/VirtualMachine.cs](https://github.com/Tencent/InjectFix/blob/master/Source/VSProj/Src/Core/VirtualMachine.cs) 168 | 169 | 这个`VirtualMachine`虚拟机是由加载补丁的时候 `PatchManager.Load`函数创建的: 170 | 171 | ```csharp 172 | public static class PatchManager 173 | { 174 | unsafe static public VirtualMachine Load(Stream stream, bool checkNew = true) 175 | { 176 | 177 | // ... 178 | // stream是二进制的补丁,里面放着热更新代码的IL指令,该二进制文件格式参考后面章节 179 | BinaryReader reader = new BinaryReader(stream); 180 | // 这里会将二进制的补丁文件的所有热更新的方法定义及IL指令都读出来 181 | // 并把所有指令都保存到unmanagedCodes变量中,传给 VirtualMachine 构造函数。 182 | unmanagedCodes = (Instruction**)nativePointer.ToPointer(); 183 | 184 | var virtualMachine = new VirtualMachine(unmanagedCodes, () => 185 | { 186 | for (int i = 0; i < nativePointers.Count; i++) 187 | { 188 | System.Runtime.InteropServices.Marshal.FreeHGlobal(nativePointers[i]); 189 | } 190 | }) 191 | { 192 | ExternTypes = externTypes, 193 | ExternMethods = externMethods, 194 | ExceptionHandlers = exceptionHandlers.ToArray(), 195 | InternStrings = internStrings, 196 | FieldInfos = fieldInfos, 197 | AnonymousStoreyInfos = anonymousStoreyInfos, 198 | StaticFieldTypes = staticFieldTypes, 199 | Cctors = cctors 200 | }; 201 | // ... 202 | } 203 | } 204 | ``` 205 | 206 | 创建虚拟机方法如下: 207 | 208 | ```csharp 209 | internal VirtualMachine(Instruction** unmanaged_codes, Action on_dispose); 210 | ``` 211 | 212 | - 参数1: 热修复的所有函数及其IL指令。 213 | - 参数2:当虚拟机被消耗时,用于释放相关内存的析构函数。 214 | 215 | 执行热更新的代码,主要通过调用 `VirtualMachine` 的 `Execute` 函数来实现的,这个方法会直接去执行热更新补丁中这个函数的IL指令: 216 | 217 | ```csharp 218 | public void Execute(int methodIndex, ref Call call, int argsCount, int refCount = 0) 219 | { 220 | Execute(unmanagedCodes[methodIndex], call.argumentBase + refCount, call.managedStack, 221 | call.evaluationStackBase, argsCount, methodIndex, refCount, call.topWriteBack); 222 | } 223 | 224 | public Value* Execute(Instruction* pc, Value* argumentBase, object[] managedStack, 225 | Value* evaluationStackBase, int argsCount, int methodIndex, 226 | int refCount = 0, Value** topWriteBack = null) 227 | { 228 | // ... 229 | } 230 | ``` 231 | 232 | 这里传参的pc就直接是热更新代码的IL指令,关于IL的说明可查看wiki:[https://en.wikipedia.org/wiki/Common_Intermediate_Language](https://en.wikipedia.org/wiki/Common_Intermediate_Language) 233 | -------------------------------------------------------------------------------- /Docs/3_Fix.md: -------------------------------------------------------------------------------- 1 | # InjectFix实现原理(三) - 生成补丁 2 | 3 | 在本节中,我们主要跟着例子去阅读iFix的源码,看看iFix是如何去生成二进制的补丁文件,其中包括了所有热更新所需的函数及其IL指令。 4 | 5 | 它的主要原理是用Mono.cecil这个库去反编译出DLL里面的代码(IL指令),然后去找到标记为需要热更新的函数,并将这些函数里面的代码都转成iFix自己实现的虚拟机支持的IL指令,然后将IL指令都保存成二进制的热更新补丁文件。 6 | 7 | ​ 8 | 9 | ## 生成补丁 10 | 11 | ### GenPatch 12 | 13 | 点击菜单【InjectFix】-【Fix】来生成补丁,源码在:`Assets/IFix/Editor/ILFixEditor.cs` 14 | 15 | ```csharp 16 | [MenuItem("InjectFix/Fix", false, 2)] 17 | public static void Patch() 18 | { 19 | EditorUtility.DisplayProgressBar("Generate Patch for Edtior", "patching...", 0); 20 | try 21 | { 22 | foreach (var assembly in injectAssemblys) 23 | { 24 | var assembly_path = string.Format("./Library/{0}/{1}.dll", GetScriptAssembliesFolder(), assembly); 25 | GenPatch(assembly, assembly_path, "./Assets/Plugins/IFix.Core.dll", 26 | string.Format("{0}.patch.bytes", assembly)); 27 | } 28 | } 29 | catch (Exception e) 30 | { 31 | UnityEngine.Debug.LogError(e); 32 | } 33 | EditorUtility.ClearProgressBar(); 34 | } 35 | ``` 36 | 37 | 这里和 `Inject` 菜单命令一样,会去遍历两个dll,然后调用 `GenPatch` 方法来生成补丁文件: 38 | 39 | * Assembly-CSharp.dll 40 | 41 | * Assembly-CSharp-firstpass.dll 42 | 43 | 44 | 45 | GenPatch函数生成patch的逻辑如下: 46 | 47 | ```csharp 48 | /// 49 | /// 生成patch 50 | /// 51 | /// 程序集名,用来过滤配置 52 | /// 程序集路径 53 | /// IFix.Core.dll所在路径 54 | /// 生成的patch的保存路径 55 | public static void GenPatch(string assembly, string assemblyCSharpPath 56 | = "./Library/ScriptAssemblies/Assembly-CSharp.dll", 57 | string corePath = "./Assets/Plugins/IFix.Core.dll", string patchPath = "Assembly-CSharp.patch.bytes") 58 | { 59 | // 查找需要生成补丁的函数列表(即打了[Patch]注解的函数) 60 | var patchMethods = Configure.GetTagMethods(typeof(PatchAttribute), assembly).ToList(); 61 | // 不支持泛型的方法 62 | var genericMethod = patchMethods.FirstOrDefault(m => hasGenericParameter(m)); 63 | if (genericMethod != null) 64 | { 65 | throw new InvalidDataException("not support generic method: " + genericMethod); 66 | } 67 | 68 | if (patchMethods.Count == 0) 69 | { 70 | return; 71 | } 72 | 73 | // 查找需要新增的函数列表(即打了[Interpret]注解的函数或类) 74 | var newMethods = Configure.GetTagMethods(typeof(InterpretAttribute), assembly).ToList(); 75 | var newClasses = Configure.GetTagClasses(typeof(InterpretAttribute), assembly).ToList(); 76 | genericMethod = newMethods.FirstOrDefault(m => hasGenericParameter(m)); 77 | if (genericMethod != null) 78 | { 79 | throw new InvalidDataException("not support generic method: " + genericMethod); 80 | } 81 | 82 | // 将找到的这些需要打补丁或需要新增的函数和类写入到cfg文件中 83 | var processCfgPath = "./process_cfg"; 84 | 85 | using (BinaryWriter writer = new BinaryWriter(new FileStream(processCfgPath, FileMode.Create, 86 | FileAccess.Write))) 87 | { 88 | writeMethods(writer, patchMethods); 89 | writeMethods(writer, newMethods); 90 | writeClasses(writer, newClasses); 91 | } 92 | 93 | List args = new List() { "-patch", corePath, assemblyCSharpPath, "null", 94 | processCfgPath, patchPath }; 95 | 96 | foreach (var path in 97 | (from asm in AppDomain.CurrentDomain.GetAssemblies() 98 | select Path.GetDirectoryName(asm.ManifestModule.FullyQualifiedName)).Distinct()) 99 | { 100 | try 101 | { 102 | //UnityEngine.Debug.Log("searchPath:" + path); 103 | args.Add(path); 104 | } 105 | catch { } 106 | } 107 | 108 | // 然后调用 IFix.exe来生成补丁文件 109 | CallIFix(args); 110 | 111 | File.Delete(processCfgPath); 112 | 113 | AssetDatabase.Refresh(); 114 | } 115 | ``` 116 | 117 | 这个函数主要是查找DLL中哪些函数是需要打补丁的,即拥有IFix的注解: 118 | 119 | * `[IFix.patch]` : 需要打补丁的方法。 120 | * `[IFix.Interpret]` : 需要新增的属性,方法或类。 121 | 122 | 然后将这些需要打入到补丁的函数列表写入到一个名为 `process_cfg` 的临时配置文件,作为参数传给 `IFixToolKit/IFix.exe` 进行生成补丁。 123 | 124 | ​ 125 | 126 | ### IFix.exe 127 | 128 | `IFixToolKit/IFix.exe` 程序是运行 `build_for_unity.sh` 脚本后编译生成的。 129 | 130 | 从编译脚本来看,这个exe的源码主要在 InjectFix/Src/Tools 目录下的CS文件,以及动态生成出来的 `Instruction.cs` 代码文件。并且依赖了Mono.Cecil.dll库。Mono.Cecil库是Mono的一个开源库,用来读取DLL文件,查找所有的类,并且可以修改它们,再将其保存到新的DLL文件。详情可参考项目主页:https://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/。 131 | 132 | IFix.exe 这个工具的使用说明为: 133 | 134 | * IFix -**inject** core_assmbly_path assmbly_path config_path patch_file_output_path injected_assmbly_output_path [search_path1, search_path2 ...] 135 | * IFix -**inherit_inject** core_assmbly_path assmbly_path config_path patch_file_output_path injected_assmbly_output_path inherit_assmbly_path 136 | * IFix -**patch** core_assmbly_path assmbly_path injected_assmbly_path config_path patch_file_output_path [search_path1, search_path2 ...] 137 | 138 | 该工具支持 Inject注入 和 生成Patch 两个功能,例如这里我们用来生成Patch的实际运行的命令及传参如下: 139 | 140 | ```bash 141 | /Applications/Unity/Hub/Editor/2019.4.17f1c1/Unity.app/Contents/Managed/UnityEngine/../../MonoBleedingEdge/bin/mono --debug --runtime=v4.0.30319 ./IFixToolKit/IFix.exe 142 | 143 | # arg[0]: command 命令 144 | -patch 145 | 146 | # arg[1]: core_assmbly_path IFix核心DLL库 147 | ./Assets/Plugins/IFix.Core.dl 148 | 149 | # arg[2]: injected_assmbly_path 被注入的DLL库 150 | ./Library/ScriptAssemblies/Assembly-CSharp.dll 151 | null 152 | 153 | # arg[3]: config_path 配置文件(生成的) 154 | ./process_cfg 155 | 156 | # arg[4]: patch_file_output_path 补丁的输出路径 157 | Assembly-CSharp.patch.bytes 158 | 159 | # search_path 搜索目录 160 | /Applications/Unity/Hub/Editor/2019.4.17f1c1/Unity.app/Contents/MonoBleedingEdge/lib/mono/unityjit 161 | /Applications/Unity/Hub/Editor/2019.4.17f1c1/Unity.app/Contents/Managed/UnityEngine 162 | /Applications/Unity/Hub/Editor/2019.4.17f1c1/Unity.app/Contents/Managed 163 | /Applications/Unity/Hub/Editor/2019.4.17f1c1/Unity.app/Contents/UnityExtensions/Unity/UnityVR/Editor 164 | /Applications/Unity/Hub/Editor/2019.4.17f1c1/PlaybackEngines/WindowsStandaloneSupport 165 | /Applications/Unity/Hub/Editor/2019.4.17f1c1/Unity.app/Contents/PlaybackEngines/MacStandaloneSupport 166 | /Applications/Unity/Hub/Editor/2019.4.17f1c1/PlaybackEngines/AndroidPlayer 167 | /Applications/Unity/Hub/Editor/2019.4.17f1c1/PlaybackEngines/iOSSupport 168 | /Applications/Visual Studio.app/Contents/Resources/lib/monodeve… 169 | ``` 170 | 171 | IFix.exe 这个工具的入口在 `InjectFix/Src/Tools/CSFix.cs` 源码中的 `Main` 函数。 172 | 173 | ```csharp 174 | static void Main(string[] args) 175 | { 176 | CodeTranslator tranlater = new CodeTranslator(); 177 | AssemblyDefinition assembly = null; 178 | 179 | //尝试读取符号 180 | assembly = AssemblyDefinition.ReadAssembly(assmeblyPath, 181 | new ReaderParameters { ReadSymbols = true }); 182 | 183 | var resolver = assembly.MainModule.AssemblyResolver as BaseAssemblyResolver; 184 | resolver.AddSearchDirectory(path); 185 | 186 | ilfixAassembly = AssemblyDefinition.ReadAssembly(args[1]); 187 | 188 | if (mode == ProcessMode.Inject) 189 | { 190 | // 注入逻辑 191 | tranlater.Process(assembly, ilfixAassembly, configure, mode); 192 | } 193 | else 194 | { 195 | // 补丁生成流程 196 | configure = new PatchGenerateConfigure(assembly, args[4] /* patch_file_output_path */); 197 | tranlater.Process(assembly, ilfixAassembly, configure, mode); 198 | 199 | tranlater.Serialize(args[5] /* patch_file_output_path */); 200 | } 201 | } 202 | ``` 203 | 204 | 这里 `AssemblyDefinition` 是`Mono.Cecil` 库提供的,用来读取一个Assembly(DLL文件),并且先会尝试带符号的去读取,如果没有符号再使用无符号的方式读取DLL文件,最后交给 `CodeTranslator` 类的 `Process` 方法来进行注入或者打补丁的操作。 205 | 206 | `Source/VSProj/Src/Tools/CodeTranslator.cs` 207 | 208 | ```csharp 209 | public ProcessResult Process(AssemblyDefinition assembly, AssemblyDefinition ilfixAassembly, 210 | GenerateConfigure configure, ProcessMode mode) 211 | { 212 | // ... 213 | 214 | init(assembly, ilfixAassembly); 215 | 216 | emitWrapperManager(); 217 | 218 | // 从DLL中查找到所有类,除了IFix命名空间下的,泛型的,编译器生成的,以及补丁新增的类 219 | var allTypes = (from type in assembly.GetAllType() 220 | where type.Namespace != "IFix" && !type.IsGeneric() && !(isCompilerGenerated(type) || isNewClass(type)) 221 | select type); 222 | 223 | // 遍历所有类的所有方法,除了构造器,编译器生成的,有泛型,以及返回值的类型带IsRequired修饰符的? 224 | foreach (var method in ( 225 | from type in allTypes 226 | where !(isCompilerGenerated(type) || isNewClass(type)) && !type.HasGenericParameters 227 | from method in type.Methods 228 | where !method.IsConstructor && !isCompilerGenerated(method) && !method.HasGenericParameters && !method.ReturnType.IsRequiredModifier 229 | select method)) 230 | { 231 | int flag; 232 | // [IFix.Interpret] 标记的新增的函数 233 | if (configure.TryGetConfigure("IFix.InterpretAttribute", method, out flag)) 234 | { 235 | methodToInjectType[method] = InjectType.Redirect; 236 | hasRedirect = true; 237 | } 238 | // Configure文件,标记[IFix.IFix]的函数,用来返回可能打补丁的函数列表 239 | else if(configure.TryGetConfigure("IFix.IFixAttribute", method, out flag)) 240 | { 241 | methodToInjectType[method] = InjectType.Switch; 242 | } 243 | } 244 | 245 | // 处理要生成补丁的函数 246 | foreach(var kv in methodToInjectType) 247 | { 248 | processMethod(kv.Key); 249 | } 250 | 251 | genCodeForCustomBridge(); 252 | 253 | emitCCtor(); 254 | 255 | postProcessInterfaceBridge(); 256 | 257 | if (mode == ProcessMode.Inject) 258 | { 259 | redirectFieldRename(); 260 | if (awaitUnsafeOnCompletedMethods.Count != 0) 261 | { 262 | EmitRefAwaitUnsafeOnCompletedMethod(); 263 | } 264 | } 265 | 266 | return ProcessResult.OK; 267 | } 268 | 269 | void init(AssemblyDefinition assembly, AssemblyDefinition ilfixAassembly) 270 | { 271 | this.assembly = assembly; 272 | // System.Object类及其公共方法 273 | objType = assembly.MainModule.TypeSystem.Object; 274 | List supportedMethods = new List() { "Equals", "Finalize","GetHashCode", "ToString"}; 275 | ObjectVirtualMethodDefinitionList = (from method in objType.Resolve().Methods where method.IsVirtual && supportedMethods.Contains(method.Name) select method).ToList(); 276 | if (ObjectVirtualMethodDefinitionList.Count != 4) 277 | { 278 | throw new InvalidProgramException(); // Object的公共方法不包括这4个,视为非法的程序集 279 | } 280 | ObjectVirtualMethodDefinitionList.OrderBy(t => t.FullName); 281 | for (int methodIdx = 0; methodIdx < ObjectVirtualMethodDefinitionList.Count; methodIdx++) 282 | { 283 | virtualMethodToIndex.Add(ObjectVirtualMethodDefinitionList[methodIdx], methodIdx); 284 | } 285 | // System.Void类 286 | voidType = assembly.MainModule.TypeSystem.Void; 287 | 288 | // 新增一个类的定义,名为:ILFixDynamicMethodWrapper 289 | wrapperType = new TypeDefinition("IFix", DYNAMICWRAPPER /* ILFixDynamicMethodWrapper */, Mono.Cecil.TypeAttributes.Class 290 | | Mono.Cecil.TypeAttributes.Public, objType); 291 | assembly.MainModule.Types.Add(wrapperType); 292 | 293 | // 从IFix.Core.dll中找到VirtualMachine类和WrappersManager类 294 | TypeDefinition VirtualMachine; 295 | VirtualMachine = ilfixAassembly.MainModule.Types.Single(t => t.Name == "VirtualMachine"); 296 | VirtualMachineType = assembly.MainModule.ImportReference(VirtualMachine); 297 | WrappersManagerType = assembly.MainModule.ImportReference( 298 | ilfixAassembly.MainModule.Types.Single(t => t.Name == "WrappersManager")); 299 | 300 | // 给 ILFixDynamicMethodWrapper 添加一些成员成员: 301 | // private VirtualMachine virtualMachine; 302 | // private int methodId; 303 | // private object anonObj; 304 | // public static ILFixDynamicMethodWrapper[] wrapperArray = new ILFixDynamicMethodWrapper[0]; 305 | virualMachineFieldOfWrapper = new FieldDefinition("virtualMachine", Mono.Cecil.FieldAttributes.Private, 306 | VirtualMachineType); 307 | wrapperType.Fields.Add(virualMachineFieldOfWrapper); 308 | methodIdFieldOfWrapper = new FieldDefinition("methodId", Mono.Cecil.FieldAttributes.Private, 309 | assembly.MainModule.TypeSystem.Int32); 310 | wrapperType.Fields.Add(methodIdFieldOfWrapper); 311 | anonObjOfWrapper = new FieldDefinition("anonObj", Mono.Cecil.FieldAttributes.Private, 312 | objType); 313 | wrapperType.Fields.Add(anonObjOfWrapper); 314 | wrapperArray = new FieldDefinition("wrapperArray", Mono.Cecil.FieldAttributes.Public 315 | | Mono.Cecil.FieldAttributes.Static, 316 | new ArrayType(wrapperType)); 317 | wrapperType.Fields.Add(wrapperArray); 318 | 319 | idTagCtor_Ref = assembly.MainModule.ImportReference( 320 | ilfixAassembly.MainModule.Types.Single(t => t.Name == "IDTagAttribute") 321 | .Methods.Single(m => m.Name == ".ctor" && m.Parameters.Count == 1)); 322 | 323 | // 给 ILFixDynamicMethodWrapper 添加构造器: 324 | // public ILFixDynamicMethodWrapper(VirtualMachine virtualMachine, int methodId, object anonObj) 325 | // { 326 | // this.virtualMachine = virtualMachine; 327 | // this.methodId = methodId; 328 | // this.anonObj = anonObj; 329 | // } 330 | var objEmptyConstructor = assembly.MainModule.ImportReference(objType.Resolve().Methods. 331 | Single(m => m.Name == ".ctor" && m.Parameters.Count == 0)); 332 | var methodAttributes = MethodAttributes.Public 333 | | MethodAttributes.HideBySig 334 | | MethodAttributes.SpecialName 335 | | MethodAttributes.RTSpecialName; 336 | ctorOfWrapper = new MethodDefinition(".ctor", methodAttributes, voidType); 337 | ctorOfWrapper.Parameters.Add(new ParameterDefinition("virtualMachine", 338 | Mono.Cecil.ParameterAttributes.None, VirtualMachineType)); 339 | ctorOfWrapper.Parameters.Add(new ParameterDefinition("methodId", 340 | Mono.Cecil.ParameterAttributes.None, assembly.MainModule.TypeSystem.Int32)); 341 | ctorOfWrapper.Parameters.Add(new ParameterDefinition("anonObj", 342 | Mono.Cecil.ParameterAttributes.None, objType)); 343 | var instructions = ctorOfWrapper.Body.Instructions; 344 | instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); // 将索引为 0 的参数(this)加载到计算堆栈上。 345 | instructions.Add(Instruction.Create(OpCodes.Call, objEmptyConstructor)); // 调用由传递的方法说明符指示的方法。 346 | instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); // 将索引为 0 的参数(this)加载到计算堆栈上。 347 | instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); // 将索引为 1 的参数(virtualMachine)加载到计算堆栈上。 348 | instructions.Add(Instruction.Create(OpCodes.Stfld, virualMachineFieldOfWrapper)); // 用新值替换在对象引用或指针的字段中存储的值。 349 | instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); // this.methodId = methodId 350 | instructions.Add(Instruction.Create(OpCodes.Ldarg_2)); 351 | instructions.Add(Instruction.Create(OpCodes.Stfld, methodIdFieldOfWrapper)); 352 | instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); // this.anonObj = anonObj 353 | instructions.Add(Instruction.Create(OpCodes.Ldarg_3)); 354 | instructions.Add(Instruction.Create(OpCodes.Stfld, anonObjOfWrapper)); 355 | instructions.Add(Instruction.Create(OpCodes.Ret)); // 从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。 356 | wrapperType.Methods.Add(ctorOfWrapper); 357 | 358 | //begin init itfBridgeType 359 | // public class ILFixInterfaceBridge : AnonymousStorey, IDisposable, IEnumerator, IEnumerator 360 | bridgeMethodId = 0; 361 | var anonymousStoreyType = ilfixAassembly.MainModule.Types.Single(t => t.Name == "AnonymousStorey"); 362 | anonymousStoreyTypeRef = assembly.MainModule.ImportReference(anonymousStoreyType); 363 | anonymousStoreyCtorRef = assembly.MainModule.ImportReference( 364 | anonymousStoreyType.Methods.Single(m => m.Name == ".ctor" && m.Parameters.Count == 5)); 365 | 366 | objectVirtualMethodReferenceList = anonymousStoreyType.Methods.Where(m => m.Name.StartsWith("Object")). 367 | Select(m => assembly.MainModule.ImportReference(m)).ToList(); 368 | 369 | itfBridgeType = new TypeDefinition("IFix", INTERFACEBRIDGE, TypeAttributes.Class | TypeAttributes.Public, 370 | anonymousStoreyTypeRef); 371 | virualMachineFieldOfBridge = assembly.MainModule.ImportReference(anonymousStoreyType.Fields.Single(f => f.Name == "virtualMachine")); 372 | assembly.MainModule.Types.Add(itfBridgeType); 373 | addExternType(itfBridgeType); 374 | 375 | //end init itfBridgeType 376 | 377 | //begin init idMapper 378 | enumType = assembly.MainModule.ImportReference(typeof(System.Enum)); 379 | idMapList = new List(); 380 | idMapType = null; 381 | //end init idMapper 382 | 383 | wrapperMethods = new List(); 384 | 385 | TypeDefinition Call; 386 | Call = ilfixAassembly.MainModule.Types.Single(t => t.Name == "Call"); 387 | Call_Ref = assembly.MainModule.ImportReference(Call); 388 | Call_Begin_Ref = importMethodReference(Call, "Begin"); 389 | Call_PushRef_Ref = importMethodReference(Call, "PushRef"); 390 | Call_PushValueType_Ref = importMethodReference(Call, "PushValueType"); 391 | Call_GetAsType_Ref = importMethodReference(Call, "GetAsType"); 392 | 393 | VirtualMachine_Execute_Ref = assembly.MainModule.ImportReference( 394 | VirtualMachine.Methods.Single(m => m.Name == "Execute" && m.Parameters.Count == 4)); 395 | 396 | Utils_TryAdapterToDelegate_Ref = assembly.MainModule.ImportReference( 397 | ilfixAassembly.MainModule.Types.Single(t => t.FullName == "IFix.Core.Utils") 398 | .Methods.Single(m => m.Name == "TryAdapterToDelegate")); 399 | 400 | // 基础类型的IL指令对照表 401 | ldinds = new Dictionary() 402 | { 403 | {assembly.MainModule.TypeSystem.Boolean, OpCodes.Ldind_U1 }, 404 | {assembly.MainModule.TypeSystem.Byte, OpCodes.Ldind_U1 }, 405 | {assembly.MainModule.TypeSystem.SByte, OpCodes.Ldind_I1 }, 406 | {assembly.MainModule.TypeSystem.Int16, OpCodes.Ldind_I2 }, 407 | {assembly.MainModule.TypeSystem.Char, OpCodes.Ldind_U2 }, 408 | {assembly.MainModule.TypeSystem.UInt16, OpCodes.Ldind_U2 }, 409 | {assembly.MainModule.TypeSystem.Int32, OpCodes.Ldind_I4 }, 410 | {assembly.MainModule.TypeSystem.UInt32, OpCodes.Ldind_U4 }, 411 | {assembly.MainModule.TypeSystem.Int64, OpCodes.Ldind_I8 }, 412 | {assembly.MainModule.TypeSystem.UInt64, OpCodes.Ldind_I8 }, 413 | {assembly.MainModule.TypeSystem.Single, OpCodes.Ldind_R4 }, 414 | {assembly.MainModule.TypeSystem.Double, OpCodes.Ldind_R8 }, 415 | {assembly.MainModule.TypeSystem.IntPtr, OpCodes.Ldind_I }, 416 | {assembly.MainModule.TypeSystem.UIntPtr, OpCodes.Ldind_I }, 417 | }; 418 | 419 | stinds = new Dictionary() 420 | { 421 | {assembly.MainModule.TypeSystem.Boolean, OpCodes.Stind_I1 }, 422 | {assembly.MainModule.TypeSystem.Byte, OpCodes.Stind_I1 }, 423 | {assembly.MainModule.TypeSystem.SByte, OpCodes.Stind_I1 }, 424 | {assembly.MainModule.TypeSystem.Int16, OpCodes.Stind_I2 }, 425 | {assembly.MainModule.TypeSystem.Char, OpCodes.Stind_I2 }, 426 | {assembly.MainModule.TypeSystem.UInt16, OpCodes.Stind_I2 }, 427 | {assembly.MainModule.TypeSystem.Int32, OpCodes.Stind_I4 }, 428 | {assembly.MainModule.TypeSystem.UInt32, OpCodes.Stind_I4 }, 429 | {assembly.MainModule.TypeSystem.Int64, OpCodes.Stind_I8 }, 430 | {assembly.MainModule.TypeSystem.UInt64, OpCodes.Stind_I8 }, 431 | {assembly.MainModule.TypeSystem.Single, OpCodes.Stind_R4 }, 432 | {assembly.MainModule.TypeSystem.Double, OpCodes.Stind_R8 }, 433 | {assembly.MainModule.TypeSystem.IntPtr, OpCodes.Stind_I }, 434 | {assembly.MainModule.TypeSystem.UIntPtr, OpCodes.Stind_I }, 435 | }; 436 | 437 | initStackOp(Call, assembly.MainModule.TypeSystem.Boolean); 438 | initStackOp(Call, assembly.MainModule.TypeSystem.Byte); 439 | initStackOp(Call, assembly.MainModule.TypeSystem.SByte); 440 | initStackOp(Call, assembly.MainModule.TypeSystem.Int16); 441 | initStackOp(Call, assembly.MainModule.TypeSystem.UInt16); 442 | initStackOp(Call, assembly.MainModule.TypeSystem.Char); 443 | initStackOp(Call, assembly.MainModule.TypeSystem.Int32); 444 | initStackOp(Call, assembly.MainModule.TypeSystem.UInt32); 445 | initStackOp(Call, assembly.MainModule.TypeSystem.Int64); 446 | initStackOp(Call, assembly.MainModule.TypeSystem.UInt64); 447 | initStackOp(Call, assembly.MainModule.TypeSystem.Single); 448 | initStackOp(Call, assembly.MainModule.TypeSystem.Double); 449 | initStackOp(Call, assembly.MainModule.TypeSystem.Object); 450 | initStackOp(Call, assembly.MainModule.TypeSystem.IntPtr); 451 | initStackOp(Call, assembly.MainModule.TypeSystem.UIntPtr); 452 | 453 | } 454 | ``` 455 | 456 | ​ 457 | 458 | emitWrapperManager() 函数生成 WrapperManager类: 459 | 460 | ```csharp 461 | class WrapperManagerImpl { 462 | 463 | private VirtualMachine _virtualMachine; 464 | 465 | public WrapperManagerImpl(VirtualMachine vm) { 466 | super(); 467 | this._virtualMachine = vm; 468 | } 469 | 470 | public static void GetPatch(int id) { 471 | return this.wrapperArray[id]; 472 | } 473 | 474 | public static boolean IsPatched(int id) { 475 | if (this.wrapperArray.length > 0) { 476 | return this.wrapperArray[id] != null; 477 | } 478 | } 479 | } 480 | ``` 481 | 482 | ​ 483 | 484 | 处理需要打补丁的函数: 485 | 486 | ```csharp 487 | void processMethod(MethodDefinition method) 488 | { 489 | getMethodId(method, null,true); 490 | } 491 | 492 | /// 493 | /// 获取一个函数的id 494 | /// 该函数会触发指令序列生成 495 | /// 496 | /// 被调用函数 497 | /// 调用者 498 | /// 是个虚函数,会生成指令序列, 499 | /// 但是调用通过反射来调用 500 | /// 调用者的注入类型 501 | /// 负数表示需要反射访问原生,0或正数是指令数组下标 502 | // #lizard forgives 503 | unsafe MethodIdInfo getMethodId(MethodReference callee, MethodDefinition caller, bool isCallvirt, 504 | bool directCallVirtual = false, InjectType callerInjectType = InjectType.Switch) 505 | { 506 | // ... 507 | } 508 | ``` 509 | 510 | `getMethodId` 这个函数很长,逻辑比较复杂,下面我们主要以文字或伪代码的形式来说明它的具体逻辑: 511 | 512 | 首先我们需要了解到所有的方法分为如下类型: 513 | 514 | * Extern, 外部方法(原生方法,如果是dll之外的方法,或者是构造函数,析构函数,作为虚拟机之外(extern)的方法) 515 | * InteralVirtual, 内部虚方法 516 | * Internal, 内部方法 517 | * Invalid, 不支持的方法,例如含泛型方法 518 | 519 | ​ 520 | 521 | 然后在处理每一个method的,如果它不是虚函数,或者是外部函数(这里的外部是相对于IFix的虚拟机而已的),则调用 `addExternMethod` 来添加一个原生方法的引用,返回的MethodIdInfo {Type = Extern} 522 | 523 | ```c# 524 | int addExternMethod(MethodReference callee, MethodDefinition caller) 525 | { 526 | // 添加方法泛型的Type 527 | addExternType(typeArg); 528 | 529 | // 添加方法返回值的Type 530 | addExternType(callee.ReturnType); 531 | 532 | // 添加方法的类的Type 533 | addExternType(callee.DeclaringType) 534 | 535 | // 添加方法参数的Type 536 | addExternType(p.ParameterType); 537 | 538 | // 最后将其保存起来先 539 | externMethodToId[callee] = methodId; 540 | externMethods.Add(callee); 541 | } 542 | ``` 543 | 544 | 并且在依次弄完了各种Type之后,就开始处理method本身,将method转成iFix虚拟机支持的IL指令。 545 | 546 | 每个method都拥有一系列的Instruction指令集合,一般来说一个method的第一个指令如下: 547 | 548 | | Code | Operand | Note | 549 | | ---------- | ---------------- | ------------------------------------------ | 550 | | StackSpace | local \|maxstack | 该函数的局部变量个数 << 16 \| 最大堆栈大小 | 551 | 552 | 每个函数第一句都是栈的信息,包括栈上局部变量的个数,栈的大小,这是因为C#虚拟机是基于栈的。 553 | 554 | 接下面的Instruction指令,就是处理栈上的局部变量的。然后处理函数的异常Exception。 555 | 556 | 因为这里都是根据Mono.Cecil库解析出来的一个C#函数的所有信息: 557 | 558 | ```C# 559 | namespace Mono.Cecil.Cil { 560 | 561 | public sealed class MethodBody { 562 | internal int max_stack_size; // 最大栈大小 563 | internal int code_size; // IL指令个数 564 | internal bool init_locals; // 局部变量个数 565 | 566 | internal Collection instructions; // IL指令 567 | internal Collection exceptions; // 异常 568 | internal Collection variables; // 局部变量 569 | 570 | } 571 | } 572 | ``` 573 | 574 | 具体可以参考源码:https://github.com/mono/cecil/blob/main/Mono.Cecil.Cil/MethodBody.cs 575 | 576 | ​ 577 | 578 | 最后进入正题,就是函数的代码指令集合,`method.Body.Instructions`. 逐条的循环处理每一条IL指令: 579 | 580 | 处理所有的IL指令的逻辑比较枯燥,大部分情况下都是原样的保存原始的指令,除了几个需要特别处理的IL指令,例如函数调用这种,就需要判断到底是应该跳到哪个函数去执行,因为可能需要调到外部虚拟机的原生代码去执行,又或者需要调到iFix内部的虚拟机的热更新代码。 581 | 582 | 其实在补丁里面每个函数内的IL指令和正常的一个C#函数的IL指令是差不多的,例如一个C#的函数如下: 583 | 584 | ```c# 585 | public String test(int arg) { 586 | A obj; 587 | String a = "hello world"; 588 | int b = 1; 589 | Console.WriteLine(b); 590 | obj = new A(); 591 | Console.WriteLine(obj); 592 | return a; 593 | } 594 | ``` 595 | 596 | 使用 [Ildasmexe](https://docs.microsoft.com/en-us/dotnet/framework/tools/ildasm-exe-il-disassembler) 工具反编译其IL指令如下: 597 | 598 | ``` 599 | .method public instance hidebysig cil managed ofname test 600 | return: (string) 601 | params: (int32 arg) 602 | { 603 | // Code size (0x1C) 604 | .locals init ([0] Program/A, [1] string, [2] int32) 605 | .maxstack 1 606 | IL_0000: ldstr "hello world" 607 | IL_0005: stloc.1 608 | IL_0006: ldc.i4.1 609 | IL_0007: stloc.2 610 | IL_0008: ldloc.2 611 | IL_0009: call System.Void System.Console::WriteLine(System.Int32) 612 | IL_000e: newobj System.Void Program/A::.ctor() 613 | IL_0013: stloc.0 614 | IL_0014: ldloc.0 615 | IL_0015: call System.Void System.Console::WriteLine(System.Object) 616 | IL_001a: ldloc.1 617 | IL_001b: ret 618 | } 619 | ``` 620 | 621 | 一个函数的IL指令也是和Mono.Cecil库解析出来的MethodBody是对应的: 622 | 623 | * locals 局部变量 624 | * maxstack 栈大小 625 | * IL指令 626 | 627 | ​ 628 | 629 | ### 序列化补丁到二进制文件 630 | 631 | 最后处理完所有的函数之后,将所有的函数和指令写入到二进制patch文件中。 632 | 633 | #### 序列化外部函数 634 | 635 | 外部函数在patch二进制文件的结构体如下: 636 | 637 | ```c# 638 | struct IFixExternMethod 639 | { 640 | bool isGenericInstance; // MethodReference.IsGenericInstance 641 | string declaringType; // MethodReference.DeclaringType 642 | string methodName; // MethodReference.Name 643 | string[] genericArgs; // GenericInstanceMethod.GenericArguments 644 | IFIxParameter[] parameters; 645 | } 646 | 647 | struct IFIxParameter 648 | { 649 | bool isGeneric; 650 | string declaringType; 651 | } 652 | ``` 653 | 654 | 655 | 656 | 这里我们详细的阅读源码看看这些字段具体是存的什么: 657 | 658 | 首先看函数是不是泛型实例函数 (`isGenericInstance`),例如泛型函数为:`T Call()` ,那么在真正调用这个函数时,可能引用的函数时该泛型函数的实例:例如 `object Call()` ,其中泛型 `T` 实例为 `object` 类型。 659 | 660 | 所有函数都会先保存两个字段: 该函数所属的类型 `declaringType`, 该函数的函数名 `methodName`. 661 | 662 | 如果是泛型函数的实例,主要需要记录的就是泛型的类型列表 `genericArgs`, 保存每一个泛型参数的类型。 663 | 664 | 然后就是保存函数的参数列表 `parameters`,这里复杂的也是处理泛型,咱们举个例子: 665 | 666 | ```c# 667 | class SampleClass 668 | { 669 | void Swap(T t, U u, int i) { } 670 | } 671 | ``` 672 | 673 | 对于这个函数来说,有3个参数: 674 | 675 | * `t`, 类型是泛型 `T`, 这个泛型来自于 `SampleClass`类型( `GenericParameterType.Type`) 。 676 | * `u`, 类型是泛型 `U`, 这个泛型来自于 `Swap`函数( `GenericParameterType.Method`) 。 677 | * `i`, 类型是int,它不是一个泛型参数。 678 | 679 | 680 | 681 | ```c# 682 | void writeMethod(BinaryWriter writer, MethodReference method) 683 | { 684 | writer.Write(method.IsGenericInstance); 685 | if (method.IsGenericInstance) // 如果该函数是泛型的实例 (函数本身是不是泛型函数,而不是指参数有没有泛型) 686 | { 687 | //Console.WriteLine("GenericInstance:" + externMethod); 688 | writer.Write(externTypeToId[method.DeclaringType]); // 函数所在类型 689 | writer.Write(method.Name); // 函数名 690 | // method.IsGenericInstance == true时,MethodReference可向下转型为GenericInstanceMethod泛型实例函数 691 | var typeArgs = ((GenericInstanceMethod)method).GenericArguments; // 泛型参数 692 | writer.Write(typeArgs.Count); // 泛型参数的个数 693 | for (int typeArg = 0;typeArg < typeArgs.Count;typeArg++) 694 | { 695 | if (isCompilerGenerated(typeArgs[typeArg])) // 如果是编译器生成的类型,则使用bridgeType 696 | { 697 | typeArgs[typeArg] = itfBridgeType; 698 | } 699 | writer.Write(externTypeToId[typeArgs[typeArg]]); // 保存泛型参数的类型 700 | } 701 | 702 | writer.Write(method.Parameters.Count); 703 | foreach (var p in method.Parameters) 704 | { 705 | // 判断一个类型的泛型实参是否有来自函数的泛型实参 706 | // 对于ParameterType而言,如果它是GenericParameter子类型,则其Type字段中记录了其泛型是来自于类型还是函数 707 | bool paramIsGeneric = p.ParameterType.HasGenericArgumentFromMethod(); 708 | writer.Write(paramIsGeneric); // 记录该参数为泛型 709 | if (paramIsGeneric) // 如果参数的泛型是来自于函数本身 710 | { 711 | if (p.ParameterType.IsGenericParameter) 712 | { 713 | writer.Write(p.ParameterType.Name); // 是泛型,就存泛型参数的名称,它其实是对于类型泛型的引用,即类型泛型列表中的index, 格式为: `!!`,例如: `!!0`, `!!1`, `!!2`, 其中index是method.GetGenericArguments()列表中的index 714 | } 715 | else 716 | { 717 | writer.Write(p.ParameterType.GetAssemblyQualifiedName(method.DeclaringType, true)); // 不是泛型,就存正常的类型,例如 `System.object` 718 | } 719 | } 720 | else // 如果参数的泛型来自于外层的类型 721 | { 722 | if (p.ParameterType.IsGenericParameter) 723 | { 724 | writer.Write(externTypeToId[(p.ParameterType as GenericParameter) 725 | .ResolveGenericArgument(method.DeclaringType)]); // 在方法定义的类型中查找泛型参数对应的的实参 726 | } 727 | else 728 | { 729 | writer.Write(externTypeToId[p.ParameterType]); // 直接用参数的类型即可 730 | } 731 | } 732 | } 733 | } 734 | else // 不是泛型函数 735 | { 736 | //Console.WriteLine("not GenericInstance:" + externMethod); 737 | if (!externTypeToId.ContainsKey(method.DeclaringType)) 738 | { 739 | throw new Exception("externTypeToId do not exist key: " + method.DeclaringType 740 | + ", while process method: " + method); 741 | } 742 | writer.Write(externTypeToId[method.DeclaringType]); 743 | writer.Write(method.Name); 744 | writer.Write(method.Parameters.Count); 745 | foreach (var p in method.Parameters) 746 | { 747 | var paramType = p.ParameterType; 748 | if (paramType.IsGenericParameter) // 是泛型参数 749 | { 750 | paramType = (paramType as GenericParameter).ResolveGenericArgument(method.DeclaringType); // 查找泛型实参 751 | } 752 | if (paramType.IsRequiredModifier) 753 | { 754 | paramType = (paramType as RequiredModifierType).ElementType; 755 | } 756 | if (!externTypeToId.ContainsKey(paramType)) 757 | { 758 | throw new Exception("externTypeToId do not exist key: " + paramType 759 | + ", while process parameter of method: " + method); 760 | } 761 | writer.Write(externTypeToId[paramType]); 762 | } 763 | } 764 | } 765 | 766 | ``` 767 | 768 | 查找泛型参数的实参: 769 | 770 | 因为这里外层函数是泛型函数的引用,它是明确类型的,例如调用一个泛型函数,调用的时候所有的泛型都有明确的类型,而查找的就是这个明确的类型,例如: 771 | 772 | ```c# 773 | class SampleClass // 在定义的时候 T 是泛型 774 | { 775 | void Swap(T t1, T t2) { } // 函数的参数是泛型T,该泛型来自于所属的类型 776 | } 777 | 778 | SampleClass a; // 在初始化类型的时候,是要求明确泛型的类型的 779 | a.Swap("string1", "string2"); // 因此在调用泛型函数的时候,参数中的参数如果是来自于类型,那么就可以反查找到该泛型的类型 780 | ``` 781 | 782 | 783 | 784 | 具体逻辑如下: 785 | 786 | ```c# 787 | /// 788 | /// 以contextType为上下文,查找泛型参数对应的实参 789 | /// 790 | /// 泛型参数 791 | /// 上下文类型 792 | /// 793 | public static TypeReference ResolveGenericArgument(this GenericParameter gp, TypeReference contextType) 794 | { 795 | if (contextType.IsGenericInstance) // 是泛型实例,GenericInstanceType 796 | { 797 | var genericIns = ((GenericInstanceType)contextType); 798 | var genericTypeRef = genericIns.ElementType; // 泛型类型的引用 799 | var genericTypeDef = genericTypeRef.Resolve(); // 泛型类型的定义 800 | for (int i = 0; i < genericTypeRef.GenericParameters.Count; i++) // 类型的泛型列表 801 | { 802 | if (genericTypeRef.GenericParameters[i] == gp) // 如果参数的泛型类型 == 类型的泛型类型 803 | { 804 | return genericIns.GenericArguments[i]; // 返回类型的泛型实参 805 | } 806 | if (genericTypeDef != null && genericTypeDef.GenericParameters[i] == gp) 807 | { 808 | return genericIns.GenericArguments[i]; 809 | } 810 | } 811 | } 812 | 813 | if (contextType.IsNested) // 嵌套类 814 | { 815 | return gp.ResolveGenericArgument(contextType.DeclaringType); 816 | } 817 | else 818 | { 819 | return null; 820 | } 821 | } 822 | 823 | /// 824 | /// 以contextMethod为上下文,查找泛型参数对应的实参 825 | /// 826 | /// 泛型参数 827 | /// 上下文函数 828 | /// 829 | public static TypeReference ResolveGenericArgument(this GenericParameter gp, MethodReference contextMethod) 830 | { 831 | if (contextMethod.IsGenericInstance) 832 | { 833 | var genericIns = contextMethod as GenericInstanceMethod; 834 | return genericIns.GenericArguments[gp.Position]; 835 | } 836 | return null; 837 | } 838 | ``` 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | ## 补丁文件格式(二进制) 847 | 848 | patch文件的格式如下,大体的结构就是: 849 | 850 | * 文件头,魔术等 851 | * 函数列表 852 | * 指令列表 853 | * 指令 854 | 855 | 参考源码:Source\VSProj\Src\Builder\FileVirtualMachineBuilder.cs 856 | 857 | | Name | Type | Note | 858 | | ----------------------- | ----------- | ------------ | 859 | | instructionMagic | UInt64 | 317431043901 | 860 | | interfaceBridgeTypeName | const char* | | 861 | | externTypeCount | Int32 | | 862 | | methodCount | Int32 | | 863 | 864 | 下面开始是methods集合,遍历method,size为methodCount: 865 | 866 | | Name | Type | Note | 867 | | -------- | ----- | ---- | 868 | | codeSize | Int32 | | 869 | 870 | 下面开始是method的Instruction集合,遍历code,size为codeSize: 871 | 872 | | Name | Type | Note | 873 | | ------- | ----- | ---- | 874 | | Code | Int32 | | 875 | | Operand | Int32 | | 876 | 877 | 这样就拥有的热更新的二进制补丁文件,接下里只需在动态运行时下发这个二进制补丁文件,加载到iFix的VirtualMachine中,即可实现热更新功能。 878 | 879 | 在下一节里面,我们将重点研究iFix自己实现的这个VirtualMachine的,来看看这个虚拟机内部是如何去执行这些补丁文件里面的IL指令来实现一个简单的C#虚拟机,这里面重要需要解决的一个疑惑就是:众所周知Mono的C#热更新是很简单的,因为C#本身就很容易通过反射来实现这样的功能,但在il2cpp下的C#热更新就比较复杂了,最常见的问题在il2cpp中的C#是不支持反射接口的,并且每个C#函数在转成CPP的时候都会在其函数名后面增加一串数字,导致每次打包这些函数签名都是变化的,导致很难实现热更新功能,但iFix就能很巧妙的绕开这些限制来实现也支持il2cpp的C#热更新机制。 880 | -------------------------------------------------------------------------------- /Docs/4_VirtualMachine.md: -------------------------------------------------------------------------------- 1 | # InjectFix实现原理(四) - IL虚拟机 2 | 3 | InjectFix热更新的实现原理中最核心就是它通过去实现一个自己的IL虚拟机 ( `IFix.Core.VirtualMachine` ),支持了在运行时不管是mono还是il2cpp环境下都可以动态的加载和执行热更新的C#代码。 4 | 5 | ​ 6 | 7 | ## 虚拟机 8 | 9 | 首先我们需要先了解一个概念:什么是虚拟机。这里的虚拟机都是指程序虚拟机(区别于VBox等系统虚拟机),它是指设计用来在平台无关的环境下运行程序代码的应用程序,例如Java虚拟机,C#虚拟机等,它们存在的目的都是为实现跨平台,一次编写,多处运行。Java程序运行在Java虚拟机里面,C#程序运行在C#虚拟机里面,而虚拟机本身也就只是一个程序,但它为了隔离硬件和平台的不同,虚拟出一整套程序运行所需的硬件架构,包括指令集,堆栈,寄存器等。 10 | 11 | 虚拟机主要需要解决的硬件和平台差异有: 12 | 13 | * 指令集的不同。 14 | * 寄存器的不同。 15 | * 32位和64位的不同。 16 | 17 | 接下来我们来看虚拟机具体是如何处理这些差异的。 18 | 19 | ​ 20 | 21 | ## 寄存器和栈 22 | 23 | 为了解决硬件寄存器的差异问题,虚拟机一般而言都分为两种:基于栈实现的虚拟机和基于寄存器实现的虚拟机,例如在PC上运行的JVM虚拟机,就是基于栈的实现,因为不同的PC可能拥有不同的寄存器架构,它的优点是跨平台实现方便,只需要在栈上面去模拟寄存器的功能,而不需要去直接访问寄存器,但缺点也很明显就是速度相对于基于寄存器的而言会会慢一些。 24 | 25 | 在Android平台上运行的Java虚拟机(Dalvik或ART)则都是基于寄存器实现的虚拟机,它的优点显而易见就是快。但现实起来也麻烦一些,需要做平台相关的差异实现,但因为Android本身就是只在指定的硬件架构上运行的系统,因此为了追求更快的效率,Google基于寄存器重新实现了一个Java虚拟机。 26 | 27 | 在C#的世界里面,不管是微软官方的C#虚拟机还是第三方的mono虚拟机,它们都是基于栈实现的虚拟机。先记住这一点对于了解IL虚拟机的实现逻辑至关重要。 28 | 29 | ​ 30 | 31 | ## IL指令集 32 | 33 | 虚拟机在解决寄存器的差异问题后,还需要解决CPU支持的指令集不同的问题,为了解决这个问题,所有的虚拟机都需要实现一套自己的指令集,这种一般都被称为“中间代码”,在Java里面是 `Bytecode`(俗称字节码),在C#里面是 `Microsoft Intermediate Language`(简称IL),Java代码或者C#代码都会被编译器先编译成中间代码,然后再通过虚拟机在运行时将中间代码翻译成二进制机器码在本地机器上运行,当然有些虚拟机为了追求性能,也支持预编译( `AOT` )和运行时编译( `JIT`) 两种模式。 34 | 35 | ​ 36 | 37 | 首先我们先来大致了解一下IL代码具体是怎样的,这里以一个最简单的C#的helloworld程序为例,来看看C#代码转成IL代码的结果。 38 | 39 | ```c# 40 | using System; 41 | 42 | public class Program 43 | { 44 | public static void Main(string[] args) 45 | { 46 | Console.WriteLine("Hello World!" + args); 47 | } 48 | } 49 | ``` 50 | 51 | 上面这段简单的代码转成IL代码之后变成如下这样的: 52 | 53 | ```C# 54 | .method public static hidebysig cil managed ofname Main 55 | return: (void) 56 | params: (string[] args) 57 | { 58 | // Code size (0x11) 59 | .maxstack 8 60 | IL_0000: ldstr "Hello World!" 61 | IL_0005: ldarg.0 62 | IL_0006: call System.String System.String::Concat(System.Object,System.Object) 63 | IL_000b: call System.Void System.Console::WriteLine(System.String) 64 | IL_0010: ret 65 | } 66 | ``` 67 | 68 | 这里我们可以看出来IL代码这种中间语言,相对于机器码而言还是非常易读的(有助记词的帮助下),它兼容了人读和机读的两者平衡,这段IL代码看起来几乎和它对应的C#代码差不多。这里先有一个大致的了解,后面我们将深入去了解这些IL指令具体是什么意思。 69 | 70 | ​ 71 | 72 | ## IL虚拟机 73 | 74 | 到此我们基本上对于虚拟机这个概念有了一个简单的认识,接下来我们来看如何在Unity里去实现一个自己的IL虚拟机,实现在运行时动态的加载和执行C#代码。 75 | 76 | ​ 77 | 78 | 一个虚拟机最重要的作用就是在一个平台无关的环境下去运行一段代码,而在IL虚拟机上就是指执行一段IL代码,因此这里我们最关心的就是现在运行在Unity环境下的虚拟机是如何执行IL代码的。 79 | 80 | ​ 81 | 82 | 因为在Unity里面的存在mono和il2cpp两种完全不同的实现机制,因此虚拟机执行IL指令也分为两种情况: 83 | 84 | * 在mono版本里,C#代码直接转成DLL(IL指令),由mono虚拟机在运行时执行IL指令。 85 | * 在il2cpp版本里,C#代码先转成DLL(IL指令),然后通过il2cpp静态将IL指令转成CPP代码,再通过编译器编译成机器码,在运行时直接执行。 86 | 87 | ​ 88 | 89 | 那么在Unity这里面就有两个地方是在直接去处理IL指令的: 90 | 91 | * mono虚拟机在动态时执行IL指令。(JIT版本:mono_method_to_ir) 92 | * il2cpp在编译时静态将IL指令成C++代码。 93 | 94 | 第1种的mono情况很好理解,就是一个正常的虚拟机应该做的事情,在运行时一条一条的去执行IL指令就行了。而第2种情况,我们先抛开其他复杂的东西,可以简单的理解il2cpp只是将mono在运行时执行IL指令的这个过程前置到了编译时,在编译的过程中,il2cpp模拟出了一个IL虚拟机,一条一条的去处理这些IL指令,只是mono虚拟机是直接将IL指令转成了机器码去执行它,而il2cpp虚拟机则是将IL指令转成C++代码先保存起来,然后再通过C++的编译器将其转成机器码。但是如果抛开后面的流程,其实在mono和il2cpp处理IL指令的这个环节,它们的功能都是类似的,它们都是作为一个IL虚拟机去一条条的读取IL指令。 95 | 96 | ​ 97 | 98 | 我们要实现一个自己的IL虚拟机,首先就需要先去理解mono虚拟机和il2cpp虚拟机(简单把它也理解成一个虚拟机)是如何处理这些IL指令的。 99 | 100 | 101 | 102 | 103 | 104 | TODO 105 | 106 | ```c++ 107 | // System.Void NewBehaviourScript::main(System.String[]) 108 | IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void NewBehaviourScript_main_m570D5FE78B8DAC520575C311A3978992E7C53737 (NewBehaviourScript_tF2FE3ECCFBC98B6EF49F3577A340114691B00003 * __this, StringU5BU5D_t933FB07893230EA91C40FF900D5400665E87B14E* ___args0, const RuntimeMethod* method) 109 | { 110 | static bool s_Il2CppMethodInitialized; 111 | if (!s_Il2CppMethodInitialized) 112 | { 113 | il2cpp_codegen_initialize_method (NewBehaviourScript_main_m570D5FE78B8DAC520575C311A3978992E7C53737_MetadataUsageId); 114 | s_Il2CppMethodInitialized = true; 115 | } 116 | { 117 | StringU5BU5D_t933FB07893230EA91C40FF900D5400665E87B14E* L_3 = ___args0; 118 | String_t* L_4 = String_Concat_mBB19C73816BDD1C3519F248E1ADC8E11A6FDB495(_stringLiteral2EF7BDE608CE5404E97D5F042F95F89F1C232871, (RuntimeObject *)(RuntimeObject *)L_3, /*hidden argument*/NULL); 119 | IL2CPP_RUNTIME_CLASS_INIT(Console_t5C8E87BA271B0DECA837A3BF9093AC3560DB3D5D_il2cpp_TypeInfo_var); 120 | Console_WriteLine_mA5F7E391799514350980A0DE16983383542CA820(L_4, /*hidden argument*/NULL); 121 | return; 122 | } 123 | } 124 | ``` 125 | 126 | -------------------------------------------------------------------------------- /Docs/5_LoadPatch.md: -------------------------------------------------------------------------------- 1 | # InjectFix实现原理(五)-加载补丁 2 | 3 | 在本节中,主要学习IFix是如何在运行时加载补丁的。 4 | 5 | 6 | 7 | ## 加载补丁文件 8 | 9 | TODO 10 | 11 | 12 | 13 | ### 加载外部函数 14 | 15 | `FileVirtualMachineBuilder.cs` 16 | 17 | Patch文件中外部函数的结构体如下: 18 | 19 | ```c# 20 | struct IFixExternMethod 21 | { 22 | bool isGenericInstance; // MethodReference.IsGenericInstance 23 | string declaringType; // MethodReference.DeclaringType 24 | string methodName; // MethodReference.Name 25 | string[] genericArgs; // GenericInstanceMethod.GenericArguments 26 | IFIxParameter[] parameters; 27 | } 28 | 29 | struct IFIxParameter 30 | { 31 | bool isGeneric; // ParameterType.HasGenericArgumentFromMethod 32 | string declaringType; // ParameterType 33 | } 34 | ``` 35 | 36 | ​ 37 | 38 | 从Patch文件中解析这些外部函数,并在运行时查找它们: 39 | 40 | * 首先也是看这个函数是不是泛型函数的实例 `isGenericInstance` 41 | 42 | ```c# 43 | static MethodBase readMethod(BinaryReader reader, Type[] externTypes) 44 | { 45 | bool isGenericInstance = reader.ReadBoolean(); 46 | BindingFlags flag = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static 47 | | BindingFlags.NonPublic | BindingFlags.Public; 48 | if (isGenericInstance) // 泛型函数的实例(函数本身有泛型) 49 | { 50 | Type declaringType = externTypes[reader.ReadInt32()]; // 函数所属的类型 51 | string methodName = reader.ReadString(); // 函数名 52 | //Console.WriteLine("load generic method: " + declaringType + " ?? " + methodName); 53 | int genericArgCount = reader.ReadInt32(); 54 | //Console.WriteLine("genericArgCount:" + genericArgCount); 55 | Type[] genericArgs = new Type[genericArgCount]; // 函数的泛型列表 56 | for (int j = 0; j < genericArgCount; j++) 57 | { 58 | genericArgs[j] = externTypes[reader.ReadInt32()]; // 泛型的类型 59 | //Console.WriteLine(j + " ga:" + genericArgs[j]); 60 | } 61 | int paramCount = reader.ReadInt32(); // 函数的参数列表 62 | object[] paramMatchInfo = new object[paramCount]; 63 | for (int j = 0; j < paramCount; j++) 64 | { 65 | bool isGeneric = reader.ReadBoolean(); // 参数的泛型是否来自于函数的泛型 66 | paramMatchInfo[j] = isGeneric ? (object)reader.ReadString() // 是:则直接读字符串 67 | : externTypes[reader.ReadInt32()]; // 否:则读一个Type的ID 68 | } 69 | 70 | // 根据patch中的外部函数信息,在运行时查找函数 71 | MethodInfo matchMethod = null; 72 | foreach (var method in declaringType.GetMethods(flag)) // 在运行时查找类型的函数列表(DeclaredOnly|Instance|Static|NoPublic|Public) 73 | { 74 | var paramInfos = method.GetParameters(); // 函数的参数列表 75 | 76 | Type[] genericArgInfos = null; 77 | if (method.IsGenericMethodDefinition) // 是不是泛型函数定义 78 | { 79 | //UnityEngine.Debug.Log("get generic arg of "+ method); 80 | genericArgInfos = method.GetGenericArguments(); // 函数的泛型列表 81 | } 82 | bool paramMatch = paramInfos.Length == paramCount // 对比函数的参数列表个数是否相同 83 | && method.Name == methodName; // 对比函数名是否相同 84 | if (paramMatch && genericArgCount > 0) // 如果patch中有泛型列表,则该函数必须是泛型函数,并且对比函数泛型的个数是否相同(注意:这里它不对比泛型的类型是不是相同,因此虽然patch中的泛型是明确的,但是这里是函数的定义,泛型是不明确的) 85 | { 86 | if (!method.IsGenericMethodDefinition || genericArgInfos.Length != genericArgCount) 87 | { 88 | paramMatch = false; 89 | } 90 | } 91 | if (paramMatch) 92 | { 93 | for (int j = 0; j < paramCount; j++) // 遍历函数的参数列表 94 | { 95 | // paramMatchInfo是patch文件中保存的函数参数列表,它可能是一个string(isGeneric=true), 也可能是一个类型(isGeneric=false) 96 | string strMatchInfo = paramMatchInfo[j] as string; 97 | if (strMatchInfo != null) 98 | { 99 | if (!method.IsGenericMethodDefinition) // 如果函数有参数是泛型,那么该函数必须是泛型 100 | { 101 | paramMatch = false; 102 | break; 103 | } 104 | // 从WriteMethod函数中,我们就得知了参数的 `isGeneric` 是指这个参数的泛型是不是来自于它定义的类型的泛型,如果是这种情况下,我们会直接保存这个泛型的Name,这里将index引用转为Type的Name(例如: `T`) 105 | strMatchInfo = System.Text.RegularExpressions.Regex 106 | .Replace(strMatchInfo, @"!!\d+", m => 107 | genericArgInfos[int.Parse(m.Value.Substring(2))].Name); 108 | if (strMatchInfo != paramInfos[j].ParameterType.ToString()) // 对比参数的类型是否和参数泛型的泛型相同 109 | { 110 | //Console.WriteLine("gp not match:" + strMatchInfo + " ??? " 111 | // + paramInfos[j].ParameterType.ToString()); 112 | paramMatch = false; 113 | break; 114 | } 115 | } 116 | else 117 | { 118 | if ((paramMatchInfo[j] as Type) != paramInfos[j].ParameterType) // 对比参数的类型是否相同 119 | { 120 | //Console.WriteLine("pt not match:" + paramMatchInfo[j] + " ??? " 121 | // + paramInfos[j].ParameterType); 122 | paramMatch = false; 123 | break; 124 | } 125 | } 126 | } 127 | } 128 | if (paramMatch) 129 | { 130 | matchMethod = method; 131 | break; 132 | } 133 | } 134 | if (matchMethod == null) 135 | { 136 | throw new Exception("can not load generic method [" + methodName + "] of " + declaringType); 137 | } 138 | return matchMethod.MakeGenericMethod(genericArgs); 139 | } 140 | else 141 | { 142 | Type declaringType = externTypes[reader.ReadInt32()]; 143 | string methodName = reader.ReadString(); 144 | int paramCount = reader.ReadInt32(); 145 | //Console.WriteLine("load no generic method: " + declaringType + " ?? " + methodName + " pc " 146 | // + paramCount); 147 | Type[] paramTypes = new Type[paramCount]; 148 | for (int j = 0; j < paramCount; j++) 149 | { 150 | paramTypes[j] = externTypes[reader.ReadInt32()]; 151 | } 152 | bool isConstructor = methodName == ".ctor" || methodName == ".cctor"; 153 | MethodBase externMethod = null; 154 | //StringBuilder sb = new StringBuilder(); 155 | //sb.Append("method to find name: "); 156 | //sb.AppendLine(methodName); 157 | //for (int j = 0; j < paramCount; j++) 158 | //{ 159 | // sb.Append("p "); 160 | // sb.Append(j); 161 | // sb.Append(": "); 162 | // sb.AppendLine(paramTypes[j].ToString()); 163 | //} 164 | if (isConstructor) 165 | { 166 | externMethod = declaringType.GetConstructor(BindingFlags.Public | (methodName == ".ctor" ? 167 | BindingFlags.Instance : BindingFlags.Static) | 168 | BindingFlags.NonPublic, null, paramTypes, null); 169 | // : (MethodBase)(declaringType.GetMethod(methodName, paramTypes)); 170 | } 171 | else 172 | { 173 | foreach (var method in declaringType.GetMethods(flag)) // 运行时遍历类型的函数列表,method是运行时的函数定义 174 | { 175 | if (method.Name == methodName // 对比函数名是否相等 176 | && !method.IsGenericMethodDefinition 177 | && method.GetParameters().Length == paramCount) // 对比函数参数列表是否相等 178 | { 179 | var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType); 180 | if (methodParameterTypes.SequenceEqual(paramTypes)) // 对比参数列表中每一个参数的类型是不是一一相等 181 | { 182 | externMethod = method; 183 | break; 184 | } 185 | //else 186 | //{ 187 | // var mptlist = methodParameterTypes.ToList(); 188 | // for (int j = 0; j < mptlist.Count; j++) 189 | // { 190 | // sb.Append("not match p "); 191 | // sb.Append(j); 192 | // sb.Append(": "); 193 | // sb.AppendLine(mptlist[j].ToString()); 194 | // } 195 | //} 196 | } 197 | } 198 | } 199 | if (externMethod == null) 200 | { 201 | throw new Exception("can not load method [" + methodName + "] of " 202 | + declaringType/* + ", info:\r\n" + sb.ToString()*/); 203 | } 204 | return externMethod; 205 | } 206 | } 207 | 208 | ``` 209 | 210 | -------------------------------------------------------------------------------- /IFixToolKit/IFix.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandin/InjectFixSample/bf7f93dcef1d9982f406d1b266925aef29883682/IFixToolKit/IFix.exe -------------------------------------------------------------------------------- /IFixToolKit/Mono.Cecil.Mdb.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandin/InjectFixSample/bf7f93dcef1d9982f406d1b266925aef29883682/IFixToolKit/Mono.Cecil.Mdb.dll -------------------------------------------------------------------------------- /IFixToolKit/Mono.Cecil.Pdb.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandin/InjectFixSample/bf7f93dcef1d9982f406d1b266925aef29883682/IFixToolKit/Mono.Cecil.Pdb.dll -------------------------------------------------------------------------------- /IFixToolKit/Mono.Cecil.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandin/InjectFixSample/bf7f93dcef1d9982f406d1b266925aef29883682/IFixToolKit/Mono.Cecil.dll -------------------------------------------------------------------------------- /Packages/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "com.unity.collab-proxy": "1.2.16", 4 | "com.unity.ide.rider": "1.1.4", 5 | "com.unity.ide.vscode": "1.2.3", 6 | "com.unity.test-framework": "1.1.19", 7 | "com.unity.textmeshpro": "2.1.1", 8 | "com.unity.timeline": "1.2.17", 9 | "com.unity.ugui": "1.0.0", 10 | "com.unity.modules.ai": "1.0.0", 11 | "com.unity.modules.androidjni": "1.0.0", 12 | "com.unity.modules.animation": "1.0.0", 13 | "com.unity.modules.assetbundle": "1.0.0", 14 | "com.unity.modules.audio": "1.0.0", 15 | "com.unity.modules.cloth": "1.0.0", 16 | "com.unity.modules.director": "1.0.0", 17 | "com.unity.modules.imageconversion": "1.0.0", 18 | "com.unity.modules.imgui": "1.0.0", 19 | "com.unity.modules.jsonserialize": "1.0.0", 20 | "com.unity.modules.particlesystem": "1.0.0", 21 | "com.unity.modules.physics": "1.0.0", 22 | "com.unity.modules.physics2d": "1.0.0", 23 | "com.unity.modules.screencapture": "1.0.0", 24 | "com.unity.modules.terrain": "1.0.0", 25 | "com.unity.modules.terrainphysics": "1.0.0", 26 | "com.unity.modules.tilemap": "1.0.0", 27 | "com.unity.modules.ui": "1.0.0", 28 | "com.unity.modules.uielements": "1.0.0", 29 | "com.unity.modules.umbra": "1.0.0", 30 | "com.unity.modules.unityanalytics": "1.0.0", 31 | "com.unity.modules.unitywebrequest": "1.0.0", 32 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0", 33 | "com.unity.modules.unitywebrequestaudio": "1.0.0", 34 | "com.unity.modules.unitywebrequesttexture": "1.0.0", 35 | "com.unity.modules.unitywebrequestwww": "1.0.0", 36 | "com.unity.modules.vehicles": "1.0.0", 37 | "com.unity.modules.video": "1.0.0", 38 | "com.unity.modules.vr": "1.0.0", 39 | "com.unity.modules.wind": "1.0.0", 40 | "com.unity.modules.xr": "1.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Packages/packages-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "com.unity.collab-proxy": { 4 | "version": "1.2.16", 5 | "depth": 0, 6 | "source": "registry", 7 | "dependencies": {}, 8 | "url": "https://packages.unity.cn" 9 | }, 10 | "com.unity.ext.nunit": { 11 | "version": "1.0.5", 12 | "depth": 1, 13 | "source": "registry", 14 | "dependencies": {}, 15 | "url": "https://packages.unity.cn" 16 | }, 17 | "com.unity.ide.rider": { 18 | "version": "1.1.4", 19 | "depth": 0, 20 | "source": "registry", 21 | "dependencies": { 22 | "com.unity.test-framework": "1.1.1" 23 | }, 24 | "url": "https://packages.unity.cn" 25 | }, 26 | "com.unity.ide.vscode": { 27 | "version": "1.2.3", 28 | "depth": 0, 29 | "source": "registry", 30 | "dependencies": {}, 31 | "url": "https://packages.unity.cn" 32 | }, 33 | "com.unity.test-framework": { 34 | "version": "1.1.19", 35 | "depth": 0, 36 | "source": "registry", 37 | "dependencies": { 38 | "com.unity.ext.nunit": "1.0.5", 39 | "com.unity.modules.imgui": "1.0.0", 40 | "com.unity.modules.jsonserialize": "1.0.0" 41 | }, 42 | "url": "https://packages.unity.cn" 43 | }, 44 | "com.unity.textmeshpro": { 45 | "version": "2.1.1", 46 | "depth": 0, 47 | "source": "registry", 48 | "dependencies": { 49 | "com.unity.ugui": "1.0.0" 50 | }, 51 | "url": "https://packages.unity.cn" 52 | }, 53 | "com.unity.timeline": { 54 | "version": "1.2.17", 55 | "depth": 0, 56 | "source": "registry", 57 | "dependencies": {}, 58 | "url": "https://packages.unity.cn" 59 | }, 60 | "com.unity.ugui": { 61 | "version": "1.0.0", 62 | "depth": 0, 63 | "source": "builtin", 64 | "dependencies": { 65 | "com.unity.modules.ui": "1.0.0", 66 | "com.unity.modules.imgui": "1.0.0" 67 | } 68 | }, 69 | "com.unity.modules.ai": { 70 | "version": "1.0.0", 71 | "depth": 0, 72 | "source": "builtin", 73 | "dependencies": {} 74 | }, 75 | "com.unity.modules.androidjni": { 76 | "version": "1.0.0", 77 | "depth": 0, 78 | "source": "builtin", 79 | "dependencies": {} 80 | }, 81 | "com.unity.modules.animation": { 82 | "version": "1.0.0", 83 | "depth": 0, 84 | "source": "builtin", 85 | "dependencies": {} 86 | }, 87 | "com.unity.modules.assetbundle": { 88 | "version": "1.0.0", 89 | "depth": 0, 90 | "source": "builtin", 91 | "dependencies": {} 92 | }, 93 | "com.unity.modules.audio": { 94 | "version": "1.0.0", 95 | "depth": 0, 96 | "source": "builtin", 97 | "dependencies": {} 98 | }, 99 | "com.unity.modules.cloth": { 100 | "version": "1.0.0", 101 | "depth": 0, 102 | "source": "builtin", 103 | "dependencies": { 104 | "com.unity.modules.physics": "1.0.0" 105 | } 106 | }, 107 | "com.unity.modules.director": { 108 | "version": "1.0.0", 109 | "depth": 0, 110 | "source": "builtin", 111 | "dependencies": { 112 | "com.unity.modules.audio": "1.0.0", 113 | "com.unity.modules.animation": "1.0.0" 114 | } 115 | }, 116 | "com.unity.modules.imageconversion": { 117 | "version": "1.0.0", 118 | "depth": 0, 119 | "source": "builtin", 120 | "dependencies": {} 121 | }, 122 | "com.unity.modules.imgui": { 123 | "version": "1.0.0", 124 | "depth": 0, 125 | "source": "builtin", 126 | "dependencies": {} 127 | }, 128 | "com.unity.modules.jsonserialize": { 129 | "version": "1.0.0", 130 | "depth": 0, 131 | "source": "builtin", 132 | "dependencies": {} 133 | }, 134 | "com.unity.modules.particlesystem": { 135 | "version": "1.0.0", 136 | "depth": 0, 137 | "source": "builtin", 138 | "dependencies": {} 139 | }, 140 | "com.unity.modules.physics": { 141 | "version": "1.0.0", 142 | "depth": 0, 143 | "source": "builtin", 144 | "dependencies": {} 145 | }, 146 | "com.unity.modules.physics2d": { 147 | "version": "1.0.0", 148 | "depth": 0, 149 | "source": "builtin", 150 | "dependencies": {} 151 | }, 152 | "com.unity.modules.screencapture": { 153 | "version": "1.0.0", 154 | "depth": 0, 155 | "source": "builtin", 156 | "dependencies": { 157 | "com.unity.modules.imageconversion": "1.0.0" 158 | } 159 | }, 160 | "com.unity.modules.subsystems": { 161 | "version": "1.0.0", 162 | "depth": 1, 163 | "source": "builtin", 164 | "dependencies": { 165 | "com.unity.modules.jsonserialize": "1.0.0" 166 | } 167 | }, 168 | "com.unity.modules.terrain": { 169 | "version": "1.0.0", 170 | "depth": 0, 171 | "source": "builtin", 172 | "dependencies": {} 173 | }, 174 | "com.unity.modules.terrainphysics": { 175 | "version": "1.0.0", 176 | "depth": 0, 177 | "source": "builtin", 178 | "dependencies": { 179 | "com.unity.modules.physics": "1.0.0", 180 | "com.unity.modules.terrain": "1.0.0" 181 | } 182 | }, 183 | "com.unity.modules.tilemap": { 184 | "version": "1.0.0", 185 | "depth": 0, 186 | "source": "builtin", 187 | "dependencies": { 188 | "com.unity.modules.physics2d": "1.0.0" 189 | } 190 | }, 191 | "com.unity.modules.ui": { 192 | "version": "1.0.0", 193 | "depth": 0, 194 | "source": "builtin", 195 | "dependencies": {} 196 | }, 197 | "com.unity.modules.uielements": { 198 | "version": "1.0.0", 199 | "depth": 0, 200 | "source": "builtin", 201 | "dependencies": { 202 | "com.unity.modules.imgui": "1.0.0", 203 | "com.unity.modules.jsonserialize": "1.0.0" 204 | } 205 | }, 206 | "com.unity.modules.umbra": { 207 | "version": "1.0.0", 208 | "depth": 0, 209 | "source": "builtin", 210 | "dependencies": {} 211 | }, 212 | "com.unity.modules.unityanalytics": { 213 | "version": "1.0.0", 214 | "depth": 0, 215 | "source": "builtin", 216 | "dependencies": { 217 | "com.unity.modules.unitywebrequest": "1.0.0", 218 | "com.unity.modules.jsonserialize": "1.0.0" 219 | } 220 | }, 221 | "com.unity.modules.unitywebrequest": { 222 | "version": "1.0.0", 223 | "depth": 0, 224 | "source": "builtin", 225 | "dependencies": {} 226 | }, 227 | "com.unity.modules.unitywebrequestassetbundle": { 228 | "version": "1.0.0", 229 | "depth": 0, 230 | "source": "builtin", 231 | "dependencies": { 232 | "com.unity.modules.assetbundle": "1.0.0", 233 | "com.unity.modules.unitywebrequest": "1.0.0" 234 | } 235 | }, 236 | "com.unity.modules.unitywebrequestaudio": { 237 | "version": "1.0.0", 238 | "depth": 0, 239 | "source": "builtin", 240 | "dependencies": { 241 | "com.unity.modules.unitywebrequest": "1.0.0", 242 | "com.unity.modules.audio": "1.0.0" 243 | } 244 | }, 245 | "com.unity.modules.unitywebrequesttexture": { 246 | "version": "1.0.0", 247 | "depth": 0, 248 | "source": "builtin", 249 | "dependencies": { 250 | "com.unity.modules.unitywebrequest": "1.0.0", 251 | "com.unity.modules.imageconversion": "1.0.0" 252 | } 253 | }, 254 | "com.unity.modules.unitywebrequestwww": { 255 | "version": "1.0.0", 256 | "depth": 0, 257 | "source": "builtin", 258 | "dependencies": { 259 | "com.unity.modules.unitywebrequest": "1.0.0", 260 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0", 261 | "com.unity.modules.unitywebrequestaudio": "1.0.0", 262 | "com.unity.modules.audio": "1.0.0", 263 | "com.unity.modules.assetbundle": "1.0.0", 264 | "com.unity.modules.imageconversion": "1.0.0" 265 | } 266 | }, 267 | "com.unity.modules.vehicles": { 268 | "version": "1.0.0", 269 | "depth": 0, 270 | "source": "builtin", 271 | "dependencies": { 272 | "com.unity.modules.physics": "1.0.0" 273 | } 274 | }, 275 | "com.unity.modules.video": { 276 | "version": "1.0.0", 277 | "depth": 0, 278 | "source": "builtin", 279 | "dependencies": { 280 | "com.unity.modules.audio": "1.0.0", 281 | "com.unity.modules.ui": "1.0.0", 282 | "com.unity.modules.unitywebrequest": "1.0.0" 283 | } 284 | }, 285 | "com.unity.modules.vr": { 286 | "version": "1.0.0", 287 | "depth": 0, 288 | "source": "builtin", 289 | "dependencies": { 290 | "com.unity.modules.jsonserialize": "1.0.0", 291 | "com.unity.modules.physics": "1.0.0", 292 | "com.unity.modules.xr": "1.0.0" 293 | } 294 | }, 295 | "com.unity.modules.wind": { 296 | "version": "1.0.0", 297 | "depth": 0, 298 | "source": "builtin", 299 | "dependencies": {} 300 | }, 301 | "com.unity.modules.xr": { 302 | "version": "1.0.0", 303 | "depth": 0, 304 | "source": "builtin", 305 | "dependencies": { 306 | "com.unity.modules.physics": "1.0.0", 307 | "com.unity.modules.jsonserialize": "1.0.0", 308 | "com.unity.modules.subsystems": "1.0.0" 309 | } 310 | } 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /ProjectSettings/AudioManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!11 &1 4 | AudioManager: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_Volume: 1 8 | Rolloff Scale: 1 9 | Doppler Factor: 1 10 | Default Speaker Mode: 2 11 | m_SampleRate: 0 12 | m_DSPBufferSize: 1024 13 | m_VirtualVoiceCount: 512 14 | m_RealVoiceCount: 32 15 | m_SpatializerPlugin: 16 | m_AmbisonicDecoderPlugin: 17 | m_DisableAudio: 0 18 | m_VirtualizeEffects: 1 19 | m_RequestedDSPBufferSize: 1024 20 | -------------------------------------------------------------------------------- /ProjectSettings/ClusterInputManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!236 &1 4 | ClusterInputManager: 5 | m_ObjectHideFlags: 0 6 | m_Inputs: [] 7 | -------------------------------------------------------------------------------- /ProjectSettings/DynamicsManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!55 &1 4 | PhysicsManager: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 11 7 | m_Gravity: {x: 0, y: -9.81, z: 0} 8 | m_DefaultMaterial: {fileID: 0} 9 | m_BounceThreshold: 2 10 | m_SleepThreshold: 0.005 11 | m_DefaultContactOffset: 0.01 12 | m_DefaultSolverIterations: 6 13 | m_DefaultSolverVelocityIterations: 1 14 | m_QueriesHitBackfaces: 0 15 | m_QueriesHitTriggers: 1 16 | m_EnableAdaptiveForce: 0 17 | m_ClothInterCollisionDistance: 0 18 | m_ClothInterCollisionStiffness: 0 19 | m_ContactsGeneration: 1 20 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 21 | m_AutoSimulation: 1 22 | m_AutoSyncTransforms: 0 23 | m_ReuseCollisionCallbacks: 1 24 | m_ClothInterCollisionSettingsToggle: 0 25 | m_ContactPairsMode: 0 26 | m_BroadphaseType: 0 27 | m_WorldBounds: 28 | m_Center: {x: 0, y: 0, z: 0} 29 | m_Extent: {x: 250, y: 250, z: 250} 30 | m_WorldSubdivisions: 8 31 | m_FrictionType: 0 32 | m_EnableEnhancedDeterminism: 0 33 | m_EnableUnifiedHeightmaps: 1 34 | m_DefaultMaxAngluarSpeed: 7 35 | -------------------------------------------------------------------------------- /ProjectSettings/EditorBuildSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1045 &1 4 | EditorBuildSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_Scenes: [] 8 | m_configObjects: {} 9 | -------------------------------------------------------------------------------- /ProjectSettings/EditorSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!159 &1 4 | EditorSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 9 7 | m_ExternalVersionControlSupport: Visible Meta Files 8 | m_SerializationMode: 2 9 | m_LineEndingsForNewScripts: 0 10 | m_DefaultBehaviorMode: 0 11 | m_PrefabRegularEnvironment: {fileID: 0} 12 | m_PrefabUIEnvironment: {fileID: 0} 13 | m_SpritePackerMode: 0 14 | m_SpritePackerPaddingPower: 1 15 | m_EtcTextureCompressorBehavior: 1 16 | m_EtcTextureFastCompressor: 1 17 | m_EtcTextureNormalCompressor: 2 18 | m_EtcTextureBestCompressor: 4 19 | m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref 20 | m_ProjectGenerationRootNamespace: 21 | m_CollabEditorSettings: 22 | inProgressEnabled: 1 23 | m_EnableTextureStreamingInEditMode: 1 24 | m_EnableTextureStreamingInPlayMode: 1 25 | m_AsyncShaderCompilation: 1 26 | m_EnterPlayModeOptionsEnabled: 0 27 | m_EnterPlayModeOptions: 3 28 | m_ShowLightmapResolutionOverlay: 1 29 | m_UseLegacyProbeSampleCount: 0 30 | m_AssetPipelineMode: 1 31 | m_CacheServerMode: 0 32 | m_CacheServerEndpoint: 33 | m_CacheServerNamespacePrefix: default 34 | m_CacheServerEnableDownload: 1 35 | m_CacheServerEnableUpload: 1 36 | -------------------------------------------------------------------------------- /ProjectSettings/GraphicsSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!30 &1 4 | GraphicsSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 13 7 | m_Deferred: 8 | m_Mode: 1 9 | m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} 10 | m_DeferredReflections: 11 | m_Mode: 1 12 | m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} 13 | m_ScreenSpaceShadows: 14 | m_Mode: 1 15 | m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} 16 | m_LegacyDeferred: 17 | m_Mode: 1 18 | m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} 19 | m_DepthNormals: 20 | m_Mode: 1 21 | m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} 22 | m_MotionVectors: 23 | m_Mode: 1 24 | m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} 25 | m_LightHalo: 26 | m_Mode: 1 27 | m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} 28 | m_LensFlare: 29 | m_Mode: 1 30 | m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} 31 | m_AlwaysIncludedShaders: 32 | - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} 33 | - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} 34 | - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} 35 | - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} 36 | - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} 37 | - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} 38 | - {fileID: 16000, guid: 0000000000000000f000000000000000, type: 0} 39 | - {fileID: 16001, guid: 0000000000000000f000000000000000, type: 0} 40 | - {fileID: 17000, guid: 0000000000000000f000000000000000, type: 0} 41 | m_PreloadedShaders: [] 42 | m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, 43 | type: 0} 44 | m_CustomRenderPipeline: {fileID: 0} 45 | m_TransparencySortMode: 0 46 | m_TransparencySortAxis: {x: 0, y: 0, z: 1} 47 | m_DefaultRenderingPath: 1 48 | m_DefaultMobileRenderingPath: 1 49 | m_TierSettings: [] 50 | m_LightmapStripping: 0 51 | m_FogStripping: 0 52 | m_InstancingStripping: 0 53 | m_LightmapKeepPlain: 1 54 | m_LightmapKeepDirCombined: 1 55 | m_LightmapKeepDynamicPlain: 1 56 | m_LightmapKeepDynamicDirCombined: 1 57 | m_LightmapKeepShadowMask: 1 58 | m_LightmapKeepSubtractive: 1 59 | m_FogKeepLinear: 1 60 | m_FogKeepExp: 1 61 | m_FogKeepExp2: 1 62 | m_AlbedoSwatchInfos: [] 63 | m_LightsUseLinearIntensity: 0 64 | m_LightsUseColorTemperature: 0 65 | m_LogWhenShaderIsCompiled: 0 66 | m_AllowEnlightenSupportForUpgradedProject: 0 67 | -------------------------------------------------------------------------------- /ProjectSettings/InputManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!13 &1 4 | InputManager: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_Axes: 8 | - serializedVersion: 3 9 | m_Name: Horizontal 10 | descriptiveName: 11 | descriptiveNegativeName: 12 | negativeButton: left 13 | positiveButton: right 14 | altNegativeButton: a 15 | altPositiveButton: d 16 | gravity: 3 17 | dead: 0.001 18 | sensitivity: 3 19 | snap: 1 20 | invert: 0 21 | type: 0 22 | axis: 0 23 | joyNum: 0 24 | - serializedVersion: 3 25 | m_Name: Vertical 26 | descriptiveName: 27 | descriptiveNegativeName: 28 | negativeButton: down 29 | positiveButton: up 30 | altNegativeButton: s 31 | altPositiveButton: w 32 | gravity: 3 33 | dead: 0.001 34 | sensitivity: 3 35 | snap: 1 36 | invert: 0 37 | type: 0 38 | axis: 0 39 | joyNum: 0 40 | - serializedVersion: 3 41 | m_Name: Fire1 42 | descriptiveName: 43 | descriptiveNegativeName: 44 | negativeButton: 45 | positiveButton: left ctrl 46 | altNegativeButton: 47 | altPositiveButton: mouse 0 48 | gravity: 1000 49 | dead: 0.001 50 | sensitivity: 1000 51 | snap: 0 52 | invert: 0 53 | type: 0 54 | axis: 0 55 | joyNum: 0 56 | - serializedVersion: 3 57 | m_Name: Fire2 58 | descriptiveName: 59 | descriptiveNegativeName: 60 | negativeButton: 61 | positiveButton: left alt 62 | altNegativeButton: 63 | altPositiveButton: mouse 1 64 | gravity: 1000 65 | dead: 0.001 66 | sensitivity: 1000 67 | snap: 0 68 | invert: 0 69 | type: 0 70 | axis: 0 71 | joyNum: 0 72 | - serializedVersion: 3 73 | m_Name: Fire3 74 | descriptiveName: 75 | descriptiveNegativeName: 76 | negativeButton: 77 | positiveButton: left shift 78 | altNegativeButton: 79 | altPositiveButton: mouse 2 80 | gravity: 1000 81 | dead: 0.001 82 | sensitivity: 1000 83 | snap: 0 84 | invert: 0 85 | type: 0 86 | axis: 0 87 | joyNum: 0 88 | - serializedVersion: 3 89 | m_Name: Jump 90 | descriptiveName: 91 | descriptiveNegativeName: 92 | negativeButton: 93 | positiveButton: space 94 | altNegativeButton: 95 | altPositiveButton: 96 | gravity: 1000 97 | dead: 0.001 98 | sensitivity: 1000 99 | snap: 0 100 | invert: 0 101 | type: 0 102 | axis: 0 103 | joyNum: 0 104 | - serializedVersion: 3 105 | m_Name: Mouse X 106 | descriptiveName: 107 | descriptiveNegativeName: 108 | negativeButton: 109 | positiveButton: 110 | altNegativeButton: 111 | altPositiveButton: 112 | gravity: 0 113 | dead: 0 114 | sensitivity: 0.1 115 | snap: 0 116 | invert: 0 117 | type: 1 118 | axis: 0 119 | joyNum: 0 120 | - serializedVersion: 3 121 | m_Name: Mouse Y 122 | descriptiveName: 123 | descriptiveNegativeName: 124 | negativeButton: 125 | positiveButton: 126 | altNegativeButton: 127 | altPositiveButton: 128 | gravity: 0 129 | dead: 0 130 | sensitivity: 0.1 131 | snap: 0 132 | invert: 0 133 | type: 1 134 | axis: 1 135 | joyNum: 0 136 | - serializedVersion: 3 137 | m_Name: Mouse ScrollWheel 138 | descriptiveName: 139 | descriptiveNegativeName: 140 | negativeButton: 141 | positiveButton: 142 | altNegativeButton: 143 | altPositiveButton: 144 | gravity: 0 145 | dead: 0 146 | sensitivity: 0.1 147 | snap: 0 148 | invert: 0 149 | type: 1 150 | axis: 2 151 | joyNum: 0 152 | - serializedVersion: 3 153 | m_Name: Horizontal 154 | descriptiveName: 155 | descriptiveNegativeName: 156 | negativeButton: 157 | positiveButton: 158 | altNegativeButton: 159 | altPositiveButton: 160 | gravity: 0 161 | dead: 0.19 162 | sensitivity: 1 163 | snap: 0 164 | invert: 0 165 | type: 2 166 | axis: 0 167 | joyNum: 0 168 | - serializedVersion: 3 169 | m_Name: Vertical 170 | descriptiveName: 171 | descriptiveNegativeName: 172 | negativeButton: 173 | positiveButton: 174 | altNegativeButton: 175 | altPositiveButton: 176 | gravity: 0 177 | dead: 0.19 178 | sensitivity: 1 179 | snap: 0 180 | invert: 1 181 | type: 2 182 | axis: 1 183 | joyNum: 0 184 | - serializedVersion: 3 185 | m_Name: Fire1 186 | descriptiveName: 187 | descriptiveNegativeName: 188 | negativeButton: 189 | positiveButton: joystick button 0 190 | altNegativeButton: 191 | altPositiveButton: 192 | gravity: 1000 193 | dead: 0.001 194 | sensitivity: 1000 195 | snap: 0 196 | invert: 0 197 | type: 0 198 | axis: 0 199 | joyNum: 0 200 | - serializedVersion: 3 201 | m_Name: Fire2 202 | descriptiveName: 203 | descriptiveNegativeName: 204 | negativeButton: 205 | positiveButton: joystick button 1 206 | altNegativeButton: 207 | altPositiveButton: 208 | gravity: 1000 209 | dead: 0.001 210 | sensitivity: 1000 211 | snap: 0 212 | invert: 0 213 | type: 0 214 | axis: 0 215 | joyNum: 0 216 | - serializedVersion: 3 217 | m_Name: Fire3 218 | descriptiveName: 219 | descriptiveNegativeName: 220 | negativeButton: 221 | positiveButton: joystick button 2 222 | altNegativeButton: 223 | altPositiveButton: 224 | gravity: 1000 225 | dead: 0.001 226 | sensitivity: 1000 227 | snap: 0 228 | invert: 0 229 | type: 0 230 | axis: 0 231 | joyNum: 0 232 | - serializedVersion: 3 233 | m_Name: Jump 234 | descriptiveName: 235 | descriptiveNegativeName: 236 | negativeButton: 237 | positiveButton: joystick button 3 238 | altNegativeButton: 239 | altPositiveButton: 240 | gravity: 1000 241 | dead: 0.001 242 | sensitivity: 1000 243 | snap: 0 244 | invert: 0 245 | type: 0 246 | axis: 0 247 | joyNum: 0 248 | - serializedVersion: 3 249 | m_Name: Submit 250 | descriptiveName: 251 | descriptiveNegativeName: 252 | negativeButton: 253 | positiveButton: return 254 | altNegativeButton: 255 | altPositiveButton: joystick button 0 256 | gravity: 1000 257 | dead: 0.001 258 | sensitivity: 1000 259 | snap: 0 260 | invert: 0 261 | type: 0 262 | axis: 0 263 | joyNum: 0 264 | - serializedVersion: 3 265 | m_Name: Submit 266 | descriptiveName: 267 | descriptiveNegativeName: 268 | negativeButton: 269 | positiveButton: enter 270 | altNegativeButton: 271 | altPositiveButton: space 272 | gravity: 1000 273 | dead: 0.001 274 | sensitivity: 1000 275 | snap: 0 276 | invert: 0 277 | type: 0 278 | axis: 0 279 | joyNum: 0 280 | - serializedVersion: 3 281 | m_Name: Cancel 282 | descriptiveName: 283 | descriptiveNegativeName: 284 | negativeButton: 285 | positiveButton: escape 286 | altNegativeButton: 287 | altPositiveButton: joystick button 1 288 | gravity: 1000 289 | dead: 0.001 290 | sensitivity: 1000 291 | snap: 0 292 | invert: 0 293 | type: 0 294 | axis: 0 295 | joyNum: 0 296 | -------------------------------------------------------------------------------- /ProjectSettings/NavMeshAreas.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!126 &1 4 | NavMeshProjectSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | areas: 8 | - name: Walkable 9 | cost: 1 10 | - name: Not Walkable 11 | cost: 1 12 | - name: Jump 13 | cost: 2 14 | - name: 15 | cost: 1 16 | - name: 17 | cost: 1 18 | - name: 19 | cost: 1 20 | - name: 21 | cost: 1 22 | - name: 23 | cost: 1 24 | - name: 25 | cost: 1 26 | - name: 27 | cost: 1 28 | - name: 29 | cost: 1 30 | - name: 31 | cost: 1 32 | - name: 33 | cost: 1 34 | - name: 35 | cost: 1 36 | - name: 37 | cost: 1 38 | - name: 39 | cost: 1 40 | - name: 41 | cost: 1 42 | - name: 43 | cost: 1 44 | - name: 45 | cost: 1 46 | - name: 47 | cost: 1 48 | - name: 49 | cost: 1 50 | - name: 51 | cost: 1 52 | - name: 53 | cost: 1 54 | - name: 55 | cost: 1 56 | - name: 57 | cost: 1 58 | - name: 59 | cost: 1 60 | - name: 61 | cost: 1 62 | - name: 63 | cost: 1 64 | - name: 65 | cost: 1 66 | - name: 67 | cost: 1 68 | - name: 69 | cost: 1 70 | - name: 71 | cost: 1 72 | m_LastAgentTypeID: -887442657 73 | m_Settings: 74 | - serializedVersion: 2 75 | agentTypeID: 0 76 | agentRadius: 0.5 77 | agentHeight: 2 78 | agentSlope: 45 79 | agentClimb: 0.75 80 | ledgeDropHeight: 0 81 | maxJumpAcrossDistance: 0 82 | minRegionArea: 2 83 | manualCellSize: 0 84 | cellSize: 0.16666667 85 | manualTileSize: 0 86 | tileSize: 256 87 | accuratePlacement: 0 88 | debug: 89 | m_Flags: 0 90 | m_SettingNames: 91 | - Humanoid 92 | -------------------------------------------------------------------------------- /ProjectSettings/PackageManagerSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &1 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 61 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 13960, guid: 0000000000000000e000000000000000, type: 0} 13 | m_Name: 14 | m_EditorClassIdentifier: 15 | m_ScopedRegistriesSettingsExpanded: 1 16 | oneTimeWarningShown: 0 17 | m_Registries: 18 | - m_Id: main 19 | m_Name: 20 | m_Url: https://packages.unity.cn 21 | m_Scopes: [] 22 | m_IsDefault: 1 23 | m_UserSelectedRegistryName: 24 | m_UserAddingNewScopedRegistry: 0 25 | m_RegistryInfoDraft: 26 | m_ErrorMessage: 27 | m_Original: 28 | m_Id: 29 | m_Name: 30 | m_Url: 31 | m_Scopes: [] 32 | m_IsDefault: 0 33 | m_Modified: 0 34 | m_Name: 35 | m_Url: 36 | m_Scopes: 37 | - 38 | m_SelectedScopeIndex: 0 39 | -------------------------------------------------------------------------------- /ProjectSettings/Physics2DSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!19 &1 4 | Physics2DSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 4 7 | m_Gravity: {x: 0, y: -9.81} 8 | m_DefaultMaterial: {fileID: 0} 9 | m_VelocityIterations: 8 10 | m_PositionIterations: 3 11 | m_VelocityThreshold: 1 12 | m_MaxLinearCorrection: 0.2 13 | m_MaxAngularCorrection: 8 14 | m_MaxTranslationSpeed: 100 15 | m_MaxRotationSpeed: 360 16 | m_BaumgarteScale: 0.2 17 | m_BaumgarteTimeOfImpactScale: 0.75 18 | m_TimeToSleep: 0.5 19 | m_LinearSleepTolerance: 0.01 20 | m_AngularSleepTolerance: 2 21 | m_DefaultContactOffset: 0.01 22 | m_JobOptions: 23 | serializedVersion: 2 24 | useMultithreading: 0 25 | useConsistencySorting: 0 26 | m_InterpolationPosesPerJob: 100 27 | m_NewContactsPerJob: 30 28 | m_CollideContactsPerJob: 100 29 | m_ClearFlagsPerJob: 200 30 | m_ClearBodyForcesPerJob: 200 31 | m_SyncDiscreteFixturesPerJob: 50 32 | m_SyncContinuousFixturesPerJob: 50 33 | m_FindNearestContactsPerJob: 100 34 | m_UpdateTriggerContactsPerJob: 100 35 | m_IslandSolverCostThreshold: 100 36 | m_IslandSolverBodyCostScale: 1 37 | m_IslandSolverContactCostScale: 10 38 | m_IslandSolverJointCostScale: 10 39 | m_IslandSolverBodiesPerJob: 50 40 | m_IslandSolverContactsPerJob: 50 41 | m_AutoSimulation: 1 42 | m_QueriesHitTriggers: 1 43 | m_QueriesStartInColliders: 1 44 | m_CallbacksOnDisable: 1 45 | m_ReuseCollisionCallbacks: 1 46 | m_AutoSyncTransforms: 0 47 | m_AlwaysShowColliders: 0 48 | m_ShowColliderSleep: 1 49 | m_ShowColliderContacts: 0 50 | m_ShowColliderAABB: 0 51 | m_ContactArrowScale: 0.2 52 | m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} 53 | m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} 54 | m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} 55 | m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} 56 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 57 | -------------------------------------------------------------------------------- /ProjectSettings/PresetManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1386491679 &1 4 | PresetManager: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_DefaultPresets: {} 8 | -------------------------------------------------------------------------------- /ProjectSettings/ProjectSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!129 &1 4 | PlayerSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 20 7 | productGUID: 9df2ed2fd15474d6d9872e36790005b1 8 | AndroidProfiler: 0 9 | AndroidFilterTouchesWhenObscured: 0 10 | AndroidEnableSustainedPerformanceMode: 0 11 | defaultScreenOrientation: 4 12 | targetDevice: 2 13 | useOnDemandResources: 0 14 | accelerometerFrequency: 60 15 | companyName: DefaultCompany 16 | productName: IFixTest 17 | defaultCursor: {fileID: 0} 18 | cursorHotspot: {x: 0, y: 0} 19 | m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} 20 | m_ShowUnitySplashScreen: 1 21 | m_ShowUnitySplashLogo: 1 22 | m_SplashScreenOverlayOpacity: 1 23 | m_SplashScreenAnimation: 1 24 | m_SplashScreenLogoStyle: 1 25 | m_SplashScreenDrawMode: 0 26 | m_SplashScreenBackgroundAnimationZoom: 1 27 | m_SplashScreenLogoAnimationZoom: 1 28 | m_SplashScreenBackgroundLandscapeAspect: 1 29 | m_SplashScreenBackgroundPortraitAspect: 1 30 | m_SplashScreenBackgroundLandscapeUvs: 31 | serializedVersion: 2 32 | x: 0 33 | y: 0 34 | width: 1 35 | height: 1 36 | m_SplashScreenBackgroundPortraitUvs: 37 | serializedVersion: 2 38 | x: 0 39 | y: 0 40 | width: 1 41 | height: 1 42 | m_SplashScreenLogos: [] 43 | m_VirtualRealitySplashScreen: {fileID: 0} 44 | m_ShowUnitySplashAds: 0 45 | m_AdsAndroidGameId: 46 | m_AdsIosGameId: 47 | m_ShowSplashAdsSlogan: 0 48 | m_SloganImage: {fileID: 0} 49 | m_SloganHeight: 150 50 | m_HolographicTrackingLossScreen: {fileID: 0} 51 | defaultScreenWidth: 1024 52 | defaultScreenHeight: 768 53 | defaultScreenWidthWeb: 960 54 | defaultScreenHeightWeb: 600 55 | m_StereoRenderingPath: 0 56 | m_ActiveColorSpace: 0 57 | m_MTRendering: 1 58 | m_StackTraceTypes: 010000000100000001000000010000000100000001000000 59 | iosShowActivityIndicatorOnLoading: -1 60 | androidShowActivityIndicatorOnLoading: -1 61 | iosUseCustomAppBackgroundBehavior: 0 62 | iosAllowHTTPDownload: 1 63 | allowedAutorotateToPortrait: 1 64 | allowedAutorotateToPortraitUpsideDown: 1 65 | allowedAutorotateToLandscapeRight: 1 66 | allowedAutorotateToLandscapeLeft: 1 67 | useOSAutorotation: 1 68 | use32BitDisplayBuffer: 1 69 | preserveFramebufferAlpha: 0 70 | disableDepthAndStencilBuffers: 0 71 | androidStartInFullscreen: 1 72 | androidRenderOutsideSafeArea: 1 73 | androidUseSwappy: 0 74 | androidBlitType: 0 75 | defaultIsNativeResolution: 1 76 | macRetinaSupport: 1 77 | runInBackground: 1 78 | captureSingleScreen: 0 79 | muteOtherAudioSources: 0 80 | Prepare IOS For Recording: 0 81 | Force IOS Speakers When Recording: 0 82 | deferSystemGesturesMode: 0 83 | hideHomeButton: 0 84 | submitAnalytics: 1 85 | usePlayerLog: 1 86 | bakeCollisionMeshes: 0 87 | forceSingleInstance: 0 88 | useFlipModelSwapchain: 1 89 | resizableWindow: 0 90 | useMacAppStoreValidation: 0 91 | macAppStoreCategory: public.app-category.games 92 | gpuSkinning: 1 93 | xboxPIXTextureCapture: 0 94 | xboxEnableAvatar: 0 95 | xboxEnableKinect: 0 96 | xboxEnableKinectAutoTracking: 0 97 | xboxEnableFitness: 0 98 | visibleInBackground: 1 99 | allowFullscreenSwitch: 1 100 | fullscreenMode: 1 101 | xboxSpeechDB: 0 102 | xboxEnableHeadOrientation: 0 103 | xboxEnableGuest: 0 104 | xboxEnablePIXSampling: 0 105 | metalFramebufferOnly: 0 106 | xboxOneResolution: 0 107 | xboxOneSResolution: 0 108 | xboxOneXResolution: 3 109 | xboxOneMonoLoggingLevel: 0 110 | xboxOneLoggingLevel: 1 111 | xboxOneDisableEsram: 0 112 | xboxOneEnableTypeOptimization: 0 113 | xboxOnePresentImmediateThreshold: 0 114 | switchQueueCommandMemory: 0 115 | switchQueueControlMemory: 16384 116 | switchQueueComputeMemory: 262144 117 | switchNVNShaderPoolsGranularity: 33554432 118 | switchNVNDefaultPoolsGranularity: 16777216 119 | switchNVNOtherPoolsGranularity: 16777216 120 | switchNVNMaxPublicTextureIDCount: 0 121 | switchNVNMaxPublicSamplerIDCount: 0 122 | stadiaPresentMode: 0 123 | stadiaTargetFramerate: 0 124 | vulkanNumSwapchainBuffers: 3 125 | vulkanEnableSetSRGBWrite: 0 126 | vulkanEnableLateAcquireNextImage: 0 127 | useSecurityBuild: 0 128 | m_SupportedAspectRatios: 129 | 4:3: 1 130 | 5:4: 1 131 | 16:10: 1 132 | 16:9: 1 133 | Others: 1 134 | bundleVersion: 0.1 135 | preloadedAssets: [] 136 | metroInputSource: 0 137 | wsaTransparentSwapchain: 0 138 | m_HolographicPauseOnTrackingLoss: 1 139 | xboxOneDisableKinectGpuReservation: 1 140 | xboxOneEnable7thCore: 1 141 | vrSettings: 142 | cardboard: 143 | depthFormat: 0 144 | enableTransitionView: 0 145 | daydream: 146 | depthFormat: 0 147 | useSustainedPerformanceMode: 0 148 | enableVideoLayer: 0 149 | useProtectedVideoMemory: 0 150 | minimumSupportedHeadTracking: 0 151 | maximumSupportedHeadTracking: 1 152 | hololens: 153 | depthFormat: 1 154 | depthBufferSharingEnabled: 1 155 | lumin: 156 | depthFormat: 0 157 | frameTiming: 2 158 | enableGLCache: 0 159 | glCacheMaxBlobSize: 524288 160 | glCacheMaxFileSize: 8388608 161 | oculus: 162 | sharedDepthBuffer: 1 163 | dashSupport: 1 164 | lowOverheadMode: 0 165 | protectedContext: 0 166 | v2Signing: 1 167 | enable360StereoCapture: 0 168 | isWsaHolographicRemotingEnabled: 0 169 | enableFrameTimingStats: 0 170 | useHDRDisplay: 0 171 | D3DHDRBitDepth: 0 172 | m_ColorGamuts: 00000000 173 | targetPixelDensity: 30 174 | resolutionScalingMode: 0 175 | androidSupportedAspectRatio: 1 176 | androidMaxAspectRatio: 2.1 177 | applicationIdentifier: {} 178 | buildNumber: {} 179 | AndroidBundleVersionCode: 1 180 | AndroidMinSdkVersion: 19 181 | AndroidTargetSdkVersion: 0 182 | AndroidPreferredInstallLocation: 1 183 | aotOptions: 184 | stripEngineCode: 1 185 | iPhoneStrippingLevel: 0 186 | iPhoneScriptCallOptimization: 0 187 | ForceInternetPermission: 0 188 | ForceSDCardPermission: 0 189 | CreateWallpaper: 0 190 | APKExpansionFiles: 0 191 | keepLoadedShadersAlive: 0 192 | StripUnusedMeshComponents: 1 193 | VertexChannelCompressionMask: 4054 194 | iPhoneSdkVersion: 988 195 | iOSTargetOSVersionString: 10.0 196 | tvOSSdkVersion: 0 197 | tvOSRequireExtendedGameController: 0 198 | tvOSTargetOSVersionString: 10.0 199 | uIPrerenderedIcon: 0 200 | uIRequiresPersistentWiFi: 0 201 | uIRequiresFullScreen: 1 202 | uIStatusBarHidden: 1 203 | uIExitOnSuspend: 0 204 | uIStatusBarStyle: 0 205 | appleTVSplashScreen: {fileID: 0} 206 | appleTVSplashScreen2x: {fileID: 0} 207 | tvOSSmallIconLayers: [] 208 | tvOSSmallIconLayers2x: [] 209 | tvOSLargeIconLayers: [] 210 | tvOSLargeIconLayers2x: [] 211 | tvOSTopShelfImageLayers: [] 212 | tvOSTopShelfImageLayers2x: [] 213 | tvOSTopShelfImageWideLayers: [] 214 | tvOSTopShelfImageWideLayers2x: [] 215 | iOSLaunchScreenType: 0 216 | iOSLaunchScreenPortrait: {fileID: 0} 217 | iOSLaunchScreenLandscape: {fileID: 0} 218 | iOSLaunchScreenBackgroundColor: 219 | serializedVersion: 2 220 | rgba: 0 221 | iOSLaunchScreenFillPct: 100 222 | iOSLaunchScreenSize: 100 223 | iOSLaunchScreenCustomXibPath: 224 | iOSLaunchScreeniPadType: 0 225 | iOSLaunchScreeniPadImage: {fileID: 0} 226 | iOSLaunchScreeniPadBackgroundColor: 227 | serializedVersion: 2 228 | rgba: 0 229 | iOSLaunchScreeniPadFillPct: 100 230 | iOSLaunchScreeniPadSize: 100 231 | iOSLaunchScreeniPadCustomXibPath: 232 | iOSUseLaunchScreenStoryboard: 0 233 | iOSLaunchScreenCustomStoryboardPath: 234 | iOSDeviceRequirements: [] 235 | iOSURLSchemes: [] 236 | iOSBackgroundModes: 0 237 | iOSMetalForceHardShadows: 0 238 | metalEditorSupport: 1 239 | metalAPIValidation: 1 240 | iOSRenderExtraFrameOnPause: 0 241 | iosCopyPluginsCodeInsteadOfSymlink: 0 242 | appleDeveloperTeamID: 243 | iOSManualSigningProvisioningProfileID: 244 | tvOSManualSigningProvisioningProfileID: 245 | iOSManualSigningProvisioningProfileType: 0 246 | tvOSManualSigningProvisioningProfileType: 0 247 | appleEnableAutomaticSigning: 0 248 | iOSRequireARKit: 0 249 | iOSAutomaticallyDetectAndAddCapabilities: 1 250 | appleEnableProMotion: 0 251 | clonedFromGUID: c0afd0d1d80e3634a9dac47e8a0426ea 252 | templatePackageId: com.unity.template.3d@4.2.8 253 | templateDefaultScene: Assets/Scenes/SampleScene.unity 254 | AndroidTargetArchitectures: 1 255 | AndroidSplashScreenScale: 0 256 | androidSplashScreen: {fileID: 0} 257 | AndroidKeystoreName: 258 | AndroidKeyaliasName: 259 | AndroidBuildApkPerCpuArchitecture: 0 260 | AndroidTVCompatibility: 0 261 | AndroidIsGame: 1 262 | AndroidEnableTango: 0 263 | androidEnableBanner: 1 264 | androidUseLowAccuracyLocation: 0 265 | androidUseCustomKeystore: 0 266 | m_AndroidBanners: 267 | - width: 320 268 | height: 180 269 | banner: {fileID: 0} 270 | androidGamepadSupportLevel: 0 271 | AndroidValidateAppBundleSize: 1 272 | AndroidAppBundleSizeToValidate: 150 273 | m_BuildTargetIcons: [] 274 | m_BuildTargetPlatformIcons: [] 275 | m_BuildTargetBatching: 276 | - m_BuildTarget: Standalone 277 | m_StaticBatching: 1 278 | m_DynamicBatching: 0 279 | - m_BuildTarget: tvOS 280 | m_StaticBatching: 1 281 | m_DynamicBatching: 0 282 | - m_BuildTarget: Android 283 | m_StaticBatching: 1 284 | m_DynamicBatching: 0 285 | - m_BuildTarget: iPhone 286 | m_StaticBatching: 1 287 | m_DynamicBatching: 0 288 | - m_BuildTarget: WebGL 289 | m_StaticBatching: 0 290 | m_DynamicBatching: 0 291 | m_BuildTargetEncrypting: [] 292 | m_BuildTargetGraphicsJobs: 293 | - m_BuildTarget: MacStandaloneSupport 294 | m_GraphicsJobs: 0 295 | - m_BuildTarget: Switch 296 | m_GraphicsJobs: 1 297 | - m_BuildTarget: MetroSupport 298 | m_GraphicsJobs: 1 299 | - m_BuildTarget: AppleTVSupport 300 | m_GraphicsJobs: 0 301 | - m_BuildTarget: BJMSupport 302 | m_GraphicsJobs: 1 303 | - m_BuildTarget: LinuxStandaloneSupport 304 | m_GraphicsJobs: 1 305 | - m_BuildTarget: PS4Player 306 | m_GraphicsJobs: 1 307 | - m_BuildTarget: iOSSupport 308 | m_GraphicsJobs: 0 309 | - m_BuildTarget: WindowsStandaloneSupport 310 | m_GraphicsJobs: 1 311 | - m_BuildTarget: XboxOnePlayer 312 | m_GraphicsJobs: 1 313 | - m_BuildTarget: LuminSupport 314 | m_GraphicsJobs: 0 315 | - m_BuildTarget: AndroidPlayer 316 | m_GraphicsJobs: 0 317 | - m_BuildTarget: WebGLSupport 318 | m_GraphicsJobs: 0 319 | m_BuildTargetGraphicsJobMode: 320 | - m_BuildTarget: PS4Player 321 | m_GraphicsJobMode: 0 322 | - m_BuildTarget: XboxOnePlayer 323 | m_GraphicsJobMode: 0 324 | m_BuildTargetGraphicsAPIs: 325 | - m_BuildTarget: AndroidPlayer 326 | m_APIs: 150000000b000000 327 | m_Automatic: 0 328 | - m_BuildTarget: iOSSupport 329 | m_APIs: 10000000 330 | m_Automatic: 1 331 | - m_BuildTarget: AppleTVSupport 332 | m_APIs: 10000000 333 | m_Automatic: 0 334 | - m_BuildTarget: WebGLSupport 335 | m_APIs: 0b000000 336 | m_Automatic: 1 337 | m_BuildTargetVRSettings: 338 | - m_BuildTarget: Standalone 339 | m_Enabled: 0 340 | m_Devices: 341 | - Oculus 342 | - OpenVR 343 | openGLRequireES31: 0 344 | openGLRequireES31AEP: 0 345 | openGLRequireES32: 0 346 | m_TemplateCustomTags: {} 347 | mobileMTRendering: 348 | Android: 1 349 | iPhone: 1 350 | tvOS: 1 351 | m_BuildTargetGroupLightmapEncodingQuality: [] 352 | m_BuildTargetGroupLightmapSettings: [] 353 | playModeTestRunnerEnabled: 0 354 | runPlayModeTestAsEditModeTest: 0 355 | actionOnDotNetUnhandledException: 1 356 | enableInternalProfiler: 0 357 | logObjCUncaughtExceptions: 1 358 | enableCrashReportAPI: 0 359 | cameraUsageDescription: 360 | locationUsageDescription: 361 | microphoneUsageDescription: 362 | switchNetLibKey: 363 | switchSocketMemoryPoolSize: 6144 364 | switchSocketAllocatorPoolSize: 128 365 | switchSocketConcurrencyLimit: 14 366 | switchScreenResolutionBehavior: 2 367 | switchUseCPUProfiler: 0 368 | switchApplicationID: 0x01004b9000490000 369 | switchNSODependencies: 370 | switchTitleNames_0: 371 | switchTitleNames_1: 372 | switchTitleNames_2: 373 | switchTitleNames_3: 374 | switchTitleNames_4: 375 | switchTitleNames_5: 376 | switchTitleNames_6: 377 | switchTitleNames_7: 378 | switchTitleNames_8: 379 | switchTitleNames_9: 380 | switchTitleNames_10: 381 | switchTitleNames_11: 382 | switchTitleNames_12: 383 | switchTitleNames_13: 384 | switchTitleNames_14: 385 | switchPublisherNames_0: 386 | switchPublisherNames_1: 387 | switchPublisherNames_2: 388 | switchPublisherNames_3: 389 | switchPublisherNames_4: 390 | switchPublisherNames_5: 391 | switchPublisherNames_6: 392 | switchPublisherNames_7: 393 | switchPublisherNames_8: 394 | switchPublisherNames_9: 395 | switchPublisherNames_10: 396 | switchPublisherNames_11: 397 | switchPublisherNames_12: 398 | switchPublisherNames_13: 399 | switchPublisherNames_14: 400 | switchIcons_0: {fileID: 0} 401 | switchIcons_1: {fileID: 0} 402 | switchIcons_2: {fileID: 0} 403 | switchIcons_3: {fileID: 0} 404 | switchIcons_4: {fileID: 0} 405 | switchIcons_5: {fileID: 0} 406 | switchIcons_6: {fileID: 0} 407 | switchIcons_7: {fileID: 0} 408 | switchIcons_8: {fileID: 0} 409 | switchIcons_9: {fileID: 0} 410 | switchIcons_10: {fileID: 0} 411 | switchIcons_11: {fileID: 0} 412 | switchIcons_12: {fileID: 0} 413 | switchIcons_13: {fileID: 0} 414 | switchIcons_14: {fileID: 0} 415 | switchSmallIcons_0: {fileID: 0} 416 | switchSmallIcons_1: {fileID: 0} 417 | switchSmallIcons_2: {fileID: 0} 418 | switchSmallIcons_3: {fileID: 0} 419 | switchSmallIcons_4: {fileID: 0} 420 | switchSmallIcons_5: {fileID: 0} 421 | switchSmallIcons_6: {fileID: 0} 422 | switchSmallIcons_7: {fileID: 0} 423 | switchSmallIcons_8: {fileID: 0} 424 | switchSmallIcons_9: {fileID: 0} 425 | switchSmallIcons_10: {fileID: 0} 426 | switchSmallIcons_11: {fileID: 0} 427 | switchSmallIcons_12: {fileID: 0} 428 | switchSmallIcons_13: {fileID: 0} 429 | switchSmallIcons_14: {fileID: 0} 430 | switchManualHTML: 431 | switchAccessibleURLs: 432 | switchLegalInformation: 433 | switchMainThreadStackSize: 1048576 434 | switchPresenceGroupId: 435 | switchLogoHandling: 0 436 | switchReleaseVersion: 0 437 | switchDisplayVersion: 1.0.0 438 | switchStartupUserAccount: 0 439 | switchTouchScreenUsage: 0 440 | switchSupportedLanguagesMask: 0 441 | switchLogoType: 0 442 | switchApplicationErrorCodeCategory: 443 | switchUserAccountSaveDataSize: 0 444 | switchUserAccountSaveDataJournalSize: 0 445 | switchApplicationAttribute: 0 446 | switchCardSpecSize: -1 447 | switchCardSpecClock: -1 448 | switchRatingsMask: 0 449 | switchRatingsInt_0: 0 450 | switchRatingsInt_1: 0 451 | switchRatingsInt_2: 0 452 | switchRatingsInt_3: 0 453 | switchRatingsInt_4: 0 454 | switchRatingsInt_5: 0 455 | switchRatingsInt_6: 0 456 | switchRatingsInt_7: 0 457 | switchRatingsInt_8: 0 458 | switchRatingsInt_9: 0 459 | switchRatingsInt_10: 0 460 | switchRatingsInt_11: 0 461 | switchRatingsInt_12: 0 462 | switchLocalCommunicationIds_0: 463 | switchLocalCommunicationIds_1: 464 | switchLocalCommunicationIds_2: 465 | switchLocalCommunicationIds_3: 466 | switchLocalCommunicationIds_4: 467 | switchLocalCommunicationIds_5: 468 | switchLocalCommunicationIds_6: 469 | switchLocalCommunicationIds_7: 470 | switchParentalControl: 0 471 | switchAllowsScreenshot: 1 472 | switchAllowsVideoCapturing: 1 473 | switchAllowsRuntimeAddOnContentInstall: 0 474 | switchDataLossConfirmation: 0 475 | switchUserAccountLockEnabled: 0 476 | switchSystemResourceMemory: 16777216 477 | switchSupportedNpadStyles: 22 478 | switchNativeFsCacheSize: 32 479 | switchIsHoldTypeHorizontal: 0 480 | switchSupportedNpadCount: 8 481 | switchSocketConfigEnabled: 0 482 | switchTcpInitialSendBufferSize: 32 483 | switchTcpInitialReceiveBufferSize: 64 484 | switchTcpAutoSendBufferSizeMax: 256 485 | switchTcpAutoReceiveBufferSizeMax: 256 486 | switchUdpSendBufferSize: 9 487 | switchUdpReceiveBufferSize: 42 488 | switchSocketBufferEfficiency: 4 489 | switchSocketInitializeEnabled: 1 490 | switchNetworkInterfaceManagerInitializeEnabled: 1 491 | switchPlayerConnectionEnabled: 1 492 | ps4NPAgeRating: 12 493 | ps4NPTitleSecret: 494 | ps4NPTrophyPackPath: 495 | ps4ParentalLevel: 11 496 | ps4ContentID: ED1633-NPXX51362_00-0000000000000000 497 | ps4Category: 0 498 | ps4MasterVersion: 01.00 499 | ps4AppVersion: 01.00 500 | ps4AppType: 0 501 | ps4ParamSfxPath: 502 | ps4VideoOutPixelFormat: 0 503 | ps4VideoOutInitialWidth: 1920 504 | ps4VideoOutBaseModeInitialWidth: 1920 505 | ps4VideoOutReprojectionRate: 60 506 | ps4PronunciationXMLPath: 507 | ps4PronunciationSIGPath: 508 | ps4BackgroundImagePath: 509 | ps4StartupImagePath: 510 | ps4StartupImagesFolder: 511 | ps4IconImagesFolder: 512 | ps4SaveDataImagePath: 513 | ps4SdkOverride: 514 | ps4BGMPath: 515 | ps4ShareFilePath: 516 | ps4ShareOverlayImagePath: 517 | ps4PrivacyGuardImagePath: 518 | ps4ExtraSceSysFile: 519 | ps4NPtitleDatPath: 520 | ps4RemotePlayKeyAssignment: -1 521 | ps4RemotePlayKeyMappingDir: 522 | ps4PlayTogetherPlayerCount: 0 523 | ps4EnterButtonAssignment: 1 524 | ps4ApplicationParam1: 0 525 | ps4ApplicationParam2: 0 526 | ps4ApplicationParam3: 0 527 | ps4ApplicationParam4: 0 528 | ps4DownloadDataSize: 0 529 | ps4GarlicHeapSize: 2048 530 | ps4ProGarlicHeapSize: 2560 531 | playerPrefsMaxSize: 32768 532 | ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ 533 | ps4pnSessions: 1 534 | ps4pnPresence: 1 535 | ps4pnFriends: 1 536 | ps4pnGameCustomData: 1 537 | playerPrefsSupport: 0 538 | enableApplicationExit: 0 539 | resetTempFolder: 1 540 | restrictedAudioUsageRights: 0 541 | ps4UseResolutionFallback: 0 542 | ps4ReprojectionSupport: 0 543 | ps4UseAudio3dBackend: 0 544 | ps4UseLowGarlicFragmentationMode: 1 545 | ps4SocialScreenEnabled: 0 546 | ps4ScriptOptimizationLevel: 0 547 | ps4Audio3dVirtualSpeakerCount: 14 548 | ps4attribCpuUsage: 0 549 | ps4PatchPkgPath: 550 | ps4PatchLatestPkgPath: 551 | ps4PatchChangeinfoPath: 552 | ps4PatchDayOne: 0 553 | ps4attribUserManagement: 0 554 | ps4attribMoveSupport: 0 555 | ps4attrib3DSupport: 0 556 | ps4attribShareSupport: 0 557 | ps4attribExclusiveVR: 0 558 | ps4disableAutoHideSplash: 0 559 | ps4videoRecordingFeaturesUsed: 0 560 | ps4contentSearchFeaturesUsed: 0 561 | ps4CompatibilityPS5: 0 562 | ps4GPU800MHz: 1 563 | ps4attribEyeToEyeDistanceSettingVR: 0 564 | ps4IncludedModules: [] 565 | ps4attribVROutputEnabled: 0 566 | monoEnv: 567 | splashScreenBackgroundSourceLandscape: {fileID: 0} 568 | splashScreenBackgroundSourcePortrait: {fileID: 0} 569 | blurSplashScreenBackground: 1 570 | spritePackerPolicy: 571 | webGLMemorySize: 16 572 | webGLExceptionSupport: 1 573 | webGLNameFilesAsHashes: 0 574 | webGLDataCaching: 1 575 | webGLDebugSymbols: 0 576 | webGLEmscriptenArgs: 577 | webGLModulesDirectory: 578 | webGLTemplate: APPLICATION:Default 579 | webGLAnalyzeBuildSize: 0 580 | webGLUseEmbeddedResources: 0 581 | webGLCompressionFormat: 1 582 | webGLLinkerTarget: 1 583 | webGLThreadsSupport: 0 584 | webGLWasmStreaming: 0 585 | scriptingDefineSymbols: {} 586 | platformArchitecture: {} 587 | scriptingBackend: {} 588 | il2cppCompilerConfiguration: {} 589 | managedStrippingLevel: {} 590 | incrementalIl2cppBuild: {} 591 | allowUnsafeCode: 0 592 | additionalIl2CppArgs: 593 | scriptingRuntimeVersion: 1 594 | gcIncremental: 0 595 | assemblyVersionValidation: 1 596 | gcWBarrierValidation: 0 597 | apiCompatibilityLevelPerPlatform: {} 598 | m_RenderingPath: 1 599 | m_MobileRenderingPath: 1 600 | metroPackageName: Template_3D 601 | metroPackageVersion: 602 | metroCertificatePath: 603 | metroCertificatePassword: 604 | metroCertificateSubject: 605 | metroCertificateIssuer: 606 | metroCertificateNotAfter: 0000000000000000 607 | metroApplicationDescription: Template_3D 608 | wsaImages: {} 609 | metroTileShortName: 610 | metroTileShowName: 0 611 | metroMediumTileShowName: 0 612 | metroLargeTileShowName: 0 613 | metroWideTileShowName: 0 614 | metroSupportStreamingInstall: 0 615 | metroLastRequiredScene: 0 616 | metroDefaultTileSize: 1 617 | metroTileForegroundText: 2 618 | metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} 619 | metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, 620 | a: 1} 621 | metroSplashScreenUseBackgroundColor: 0 622 | platformCapabilities: {} 623 | metroTargetDeviceFamilies: {} 624 | metroFTAName: 625 | metroFTAFileTypes: [] 626 | metroProtocolName: 627 | XboxOneProductId: 628 | XboxOneUpdateKey: 629 | XboxOneSandboxId: 630 | XboxOneContentId: 631 | XboxOneTitleId: 632 | XboxOneSCId: 633 | XboxOneGameOsOverridePath: 634 | XboxOnePackagingOverridePath: 635 | XboxOneAppManifestOverridePath: 636 | XboxOneVersion: 1.0.0.0 637 | XboxOnePackageEncryption: 0 638 | XboxOnePackageUpdateGranularity: 2 639 | XboxOneDescription: 640 | XboxOneLanguage: 641 | - enus 642 | XboxOneCapability: [] 643 | XboxOneGameRating: {} 644 | XboxOneIsContentPackage: 0 645 | XboxOneEnhancedXboxCompatibilityMode: 0 646 | XboxOneEnableGPUVariability: 1 647 | XboxOneSockets: {} 648 | XboxOneSplashScreen: {fileID: 0} 649 | XboxOneAllowedProductIds: [] 650 | XboxOnePersistentLocalStorageSize: 0 651 | XboxOneXTitleMemory: 8 652 | XboxOneOverrideIdentityName: 653 | XboxOneOverrideIdentityPublisher: 654 | vrEditorSettings: 655 | daydream: 656 | daydreamIconForeground: {fileID: 0} 657 | daydreamIconBackground: {fileID: 0} 658 | cloudServicesEnabled: 659 | UNet: 1 660 | luminIcon: 661 | m_Name: 662 | m_ModelFolderPath: 663 | m_PortalFolderPath: 664 | luminCert: 665 | m_CertPath: 666 | m_SignPackage: 1 667 | luminIsChannelApp: 0 668 | luminVersion: 669 | m_VersionCode: 1 670 | m_VersionName: 671 | apiCompatibilityLevel: 6 672 | cloudProjectId: 673 | framebufferDepthMemorylessMode: 0 674 | projectName: 675 | organizationId: 676 | cloudEnabled: 0 677 | enableNativePlatformBackendsForNewInputSystem: 0 678 | disableOldInputManagerSupport: 0 679 | legacyClampBlendShapeWeights: 0 680 | -------------------------------------------------------------------------------- /ProjectSettings/ProjectVersion.txt: -------------------------------------------------------------------------------- 1 | m_EditorVersion: 2019.4.17f1c1 2 | m_EditorVersionWithRevision: 2019.4.17f1c1 (ab9d18516a31) 3 | -------------------------------------------------------------------------------- /ProjectSettings/QualitySettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!47 &1 4 | QualitySettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 5 7 | m_CurrentQuality: 5 8 | m_QualitySettings: 9 | - serializedVersion: 2 10 | name: Very Low 11 | pixelLightCount: 0 12 | shadows: 0 13 | shadowResolution: 0 14 | shadowProjection: 1 15 | shadowCascades: 1 16 | shadowDistance: 15 17 | shadowNearPlaneOffset: 3 18 | shadowCascade2Split: 0.33333334 19 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 20 | shadowmaskMode: 0 21 | blendWeights: 1 22 | textureQuality: 1 23 | anisotropicTextures: 0 24 | antiAliasing: 0 25 | softParticles: 0 26 | softVegetation: 0 27 | realtimeReflectionProbes: 0 28 | billboardsFaceCameraPosition: 0 29 | vSyncCount: 0 30 | lodBias: 0.3 31 | maximumLODLevel: 0 32 | streamingMipmapsActive: 0 33 | streamingMipmapsAddAllCameras: 1 34 | streamingMipmapsMemoryBudget: 512 35 | streamingMipmapsRenderersPerFrame: 512 36 | streamingMipmapsMaxLevelReduction: 2 37 | streamingMipmapsMaxFileIORequests: 1024 38 | particleRaycastBudget: 4 39 | asyncUploadTimeSlice: 2 40 | asyncUploadBufferSize: 16 41 | asyncUploadPersistentBuffer: 1 42 | resolutionScalingFixedDPIFactor: 1 43 | excludedTargetPlatforms: [] 44 | - serializedVersion: 2 45 | name: Low 46 | pixelLightCount: 0 47 | shadows: 0 48 | shadowResolution: 0 49 | shadowProjection: 1 50 | shadowCascades: 1 51 | shadowDistance: 20 52 | shadowNearPlaneOffset: 3 53 | shadowCascade2Split: 0.33333334 54 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 55 | shadowmaskMode: 0 56 | blendWeights: 2 57 | textureQuality: 0 58 | anisotropicTextures: 0 59 | antiAliasing: 0 60 | softParticles: 0 61 | softVegetation: 0 62 | realtimeReflectionProbes: 0 63 | billboardsFaceCameraPosition: 0 64 | vSyncCount: 0 65 | lodBias: 0.4 66 | maximumLODLevel: 0 67 | streamingMipmapsActive: 0 68 | streamingMipmapsAddAllCameras: 1 69 | streamingMipmapsMemoryBudget: 512 70 | streamingMipmapsRenderersPerFrame: 512 71 | streamingMipmapsMaxLevelReduction: 2 72 | streamingMipmapsMaxFileIORequests: 1024 73 | particleRaycastBudget: 16 74 | asyncUploadTimeSlice: 2 75 | asyncUploadBufferSize: 16 76 | asyncUploadPersistentBuffer: 1 77 | resolutionScalingFixedDPIFactor: 1 78 | excludedTargetPlatforms: [] 79 | - serializedVersion: 2 80 | name: Medium 81 | pixelLightCount: 1 82 | shadows: 1 83 | shadowResolution: 0 84 | shadowProjection: 1 85 | shadowCascades: 1 86 | shadowDistance: 20 87 | shadowNearPlaneOffset: 3 88 | shadowCascade2Split: 0.33333334 89 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 90 | shadowmaskMode: 0 91 | blendWeights: 2 92 | textureQuality: 0 93 | anisotropicTextures: 1 94 | antiAliasing: 0 95 | softParticles: 0 96 | softVegetation: 0 97 | realtimeReflectionProbes: 0 98 | billboardsFaceCameraPosition: 0 99 | vSyncCount: 1 100 | lodBias: 0.7 101 | maximumLODLevel: 0 102 | streamingMipmapsActive: 0 103 | streamingMipmapsAddAllCameras: 1 104 | streamingMipmapsMemoryBudget: 512 105 | streamingMipmapsRenderersPerFrame: 512 106 | streamingMipmapsMaxLevelReduction: 2 107 | streamingMipmapsMaxFileIORequests: 1024 108 | particleRaycastBudget: 64 109 | asyncUploadTimeSlice: 2 110 | asyncUploadBufferSize: 16 111 | asyncUploadPersistentBuffer: 1 112 | resolutionScalingFixedDPIFactor: 1 113 | excludedTargetPlatforms: [] 114 | - serializedVersion: 2 115 | name: High 116 | pixelLightCount: 2 117 | shadows: 2 118 | shadowResolution: 1 119 | shadowProjection: 1 120 | shadowCascades: 2 121 | shadowDistance: 40 122 | shadowNearPlaneOffset: 3 123 | shadowCascade2Split: 0.33333334 124 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 125 | shadowmaskMode: 1 126 | blendWeights: 2 127 | textureQuality: 0 128 | anisotropicTextures: 1 129 | antiAliasing: 0 130 | softParticles: 0 131 | softVegetation: 1 132 | realtimeReflectionProbes: 1 133 | billboardsFaceCameraPosition: 1 134 | vSyncCount: 1 135 | lodBias: 1 136 | maximumLODLevel: 0 137 | streamingMipmapsActive: 0 138 | streamingMipmapsAddAllCameras: 1 139 | streamingMipmapsMemoryBudget: 512 140 | streamingMipmapsRenderersPerFrame: 512 141 | streamingMipmapsMaxLevelReduction: 2 142 | streamingMipmapsMaxFileIORequests: 1024 143 | particleRaycastBudget: 256 144 | asyncUploadTimeSlice: 2 145 | asyncUploadBufferSize: 16 146 | asyncUploadPersistentBuffer: 1 147 | resolutionScalingFixedDPIFactor: 1 148 | excludedTargetPlatforms: [] 149 | - serializedVersion: 2 150 | name: Very High 151 | pixelLightCount: 3 152 | shadows: 2 153 | shadowResolution: 2 154 | shadowProjection: 1 155 | shadowCascades: 2 156 | shadowDistance: 70 157 | shadowNearPlaneOffset: 3 158 | shadowCascade2Split: 0.33333334 159 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 160 | shadowmaskMode: 1 161 | blendWeights: 4 162 | textureQuality: 0 163 | anisotropicTextures: 2 164 | antiAliasing: 2 165 | softParticles: 1 166 | softVegetation: 1 167 | realtimeReflectionProbes: 1 168 | billboardsFaceCameraPosition: 1 169 | vSyncCount: 1 170 | lodBias: 1.5 171 | maximumLODLevel: 0 172 | streamingMipmapsActive: 0 173 | streamingMipmapsAddAllCameras: 1 174 | streamingMipmapsMemoryBudget: 512 175 | streamingMipmapsRenderersPerFrame: 512 176 | streamingMipmapsMaxLevelReduction: 2 177 | streamingMipmapsMaxFileIORequests: 1024 178 | particleRaycastBudget: 1024 179 | asyncUploadTimeSlice: 2 180 | asyncUploadBufferSize: 16 181 | asyncUploadPersistentBuffer: 1 182 | resolutionScalingFixedDPIFactor: 1 183 | excludedTargetPlatforms: [] 184 | - serializedVersion: 2 185 | name: Ultra 186 | pixelLightCount: 4 187 | shadows: 2 188 | shadowResolution: 2 189 | shadowProjection: 1 190 | shadowCascades: 4 191 | shadowDistance: 150 192 | shadowNearPlaneOffset: 3 193 | shadowCascade2Split: 0.33333334 194 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 195 | shadowmaskMode: 1 196 | blendWeights: 4 197 | textureQuality: 0 198 | anisotropicTextures: 2 199 | antiAliasing: 2 200 | softParticles: 1 201 | softVegetation: 1 202 | realtimeReflectionProbes: 1 203 | billboardsFaceCameraPosition: 1 204 | vSyncCount: 1 205 | lodBias: 2 206 | maximumLODLevel: 0 207 | streamingMipmapsActive: 0 208 | streamingMipmapsAddAllCameras: 1 209 | streamingMipmapsMemoryBudget: 512 210 | streamingMipmapsRenderersPerFrame: 512 211 | streamingMipmapsMaxLevelReduction: 2 212 | streamingMipmapsMaxFileIORequests: 1024 213 | particleRaycastBudget: 4096 214 | asyncUploadTimeSlice: 2 215 | asyncUploadBufferSize: 16 216 | asyncUploadPersistentBuffer: 1 217 | resolutionScalingFixedDPIFactor: 1 218 | excludedTargetPlatforms: [] 219 | m_PerPlatformDefaultQuality: 220 | Android: 2 221 | Lumin: 5 222 | Nintendo 3DS: 5 223 | Nintendo Switch: 5 224 | PS4: 5 225 | PSP2: 2 226 | Stadia: 5 227 | Standalone: 5 228 | WebGL: 3 229 | Windows Store Apps: 5 230 | XboxOne: 5 231 | iPhone: 2 232 | tvOS: 2 233 | -------------------------------------------------------------------------------- /ProjectSettings/TagManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!78 &1 4 | TagManager: 5 | serializedVersion: 2 6 | tags: [] 7 | layers: 8 | - Default 9 | - TransparentFX 10 | - Ignore Raycast 11 | - 12 | - Water 13 | - UI 14 | - 15 | - 16 | - 17 | - 18 | - 19 | - 20 | - 21 | - 22 | - 23 | - 24 | - 25 | - 26 | - 27 | - 28 | - 29 | - 30 | - 31 | - 32 | - 33 | - 34 | - 35 | - 36 | - 37 | - 38 | - 39 | - 40 | m_SortingLayers: 41 | - name: Default 42 | uniqueID: 0 43 | locked: 0 44 | -------------------------------------------------------------------------------- /ProjectSettings/TimeManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!5 &1 4 | TimeManager: 5 | m_ObjectHideFlags: 0 6 | Fixed Timestep: 0.02 7 | Maximum Allowed Timestep: 0.33333334 8 | m_TimeScale: 1 9 | Maximum Particle Timestep: 0.03 10 | -------------------------------------------------------------------------------- /ProjectSettings/UnityConnectSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!310 &1 4 | UnityConnectSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 1 7 | m_Enabled: 0 8 | m_TestMode: 0 9 | m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events 10 | m_EventUrl: https://cdp.cloud.unity3d.com/v1/events 11 | m_ConfigUrl: https://config.uca.cloud.unity3d.com 12 | m_TestInitMode: 0 13 | CrashReportingSettings: 14 | m_EventUrl: https://perf-events.cloud.unity.cn 15 | m_Enabled: 0 16 | m_LogBufferSize: 10 17 | m_CaptureEditorExceptions: 1 18 | UnityPurchasingSettings: 19 | m_Enabled: 0 20 | m_TestMode: 0 21 | UnityAnalyticsSettings: 22 | m_Enabled: 0 23 | m_TestMode: 0 24 | m_InitializeOnStartup: 1 25 | UnityAdsSettings: 26 | m_Enabled: 0 27 | m_InitializeOnStartup: 1 28 | m_TestMode: 0 29 | m_IosGameId: 30 | m_AndroidGameId: 31 | m_GameIds: {} 32 | m_GameId: 33 | PerformanceReportingSettings: 34 | m_Enabled: 0 35 | -------------------------------------------------------------------------------- /ProjectSettings/VFXManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!937362698 &1 4 | VFXManager: 5 | m_ObjectHideFlags: 0 6 | m_IndirectShader: {fileID: 0} 7 | m_CopyBufferShader: {fileID: 0} 8 | m_SortShader: {fileID: 0} 9 | m_StripUpdateShader: {fileID: 0} 10 | m_RenderPipeSettingsPath: 11 | m_FixedTimeStep: 0.016666668 12 | m_MaxDeltaTime: 0.05 13 | -------------------------------------------------------------------------------- /ProjectSettings/XRSettings.asset: -------------------------------------------------------------------------------- 1 | { 2 | "m_SettingKeys": [ 3 | "VR Device Disabled", 4 | "VR Device User Alert" 5 | ], 6 | "m_SettingValues": [ 7 | "False", 8 | "False" 9 | ] 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InjectFix实现原理 2 | 3 | ​ 4 | 5 | # 简介 6 | 7 | 本文主要介绍InjectFix的相关内容,从手把手的一个例子来介绍如何使用InjectFix,一直到阅读源码来分析它的内部实现原理。 8 | 9 | InjectFix是腾讯开源的Unity C#热更新解决方案。 10 | 11 | 项目主页: 12 | 13 | [https://github.com/Tencent/InjectFix](https://github.com/Tencent/InjectFix) 14 | 15 | 原理介绍(原作者): 16 | 17 | [https://www.oschina.net/news/109803/injectfix-opensource](https://www.oschina.net/news/109803/injectfix-opensource) 18 | 19 | 20 | ​ 21 | 22 | # 目录 23 | 24 | 1. [InjectFix实现原理(一) - 如何使用](Docs/1_Introduction.md) 25 | 2. [InjectFix实现原理(二) - 自动插桩](Docs/2_Inject.md) 26 | 3. [InjectFix实现原理(三) - 生成补丁](Docs/3_Fix.md) 27 | 4. [InjectFix实现原理(四) -IL虚拟机](Docs/4_VirtualMachine.md) 28 | 4. [InjectFix实现原理(五) -加载补丁](Docs/5_LoadPatch.md) 29 | 30 | --------------------------------------------------------------------------------