├── icon.png ├── ILRepack.exe ├── _input ├── FNA.dll ├── Terraria.FNA.exe ├── Terraria.XNA.exe └── Newtonsoft.Json.dll ├── .gitmodules ├── Stripper ├── App.config ├── Properties │ └── AssemblyInfo.cs ├── Program.cs └── Stripper.csproj ├── TerrariaHookGen ├── App.config ├── Properties │ └── AssemblyInfo.cs ├── TerrariaHookGen.csproj └── Program.cs ├── TerrariaHooks ├── TerrariaHooksMod.cs ├── Properties │ └── AssemblyInfo.cs ├── TerrariaHooks.csproj └── TerrariaHooksManager.cs ├── description.txt ├── LICENSE ├── .gitattributes ├── README.md ├── TerrariaHooks.sln └── .gitignore /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/TerrariaHooks/HEAD/icon.png -------------------------------------------------------------------------------- /ILRepack.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/TerrariaHooks/HEAD/ILRepack.exe -------------------------------------------------------------------------------- /_input/FNA.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/TerrariaHooks/HEAD/_input/FNA.dll -------------------------------------------------------------------------------- /_input/Terraria.FNA.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/TerrariaHooks/HEAD/_input/Terraria.FNA.exe -------------------------------------------------------------------------------- /_input/Terraria.XNA.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/TerrariaHooks/HEAD/_input/Terraria.XNA.exe -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "MonoMod"] 2 | path = MonoMod 3 | url = https://github.com/0x0ade/MonoMod.git 4 | -------------------------------------------------------------------------------- /_input/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/TerrariaHooks/HEAD/_input/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /Stripper/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TerrariaHookGen/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TerrariaHooks/TerrariaHooksMod.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using Mono.Cecil.Cil; 3 | using MonoMod.RuntimeDetour; 4 | using MonoMod.RuntimeDetour.HookGen; 5 | using MonoMod.Utils; 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Reflection; 12 | using Terraria; 13 | using Terraria.ModLoader; 14 | 15 | namespace TerrariaHooks { 16 | class TerrariaHooksMod : Mod { 17 | 18 | public TerrariaHooksMod() { 19 | TerrariaHooksManager.Init(); 20 | 21 | Properties = new ModProperties() { 22 | }; 23 | } 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /description.txt: -------------------------------------------------------------------------------- 1 | [i:109] TerrariaHooks 2 | The power of HookGen in the palm of your hands. 3 | 4 | [i:109] Note 5 | This mod only "works" together with other mods that need it. 6 | Please visit the homepage to the up-to-date feature list and more information. 7 | 8 | [i:109] Features 9 | 10 | [i:75] "Hook" any arbitrary method you want easily: 11 | On.Terraria.Player.CheckMana += (...) => {...} 12 | 13 | [i:75] Use "hooks" to detect when a method runs, change how it runs or even replace it. 14 | "Hooks" are automatically undone whenever your mod unloads. This is handled by TerrariaHooks for you. 15 | 16 | [i:75] Manipulate any method at runtime via IL... += (...) => {...} 17 | Special thanks to Chicken-Bones for the great discussions! 18 | 19 | [i:75] Use RuntimeDetour to quickly port your existing "method swapping" code. 20 | Note that this requires you to undo your detours on unload. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Everest Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Stripper/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Stripper")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Stripper")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("fb9ed388-b9e7-4786-a2b5-04790a34dd37")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TerrariaHooks/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TerrariaHooks")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TerrariaHooks")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("0ddc1049-13ab-463d-91a9-ef4e56bff5af")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TerrariaHookGen/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TerrariaHookGen")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TerrariaHookGen")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b2dba9ae-9a85-41d0-a868-29d60cfc6249")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.0.6.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Stripper/Program.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using Mono.Cecil.Cil; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace TerrariaHookGen { 11 | public static class Stripper { 12 | 13 | static void Main(string[] args) { 14 | string inputDir; 15 | if (args.Length != 1 || !Directory.Exists(inputDir = args[0])) { 16 | Console.Error.WriteLine("Usage: inputdir"); 17 | return; 18 | } 19 | 20 | foreach (string path in Directory.GetFiles(inputDir)) { 21 | Console.WriteLine($"Stripping: {path}"); 22 | Strip(path); 23 | } 24 | } 25 | 26 | public static void Strip(string path, ReaderParameters readerParams = null) { 27 | if (readerParams == null) { 28 | DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver(); 29 | asmResolver.AddSearchDirectory(Path.GetDirectoryName(path)); 30 | readerParams = new ReaderParameters() { 31 | AssemblyResolver = asmResolver 32 | }; 33 | } 34 | 35 | ModuleDefinition module = ModuleDefinition.ReadModule(path, readerParams); 36 | int changes = 0; 37 | 38 | if ((module.Resources?.Count ?? 0) != 0) { 39 | changes += module.Resources.Count; 40 | module.Resources.Clear(); 41 | } 42 | 43 | foreach (TypeDefinition type in module.Types) 44 | Strip(ref changes, type); 45 | 46 | if (changes != 0) { 47 | module.Write(path); 48 | } 49 | } 50 | 51 | static void Strip(ref int changes, TypeDefinition type) { 52 | foreach (MethodDefinition method in type.Methods) 53 | if (method.HasBody && method.Body.Instructions.Count != 0) { 54 | changes++; 55 | method.Body = new MethodBody(method); 56 | } 57 | 58 | foreach (TypeDefinition nested in type.NestedTypes) 59 | Strip(ref changes, nested); 60 | } 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Stripper/Stripper.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FB9ED388-B9E7-4786-A2B5-04790A34DD37} 8 | Exe 9 | TerrariaHookGen 10 | Stripper 11 | v4.5.2 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 0.9.6 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /TerrariaHooks/TerrariaHooks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {0DDC1049-13AB-463D-91A9-EF4E56BFF5AF} 7 | Library 8 | TerrariaHooks 9 | TerrariaHooks 10 | net452 11 | 7.3 12 | false 13 | false 14 | false 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\_input\Newtonsoft.Json.dll 36 | 37 | 38 | 39 | ..\_input\Terraria.XNA.exe 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 0.9.6 49 | 50 | 51 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OBSOLETE 2 | ### tModLoader 0.11+ includes its own version of TerrariaHooks / MonoMod. 3 | Please tell any mod developers which still use this old version to update their mods. 4 | 5 | # TerrariaHooks 6 | 7 | ### License: MIT 8 | 9 | ---- 10 | 11 | HookGen "recipe" for Terraria, resulting in a TML mod that other mods can depend on. 12 | 13 | Built with [MonoMod](https://github.com/0x0ade/MonoMod). 14 | 15 | ## Features 16 | 17 | ### For Players 18 | 19 | This mod is a helper for other mods. Just install it if a mod needs it, it won't change Terraria on its own. 20 | 21 | ### For Modders 22 | 23 | - "Hook" any arbitrary method you want easily: 24 | ```cs 25 | On.Terraria.Player.CheckMana += (orig, player, amount, pay, blockQuickMana) => { 26 | // We can either make this act as a method replacement 27 | // or just call our code around the original code. 28 | 29 | // Let's double the mana cost of everything. 30 | // We can pass on custom values. 31 | bool spendable = orig(player, amount * 2, pay, blockQuickMana); 32 | 33 | // ... but give back half of it if it was spent. 34 | if (spendable && pay) { 35 | player.statMana += amount / 2; 36 | } 37 | 38 | // We can return custom values. 39 | return spendable; 40 | } 41 | ``` 42 | 43 | For an extended example, take a look at [this gist](https://gist.github.com/0x0ade/1d1013d6ae1ff450aa76f252b0f3b62c). 44 | 45 | - Use "hooks" to detect when a method runs, change how it runs or even replace it. 46 | "Hooks" are automatically undone whenever your mod unloads. This is handled by TerrariaHooks for you. 47 | 48 | - Manipulate any method at runtime via IL... += (...) => {...} using Cecil and many helpers. 49 | Special thanks to Chicken-Bones for the great ideas and feedback along the way! 50 | 51 | - Use RuntimeDetour to quickly port your existing "method swapping" code. 52 | Note that this requires you to undo your detours on unload. 53 | 54 | If you need more info, read the [MonoMod RuntimeDetour README](https://github.com/0x0ade/MonoMod/blob/master/README-RuntimeDetour.md). 55 | 56 | ## Instructions 57 | 58 | If you want to use TerrariaHooks in your mod: 59 | 60 | - Download `TerrariaHooks.dll` from the [latest release](https://github.com/0x0ade/TerrariaHooks/releases). 61 | - Put it into your mod `lib` folder. 62 | - Add it as a reference in Visual Studio. 63 | - In your `build.txt`, add `modReferences = TerrariaHooks` 64 | - Add your hooks in your mod `Load` method. 65 | - TerrariaHooks will automatically undo your hook when your mod unloads. 66 | 67 | ## Building TerrariaHooks yourself 68 | 69 | - `git clone --recursive https://github.com/0x0ade/TerrariaHookGen.git` 70 | - Alternatively, pull in the MonoMod submodule manually 71 | - Update the files in `_input` 72 | - The files redistributed in this repo are stripped. 73 | - The files in that directory will be stripped automatically on each run. 74 | - Build the solution. 75 | - Run `./TerrariaHookGen/bin/Debug/TerrariaHookGen.exe _input .` 76 | - In the Terraria directory, run `./tModLoaderServer.exe -build 'path/to/TerrariaHooks' -eac` 77 | 78 | This repository overcomplicates the entire procedure. It boils down to: 79 | ```bash 80 | ./MonoMod.RuntimeDetour.HookGen.exe --private Terraria.exe TerrariaHooksPre.dll 81 | ./ILRepack.exe /out:TerrariaHooks.dll TerrariaHooksPre.dll MonoMod.*.dll MonoMod.exe 82 | ``` 83 | 84 | When running the above two lines in the Terraria directory (with all dependencies present), it generates `TerrariaHooks.dll` for your `Terraria.exe`. 85 | 86 | The sole purpose of this repository is to automate the process entirely, and to allow publishing `TerrariaHooks.dll` as a TML mod. 87 | -------------------------------------------------------------------------------- /TerrariaHookGen/TerrariaHookGen.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {B2DBA9AE-9A85-41D0-A868-29D60CFC6249} 8 | Exe 9 | TerrariaHookGen 10 | TerrariaHookGen 11 | v4.5.2 12 | 512 13 | true 14 | true 15 | 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\ILRepack.exe 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {032b42a5-c409-4b69-b8e6-ec468eedfec1} 53 | MonoMod.RuntimeDetour.HookGen 54 | 55 | 56 | {d0c584c0-81d7-486e-b70e-d7f9256e0909} 57 | MonoMod.RuntimeDetour 58 | 59 | 60 | {1839cfe2-3db0-45a8-b03d-9aa797479a3a} 61 | MonoMod.Utils 62 | 63 | 64 | {8a17c958-5c33-4035-af76-f94a3aa2dc4f} 65 | MonoMod 66 | 67 | 68 | {fb9ed388-b9e7-4786-a2b5-04790a34dd37} 69 | Stripper 70 | 71 | 72 | {0ddc1049-13ab-463d-91a9-ef4e56bff5af} 73 | TerrariaHooks 74 | 75 | 76 | 77 | 78 | 0.9.6 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /TerrariaHooks.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerrariaHookGen", "TerrariaHookGen\TerrariaHookGen.csproj", "{B2DBA9AE-9A85-41D0-A868-29D60CFC6249}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stripper", "Stripper\Stripper.csproj", "{FB9ED388-B9E7-4786-A2B5-04790A34DD37}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoMod.Utils", "MonoMod\MonoMod.Utils\MonoMod.Utils.csproj", "{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoMod.RuntimeDetour", "MonoMod\MonoMod.RuntimeDetour\MonoMod.RuntimeDetour.csproj", "{D0C584C0-81D7-486E-B70E-D7F9256E0909}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoMod.RuntimeDetour.HookGen", "MonoMod\MonoMod.RuntimeDetour.HookGen\MonoMod.RuntimeDetour.HookGen.csproj", "{032B42A5-C409-4B69-B8E6-EC468EEDFEC1}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoMod", "MonoMod\MonoMod\MonoMod.csproj", "{8A17C958-5C33-4035-AF76-F94A3AA2DC4F}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerrariaHooks", "TerrariaHooks\TerrariaHooks.csproj", "{0DDC1049-13AB-463D-91A9-EF4E56BFF5AF}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {B2DBA9AE-9A85-41D0-A868-29D60CFC6249}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {B2DBA9AE-9A85-41D0-A868-29D60CFC6249}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {B2DBA9AE-9A85-41D0-A868-29D60CFC6249}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {B2DBA9AE-9A85-41D0-A868-29D60CFC6249}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {FB9ED388-B9E7-4786-A2B5-04790A34DD37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {FB9ED388-B9E7-4786-A2B5-04790A34DD37}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {FB9ED388-B9E7-4786-A2B5-04790A34DD37}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {FB9ED388-B9E7-4786-A2B5-04790A34DD37}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Any CPU.ActiveCfg = DebugLegacy|Any CPU 35 | {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Any CPU.Build.0 = DebugLegacy|Any CPU 36 | {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Any CPU.ActiveCfg = ReleaseLegacy|Any CPU 37 | {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Any CPU.Build.0 = ReleaseLegacy|Any CPU 38 | {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Any CPU.ActiveCfg = DebugLegacy|Any CPU 39 | {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Any CPU.Build.0 = DebugLegacy|Any CPU 40 | {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Any CPU.ActiveCfg = ReleaseLegacy|Any CPU 41 | {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Any CPU.Build.0 = ReleaseLegacy|Any CPU 42 | {032B42A5-C409-4B69-B8E6-EC468EEDFEC1}.Debug|Any CPU.ActiveCfg = DebugLegacy|Any CPU 43 | {032B42A5-C409-4B69-B8E6-EC468EEDFEC1}.Debug|Any CPU.Build.0 = DebugLegacy|Any CPU 44 | {032B42A5-C409-4B69-B8E6-EC468EEDFEC1}.Release|Any CPU.ActiveCfg = ReleaseLegacy|Any CPU 45 | {032B42A5-C409-4B69-B8E6-EC468EEDFEC1}.Release|Any CPU.Build.0 = ReleaseLegacy|Any CPU 46 | {8A17C958-5C33-4035-AF76-F94A3AA2DC4F}.Debug|Any CPU.ActiveCfg = DebugLegacy|Any CPU 47 | {8A17C958-5C33-4035-AF76-F94A3AA2DC4F}.Debug|Any CPU.Build.0 = DebugLegacy|Any CPU 48 | {8A17C958-5C33-4035-AF76-F94A3AA2DC4F}.Release|Any CPU.ActiveCfg = ReleaseLegacy|Any CPU 49 | {8A17C958-5C33-4035-AF76-F94A3AA2DC4F}.Release|Any CPU.Build.0 = ReleaseLegacy|Any CPU 50 | {0DDC1049-13AB-463D-91A9-EF4E56BFF5AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {0DDC1049-13AB-463D-91A9-EF4E56BFF5AF}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {0DDC1049-13AB-463D-91A9-EF4E56BFF5AF}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {0DDC1049-13AB-463D-91A9-EF4E56BFF5AF}.Release|Any CPU.Build.0 = Release|Any CPU 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | GlobalSection(ExtensibilityGlobals) = postSolution 59 | SolutionGuid = {FDBC892E-FD69-4D1C-93CD-3B994A329B7D} 60 | EndGlobalSection 61 | EndGlobal 62 | -------------------------------------------------------------------------------- /TerrariaHookGen/Program.cs: -------------------------------------------------------------------------------- 1 | using ILRepacking; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Cil; 4 | using MonoMod; 5 | using MonoMod.RuntimeDetour.HookGen; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Reflection; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace TerrariaHookGen { 15 | class Program { 16 | 17 | static void Main(string[] args) { 18 | // Required for the relative extra paths to work properly. 19 | if (!File.Exists("MonoMod.RuntimeDetour.dll")) 20 | Environment.CurrentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 21 | 22 | string inputDir, outputDir; 23 | if (args.Length != 2 || 24 | !Directory.Exists(inputDir = args[0]) || 25 | !Directory.Exists(outputDir = args[1])) { 26 | Console.Error.WriteLine("Usage: inputdir outputdir"); 27 | return; 28 | } 29 | 30 | // Check that the files exist. 31 | if (!VerifyFile(out string inputXNA, inputDir, "Terraria.XNA.exe")) 32 | return; 33 | if (!VerifyFile(out string inputFNA, inputDir, "Terraria.FNA.exe")) 34 | return; 35 | 36 | // Strip or copy. 37 | foreach (string path in Directory.GetFiles(inputDir)) { 38 | if (!path.EndsWith(".exe") && !path.EndsWith(".dll")) { 39 | Console.WriteLine($"Copying: {path}"); 40 | File.Copy(path, Path.Combine(outputDir, Path.GetFileName(path))); 41 | continue; 42 | } 43 | 44 | Console.WriteLine($"Stripping: {path}"); 45 | Stripper.Strip(path); 46 | } 47 | 48 | // Generate hooks. 49 | string hooksXNA = Path.Combine(outputDir, "Windows.Pre.dll"); 50 | string hooksFNA = Path.Combine(outputDir, "Mono.Pre.dll"); 51 | GenHooks(inputXNA, hooksXNA); 52 | GenHooks(inputFNA, hooksFNA); 53 | 54 | // Merge generated .dlls and MonoMod into one .dll per environment. 55 | string[] extrasMod = { 56 | "TerrariaHooks.dll", 57 | "MonoMod.exe", 58 | "MonoMod.RuntimeDetour.dll", 59 | "MonoMod.Utils.dll" 60 | }; 61 | Repack(hooksXNA, extrasMod, Path.Combine(outputDir, "Windows.dll"), "TerrariaHooks.dll"); 62 | File.Delete(hooksXNA); 63 | Repack(hooksFNA, extrasMod, Path.Combine(outputDir, "Mono.dll"), "TerrariaHooks.dll"); 64 | File.Delete(hooksFNA); 65 | } 66 | 67 | static bool VerifyFile(out string path, params string[] paths) { 68 | path = Path.Combine(paths); 69 | if (!File.Exists(path) && !Directory.Exists(path)) { 70 | Console.Error.WriteLine($"Missing: {path}"); 71 | return false; 72 | } 73 | return true; 74 | } 75 | 76 | static void GenHooks(string input, string output) { 77 | Console.WriteLine($"Hooking: {input} -> {output}"); 78 | 79 | using (MonoModder mm = new MonoModder() { 80 | InputPath = input, 81 | OutputPath = output, 82 | ReadingMode = ReadingMode.Deferred, 83 | 84 | MissingDependencyThrow = false, 85 | }) { 86 | mm.Read(); 87 | mm.MapDependencies(); 88 | 89 | if (File.Exists(output)) 90 | File.Delete(output); 91 | 92 | HookGenerator gen = new HookGenerator(mm, Path.GetFileName(output)) { 93 | HookPrivate = true, 94 | }; 95 | gen.Generate(); 96 | gen.OutputModule.Write(output); 97 | } 98 | } 99 | 100 | static void Repack(string input, string[] extras, string output, string name = null) { 101 | Console.Error.WriteLine($"Repacking: {input} -> {output}"); 102 | if (name == null) 103 | name = Path.GetFileName(output); 104 | 105 | string outputTmp = Path.Combine(Path.GetDirectoryName(output), name); 106 | if (File.Exists(outputTmp)) 107 | File.Delete(outputTmp); 108 | 109 | List args = new List(); 110 | args.Add($"/out:{outputTmp}"); 111 | args.Add(input); 112 | foreach (string dep in extras) 113 | if (!string.IsNullOrWhiteSpace(dep)) 114 | args.Add(dep.Trim()); 115 | 116 | RepackOptions options = new RepackOptions(args); 117 | ILRepack repack = new ILRepack(options); 118 | repack.Repack(); 119 | 120 | if (output != outputTmp) { 121 | if (File.Exists(output)) 122 | File.Delete(output); 123 | File.Move(outputTmp, output); 124 | } 125 | } 126 | 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __input 2 | Mono.dll 3 | Mono.pdb 4 | Windows.dll 5 | Windows.pdb 6 | tModLoaderServer_TerrariaHooks.exe 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.userosscache 15 | *.sln.docstates 16 | 17 | # User-specific files (MonoDevelop/Xamarin Studio) 18 | *.userprefs 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | bld/ 28 | [Bb]in/ 29 | [Oo]bj/ 30 | [Ll]og/ 31 | 32 | # Visual Studio 2015 cache/options directory 33 | .vs/ 34 | # Uncomment if you have tasks that create the project's static files in wwwroot 35 | #wwwroot/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | # DNX 51 | project.lock.json 52 | project.fragment.lock.json 53 | artifacts/ 54 | 55 | *_i.c 56 | *_p.c 57 | *_i.h 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.svclog 78 | *.scc 79 | 80 | # Chutzpah Test files 81 | _Chutzpah* 82 | 83 | # Visual C++ cache files 84 | ipch/ 85 | *.aps 86 | *.ncb 87 | *.opendb 88 | *.opensdf 89 | *.sdf 90 | *.cachefile 91 | *.VC.db 92 | *.VC.VC.opendb 93 | 94 | # Visual Studio profiler 95 | *.psess 96 | *.vsp 97 | *.vspx 98 | *.sap 99 | 100 | # TFS 2012 Local Workspace 101 | $tf/ 102 | 103 | # Guidance Automation Toolkit 104 | *.gpState 105 | 106 | # ReSharper is a .NET coding add-in 107 | _ReSharper*/ 108 | *.[Rr]e[Ss]harper 109 | *.DotSettings.user 110 | 111 | # JustCode is a .NET coding add-in 112 | .JustCode 113 | 114 | # TeamCity is a build add-in 115 | _TeamCity* 116 | 117 | # DotCover is a Code Coverage Tool 118 | *.dotCover 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | #*.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignoreable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | node_modules/ 203 | orleans.codegen.cs 204 | 205 | # Since there are multiple workflows, uncomment next line to ignore bower_components 206 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 207 | #bower_components/ 208 | 209 | # RIA/Silverlight projects 210 | Generated_Code/ 211 | 212 | # Backup & report files from converting an old project file 213 | # to a newer Visual Studio version. Backup files are not needed, 214 | # because we have git ;-) 215 | _UpgradeReport_Files/ 216 | Backup*/ 217 | UpgradeLog*.XML 218 | UpgradeLog*.htm 219 | 220 | # SQL Server files 221 | *.mdf 222 | *.ldf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | 238 | # Visual Studio 6 build log 239 | *.plg 240 | 241 | # Visual Studio 6 workspace options file 242 | *.opt 243 | 244 | # Visual Studio LightSwitch build output 245 | **/*.HTMLClient/GeneratedArtifacts 246 | **/*.DesktopClient/GeneratedArtifacts 247 | **/*.DesktopClient/ModelManifest.xml 248 | **/*.Server/GeneratedArtifacts 249 | **/*.Server/ModelManifest.xml 250 | _Pvt_Extensions 251 | 252 | # Paket dependency manager 253 | .paket/paket.exe 254 | paket-files/ 255 | 256 | # FAKE - F# Make 257 | .fake/ 258 | 259 | # JetBrains Rider 260 | .idea/ 261 | *.sln.iml 262 | 263 | # CodeRush 264 | .cr/ 265 | 266 | # Python Tools for Visual Studio (PTVS) 267 | __pycache__/ 268 | *.pyc -------------------------------------------------------------------------------- /TerrariaHooks/TerrariaHooksManager.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using Mono.Cecil.Cil; 3 | using MonoMod.RuntimeDetour; 4 | using MonoMod.RuntimeDetour.HookGen; 5 | using MonoMod.Utils; 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Diagnostics; 10 | using System.IO; 11 | using System.Linq; 12 | using System.Reflection; 13 | using System.Runtime.ExceptionServices; 14 | using System.Text; 15 | using Terraria; 16 | using Terraria.ModLoader; 17 | using TerrariaHooks; 18 | 19 | namespace TerrariaHooks { 20 | static partial class TerrariaHooksManager { 21 | 22 | static bool Initialized = false; 23 | 24 | static DetourModManager Manager; 25 | 26 | public static void Init() { 27 | if (Initialized) 28 | return; 29 | Initialized = true; 30 | 31 | Manager = new DetourModManager(); 32 | Manager.Ignored.Add(Assembly.GetExecutingAssembly()); 33 | 34 | // Load the cecil module generator. 35 | // Adding this more than once shouldn't hurt. 36 | HookEndpointManager.OnGenerateCecilModule += GenerateCecilModule; 37 | 38 | // Some mods might forget to undo their hooks. 39 | HookOnUnloadContent = new Hook( 40 | typeof(Mod).GetMethod("UnloadContent", BindingFlags.NonPublic | BindingFlags.Instance), 41 | typeof(TerrariaHooksManager).GetMethod("OnUnloadContent", BindingFlags.NonPublic | BindingFlags.Static) 42 | ); 43 | 44 | // All of our own hooks need to be undone last. 45 | MethodBase m_Unload = typeof(ModLoader).GetMethod("Unload", BindingFlags.NonPublic | BindingFlags.Static); 46 | if (m_Unload != null) { 47 | HookOnUnloadAll = new Hook( 48 | m_Unload, 49 | typeof(TerrariaHooksManager).GetMethod("OnUnloadAll", BindingFlags.NonPublic | BindingFlags.Static) 50 | ); 51 | } else { 52 | /* The unofficial tML x64 form is a hot mess of tML 0.10 and 0.11 pre-beta. 53 | * As such, mods are only unloaded when they're reloaded. 54 | * Luckily, ModContnet.Unload runs right after unloading all mods. 55 | * Even more luckily, it pretty much shares the same signature as the old Unload method. 56 | */ 57 | MethodBase m_UnloadContent = 58 | typeof(Mod).Assembly.GetType("Terraria.ModLoader.ModContent") 59 | ?.GetMethod("Unload", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); 60 | if (m_UnloadContent != null) { 61 | HookOnUnloadAll = new Hook( 62 | m_UnloadContent, 63 | typeof(TerrariaHooksManager).GetMethod("OnUnloadAll", BindingFlags.NonPublic | BindingFlags.Static) 64 | ); 65 | } else { 66 | throw new Exception("Incompatible tML version: Can't find unload hook point"); 67 | } 68 | } 69 | 70 | // Try to hook the logger to avoid logging "silent" exceptions thrown by TerrariaHooks. 71 | MethodBase m_LogSilentException = 72 | typeof(Mod).Assembly.GetType("Terraria.ModLoader.ModCompile+<>c") 73 | ?.GetMethod("b__15_0", BindingFlags.NonPublic | BindingFlags.Instance); 74 | if (m_LogSilentException != null) { 75 | HookOnLogSilentException = new Hook( 76 | m_LogSilentException, 77 | typeof(TerrariaHooksManager).GetMethod("OnLogSilentException", BindingFlags.NonPublic | BindingFlags.Static) 78 | ); 79 | } 80 | } 81 | 82 | internal static void Dispose() { 83 | if (!Initialized) 84 | return; 85 | Initialized = false; 86 | 87 | Manager.Dispose(); 88 | 89 | HookEndpointManager.OnGenerateCecilModule -= GenerateCecilModule; 90 | 91 | HookOnUnloadContent.Dispose(); 92 | HookOnUnloadAll.Dispose(); 93 | HookOnLogSilentException?.Dispose(); 94 | } 95 | 96 | static Hook HookOnUnloadContent; 97 | static void OnUnloadContent(Action orig, Mod mod) { 98 | orig(mod); 99 | Manager.Unload(mod.Code); 100 | } 101 | 102 | static Hook HookOnUnloadAll; 103 | static void OnUnloadAll(Action orig) { 104 | orig(); 105 | 106 | // Dispose the context. 107 | Dispose(); 108 | } 109 | 110 | static Hook HookOnLogSilentException; 111 | static void OnLogSilentException(Action orig, object self, object sender, FirstChanceExceptionEventArgs exceptionArgs) { 112 | Exception e = exceptionArgs.Exception; 113 | if (e.TargetSite.Name == "CreateDelegateNoSecurityCheck" || 114 | e.TargetSite.Name == "_GetMethodBody" || 115 | e.TargetSite.Name == "GetMethodBody") 116 | return; 117 | 118 | orig(self, sender, exceptionArgs); 119 | } 120 | 121 | internal static ModuleDefinition GenerateCecilModule(AssemblyName name) { 122 | AssemblyName UnwrapName(AssemblyName other) { 123 | int underscore = other.Name.LastIndexOf('_'); 124 | if (underscore == -1) 125 | return other; 126 | 127 | other.Name = other.Name.Substring(0, underscore); 128 | return other; 129 | } 130 | 131 | // Terraria ships with some dependencies as embedded resources. 132 | string resourceName = name.Name + ".dll"; 133 | resourceName = Array.Find(typeof(Program).Assembly.GetManifestResourceNames(), element => element.EndsWith(resourceName)); 134 | if (resourceName != null) { 135 | using (Stream stream = typeof(Program).Assembly.GetManifestResourceStream(resourceName)) 136 | // Read immediately, as the stream isn't open forever. 137 | return ModuleDefinition.ReadModule(stream, new ReaderParameters(ReadingMode.Immediate)); 138 | } 139 | 140 | // Mod .dlls exist in the mod's .tmod containers. 141 | string nameStr = name.ToString(); 142 | foreach (Mod mod in ModLoader.LoadedMods) { 143 | if (mod.Code == null || mod.File == null) 144 | continue; 145 | 146 | // Check if it's the main assembly. 147 | if (mod.Code.GetName().ToString() == nameStr) { 148 | // Let's unwrap the name and cache it for all DMDs as well. 149 | // tModLoader changes the assembly name to allow mod updates, which breaks Assembly.Load 150 | ReflectionHelper.AssemblyCache[UnwrapName(mod.Code.GetName()).ToString()] = mod.Code; 151 | 152 | using (MemoryStream stream = new MemoryStream(mod.File.GetMainAssembly())) 153 | // Read immediately, as the stream isn't open forever. 154 | return ModuleDefinition.ReadModule(stream, new ReaderParameters(ReadingMode.Immediate)); 155 | } 156 | 157 | // Check if the assembly is possibly included in the .tmod 158 | if (!mod.Code.GetReferencedAssemblies().Any(other => UnwrapName(other).ToString() == nameStr)) 159 | continue; 160 | 161 | // Try to load lib/Name.dll 162 | byte[] data; 163 | if ((data = mod.File.GetFile($"lib/{name.Name}.dll")) != null) 164 | using (MemoryStream stream = new MemoryStream(data)) 165 | // Read immediately, as the stream isn't open forever. 166 | return ModuleDefinition.ReadModule(stream, new ReaderParameters(ReadingMode.Immediate)); 167 | } 168 | 169 | return null; 170 | } 171 | 172 | } 173 | } 174 | --------------------------------------------------------------------------------