├── ExamplePlugins ├── Advanced │ └── CompleteWeaponDemo │ │ ├── Assets │ │ ├── ItemGraphic.png │ │ └── WeaponProjectileFrames.png │ │ ├── Properties │ │ └── AssemblyInfo.cs │ │ └── CompleteWeaponDemo.csproj └── Simple │ ├── PrecompiledPluginDemo │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── PrecompiledPluginDemo.csproj │ └── SimpleDefenseBoostPlugin.cs │ ├── SinglePatchDemo.cs │ └── PersistentSavedataDemo.cs ├── TTPlugins ├── NamespaceDoc.cs ├── Forms │ ├── NamespaceDoc.cs │ ├── PluginReport.Designer.cs │ ├── PluginReport.resx │ └── PluginReport.cs ├── HarmonyPlugins │ ├── NamespaceDoc.cs │ ├── HPatchLocation.cs │ ├── HPluginConfiguration.cs │ ├── HSupervisedPlugin.cs │ ├── HPluginApplicatorConfiguration.cs │ ├── HPluginCompilationResult.cs │ ├── HPluginIdentity.cs │ ├── HPluginCompilationConfiguration.cs │ ├── HPatchOperation.cs │ ├── HPluginApplicatorResult.cs │ ├── HFrameworkPatches.cs │ ├── HPlugin.cs │ └── HPluginAssemblyCompiler.cs ├── Management │ ├── NamespaceDoc.cs │ ├── SecurityCompliance │ │ ├── NamespaceDoc.cs │ │ ├── LevelTestResult.cs │ │ ├── MultipleTestsResults.cs │ │ ├── LevelTestConfiguration.cs │ │ ├── PluginTestConfiguration.cs │ │ └── PluginTestResult.cs │ ├── PluginFileType.cs │ ├── TerrariaEnvironment.cs │ ├── PluginFile.cs │ └── IO.cs ├── Properties │ └── AssemblyInfo.cs ├── Extensions.cs └── TTPlugins.csproj ├── README.md ├── .gitattributes ├── Resources └── PluginTemplate.cs ├── TTPlugins.sln ├── .gitignore └── LICENSE /ExamplePlugins/Advanced/CompleteWeaponDemo/Assets/ItemGraphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiberiumFusion/TTPlugins/HEAD/ExamplePlugins/Advanced/CompleteWeaponDemo/Assets/ItemGraphic.png -------------------------------------------------------------------------------- /ExamplePlugins/Advanced/CompleteWeaponDemo/Assets/WeaponProjectileFrames.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiberiumFusion/TTPlugins/HEAD/ExamplePlugins/Advanced/CompleteWeaponDemo/Assets/WeaponProjectileFrames.png -------------------------------------------------------------------------------- /TTPlugins/NamespaceDoc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace com.tiberiumfusion.ttplugins 4 | { 5 | /// 6 | /// Root TTPlugins namespace. 7 | /// 8 | 9 | [System.Runtime.CompilerServices.CompilerGenerated] 10 | class NamespaceDoc 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TTPlugins/Forms/NamespaceDoc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace com.tiberiumfusion.ttplugins.Forms 4 | { 5 | /// 6 | /// Forms for presenting information to the user when TTPlugins is enabled during Terraria gameplay. 7 | /// 8 | 9 | [System.Runtime.CompilerServices.CompilerGenerated] 10 | class NamespaceDoc 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/NamespaceDoc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 4 | { 5 | /// 6 | /// The online runtime framework for plugins, including administration and the plugin base type and helpers available to plugin authors. 7 | /// 8 | 9 | [System.Runtime.CompilerServices.CompilerGenerated] 10 | class NamespaceDoc 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TTPlugins/Management/NamespaceDoc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace com.tiberiumfusion.ttplugins.Management 4 | { 5 | /// 6 | /// The offline file management framework for plugins. Used primarily by Terraria Tweaker 2 and TTApplicator, and of little value to plugin authors. 7 | /// 8 | 9 | [System.Runtime.CompilerServices.CompilerGenerated] 10 | class NamespaceDoc 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TTPlugins/Management/SecurityCompliance/NamespaceDoc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace com.tiberiumfusion.ttplugins.Management.SecurityCompliance 4 | { 5 | /// 6 | /// Subset of the offline plugin management framework which provides security compliance testing of plugins. 7 | /// 8 | 9 | [System.Runtime.CompilerServices.CompilerGenerated] 10 | class NamespaceDoc 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TTPlugins/Management/PluginFileType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace com.tiberiumfusion.ttplugins.Management 8 | { 9 | /// 10 | /// The kind of file type that a plugin is. 11 | /// 12 | public enum PluginFileType 13 | { 14 | /// 15 | /// For dummy values. 16 | /// 17 | None, 18 | 19 | /// 20 | /// A raw C# source file containing type(s) derived from HPlugin, which will be compiled on-demand when needed. 21 | /// 22 | CSSourceFile, 23 | 24 | /// 25 | /// An already compiled .NET assembly containing types derived from HPlugin. 26 | /// 27 | CompiledAssemblyFile 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HPatchLocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 8 | { 9 | /// 10 | /// The location at which to apply a stub patch method. 11 | /// 12 | public enum HPatchLocation 13 | { 14 | /// 15 | /// Indicates that a stub method should be dynamically appended to a target method as a postfix method. 16 | /// 17 | Postfix = 0, 18 | 19 | /// 20 | /// Indicates that a stub method should be dynamically prepended to a target method as a prefix method. 21 | /// 22 | Prefix = 1, 23 | 24 | /// 25 | /// Indicates that a stub method should be interpreted as a Harmony transpiler patch. 26 | /// 27 | Transpiler = 2 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TTPlugins/Management/TerrariaEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace com.tiberiumfusion.ttplugins.Management 8 | { 9 | /// 10 | /// Specifies how Terraria related to the assembly context in which TTPlugins is running. 11 | /// 12 | public enum TerrariaEnvironment 13 | { 14 | /// 15 | /// For situations where online vs offline has no difference, or when the status of the Terraria reference assemblies is indeterminate. 16 | /// 17 | Unspecified, 18 | 19 | /// 20 | /// Indicates that the assembly load context in which TTPlugins is running is not a running Terraria process. 21 | /// 22 | Offline, 23 | 24 | /// 25 | /// Indicates that the assembly load context in which TTPlugins is running is a running Terraria process.TTPlugins. 26 | /// 27 | Online, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TTPlugins/Management/SecurityCompliance/LevelTestResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace com.tiberiumfusion.ttplugins.Management.SecurityCompliance 8 | { 9 | /// 10 | /// Holder of the test results of a security compliance test for a single level. 11 | /// 12 | public class LevelTestResult 13 | { 14 | /// 15 | /// Whether or not the assembly passed the test. 16 | /// 17 | public bool Passed { get; private set; } 18 | 19 | /// 20 | /// A list of messages from the test, such as details on specific security violations. 21 | /// 22 | public List Messages { get; private set; } = new List(); 23 | 24 | /// 25 | /// Creates a new SecurityComplianceTestResult object with the provided data. 26 | /// 27 | /// The value to assign to the Passed property. 28 | /// The value to assign to the Messages property. 29 | public LevelTestResult(bool passed, List messages) 30 | { 31 | Passed = passed; 32 | Messages = messages; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TTPlugins/Management/SecurityCompliance/MultipleTestsResults.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace com.tiberiumfusion.ttplugins.Management.SecurityCompliance 8 | { 9 | /// 10 | /// Holder of the test results for multiple plugin files. 11 | /// 12 | public class MultipleTestsResults 13 | { 14 | /// 15 | /// Whether or not the any of the PluginFile(s) being tested was a C# source file and failed to compile. 16 | /// 17 | public bool AnyCompileFailure = false; 18 | 19 | /// 20 | /// List of per-PluginFile test results. Only tested plugins will be present in this dictionary. plugins will not have an entry. 21 | /// 22 | public Dictionary IndividualResults = new Dictionary(); 23 | 24 | /// 25 | /// A list of s which were skipped. 26 | /// 27 | /// 28 | /// For security purposes, plugins are skipped when they are in source code form and TTPlugins is running inside Terraria. This is the only scenario in which plugins are skipped. 29 | /// 30 | public List Skipped = new List(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HPluginConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Xml.Linq; 7 | 8 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 9 | { 10 | /// 11 | /// Contains configuration data for an HPlugin, such as user preferences. 12 | /// 13 | public class HPluginConfiguration 14 | { 15 | /// 16 | /// An XML element which may contain user preferences or other persistent plugin savedata. 17 | /// This object will automatically be saved to disk when Terraria closes. 18 | /// Make changes to this object to update your plugin's savedata. 19 | /// This will be an empty element with blank defaults if your plugin claims it does not need persistent savedata (HPlugin.HasPersistentData = false). 20 | /// 21 | public XElement Savedata { get; set; } 22 | 23 | /// 24 | /// Creates a new HPluginConfiguration with a blank savedata element 25 | /// 26 | public HPluginConfiguration() 27 | { 28 | Savedata = new XElement("Savedata"); 29 | } 30 | 31 | /// 32 | /// Creates a new HPluginConfiguration with the provided savedata element 33 | /// 34 | /// The XML savedata element to use. 35 | public HPluginConfiguration(XElement savedata) 36 | { 37 | Savedata = savedata; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TTPlugins/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("TTPlugins")] 9 | [assembly: AssemblyDescription("User plugin system for Terraria Tweaker 2")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("TiberiumFusion")] 12 | [assembly: AssemblyProduct("TTPlugins")] 13 | [assembly: AssemblyCopyright("Copyright © TiberiumFusion 2020-2022")] 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("a01762b1-4e94-44e0-b4d6-e81949cc09d9")] 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.2.0.1")] 36 | [assembly: AssemblyFileVersion("1.2.0.1")] 37 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HSupervisedPlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Xml.Linq; 7 | 8 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 9 | { 10 | /// 11 | /// Container of both an HPlugin and data related to the HPlugin that is hidden from the HPlugin itself. 12 | /// 13 | internal class HSupervisedPlugin 14 | { 15 | /// 16 | /// The associated HPlugin. 17 | /// 18 | internal HPlugin Plugin { get; set; } 19 | 20 | /// 21 | /// Relative path of the source file used to compile the plugin, which is used as a unique identity for plugin configuration and savedata. 22 | /// 23 | internal string SourceFileRelativePath { get; set; } 24 | 25 | /// 26 | /// The latest iteration of the runtime configuration in its xml state. 27 | /// 28 | internal XDocument LatestConfigurationXML { get; set; } 29 | 30 | /// 31 | /// Creates a new HSupervisedPlugin wrapped around the provided HPlugin. 32 | /// 33 | /// The HPlugin to wrap. 34 | /// The relative path of the plugin's source file. 35 | internal HSupervisedPlugin(HPlugin plugin, string sourceFileRelativePath) 36 | { 37 | Plugin = plugin; 38 | SourceFileRelativePath = sourceFileRelativePath; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ExamplePlugins/Simple/PrecompiledPluginDemo/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("PrecompiledPluginDemo")] 9 | [assembly: AssemblyDescription("An example of how to precompile a plugin for TTPlugins.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("TiberiumFusion")] 12 | [assembly: AssemblyProduct("PrecompiledPluginDemo")] 13 | [assembly: AssemblyCopyright("Copyright © TiberiumFusion 2020")] 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("76a9c6da-5d66-425e-84f4-27c7d769793a")] 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 | -------------------------------------------------------------------------------- /ExamplePlugins/Advanced/CompleteWeaponDemo/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("CompleteWeaponDemo")] 9 | [assembly: AssemblyDescription("A complete demonstration of how to add a new weapon to Terraria.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("TiberiumFusion")] 12 | [assembly: AssemblyProduct("CompleteWeaponDemo")] 13 | [assembly: AssemblyCopyright("Copyright © TiberiumFusion 2020")] 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("2440c5d0-4d27-4b07-b9b6-6140ab9c27d0")] 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.1.0")] 36 | [assembly: AssemblyFileVersion("1.0.1.0")] 37 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HPluginApplicatorConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 9 | { 10 | /// 11 | /// Provides configuration data for the HPluginApplicator. 12 | /// Configuration data will be provided by TTApplicator. 13 | /// 14 | public sealed class HPluginApplicatorConfiguration 15 | { 16 | /// 17 | /// The executing Terraria assembly which will be patched using Harmony. 18 | /// 19 | public Assembly ExecutingTerrariaAssembly { get; set; } 20 | 21 | /// 22 | /// List of all loaded usercode assemblies that contain the HPlugins to be applied. 23 | /// 24 | public List PluginAssemblies { get; set; } = new List(); 25 | 26 | /// 27 | /// Dictionary that maps the full name of each HPlugin type to the relative path of its source file (which is its configuration and savedata identity). 28 | /// 29 | public Dictionary PluginTypesRelativePaths { get; set; } = new Dictionary(); 30 | 31 | /// 32 | /// Path to the root folder to use for temporary plugin files (i.e. temporary configuration and savedata copies). 33 | /// 34 | public string PluginTemporaryFilesRootDirectory { get; set; } 35 | 36 | /// 37 | /// The name of the file containing a plugin's runtime configuration file, within its temporary files directory. 38 | /// 39 | public string PluginRuntimeConfigFileName { get; set; } = "RuntimeConfiguration.xml"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HPluginCompilationResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom.Compiler; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 10 | { 11 | /// 12 | /// A bundle of data produced by HPluginAssemblyCompiler.Compile() 13 | /// 14 | public class HPluginCompilationResult 15 | { 16 | /// 17 | /// The compiled usercode assemblies. 18 | /// 19 | public List CompiledAssemblies { get; set; } = new List(); 20 | 21 | /// 22 | /// List of any compiler errors. 23 | /// 24 | public List CompileErrors { get; set; } = new List(); 25 | 26 | /// 27 | /// If true, a generic exception was thrown during compilation. 28 | /// 29 | public bool GenericCompilationFailure { get; set; } = false; 30 | 31 | /// 32 | /// Dictionary that maps the full name of each compiled HPlugin to the relative path of the source file used to compile it. 33 | /// 34 | public Dictionary CompiledTypesSourceFileRelativePaths { get; set; } = new Dictionary(); 35 | 36 | /// 37 | /// A list of the paths of all output files generated during the compile process. Will include both assembly DLLs and their corresponding PDBs. 38 | /// 39 | public List OutputFilesOnDisk { get; set; } = new List(); 40 | 41 | /// 42 | /// The root directory containing the files in OutputFilesOnDisk. 43 | /// 44 | public string OutputDirectory { get; set; } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ExamplePlugins/Simple/SinglePatchDemo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | using System.Xml.XPath; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using com.tiberiumfusion.ttplugins.HarmonyPlugins; 12 | 13 | namespace TTPluginsExamples.Simple 14 | { 15 | // This example plugin will increase player speed by 10. 16 | 17 | public class SinglePatchDemo : HPlugin 18 | { 19 | public override void Initialize() 20 | { 21 | // Establish this plugin's internal Identity within the TTPlugins environment. Every plugin should have a unique internal Identity. 22 | Identity.PluginName = "SinglePatchDemo"; 23 | Identity.PluginDescription = "Example plugin. Increases player speed by 10."; 24 | Identity.PluginAuthor = "TiberiumFusion"; 25 | Identity.PluginVersion = new Version("1.0.0.0"); 26 | 27 | HasPersistentSavedata = false; // This plugin doesn't use persistent savedata. 28 | } 29 | 30 | public override void ConfigurationLoaded(bool successfulConfigLoadFromDisk) 31 | { 32 | // This plugin does not use persistent savedata, so we will ignore our Configuration property. 33 | } 34 | 35 | public override void PrePatch() 36 | { 37 | // Define our single patch operation. 38 | CreateHPatchOperation("Terraria.Player", "UpdateEquips", "PrefixPatch", HPatchLocation.Prefix); 39 | // We will patch Terraria.Player.UpdateEquips into calling our custom PrefixPatch method before the original method executes. 40 | } 41 | 42 | 43 | // Our stub method to patch into Terraria 44 | public static void PrefixPatch(Terraria.Player __instance) 45 | { 46 | // The specially named __instance parameter is automatically filled in by Harmony to reference the Player instance that is calling this method. 47 | 48 | __instance.moveSpeed += 10.0f; // Add 10 speed to the Terraria.Player 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TTPlugins 2 | TTPlugins is the user plugin framework for Terraria Tweaker 2, a Terraria client patcher. TTPlugins is included with Terraria Tweaker 2.3+ and provides a convenient means for users to modify Terraria by writing high-level, dynamic patches in C#. 3 | 4 | * Build tools are optional. Write some C# 5.0 compatible source code and TTPlugins will compile it for you. 5 | * Persistent plugin savedata allows for storing data like user preferences between Terraria launches. 6 | * User-adjustable plugin security level limits plugin code to a restricted subset of .NET features. 7 | * Plugin framework provides ways for plugins to use certain powerful .NET features in a more secure, restricted manner. 8 | 9 | ### Dynamic patching 10 | TTPlugins uses the fantastic [Harmony](https://github.com/pardeike/Harmony) library to patch Terraria at runtime. Harmony modifies the execution flow of .NET applications in memory and does not touch any on-disk files. 11 | 12 | ### How easy is it? 13 | Below is an example of a very basic but complete \*.cs plugin file that gives the player superspeed. This is the entire plugin source code. Optional plugin features are not present in this example. 14 | ```C# 15 | using System; 16 | using com.tiberiumfusion.ttplugins.HarmonyPlugins; 17 | namespace MyPlugin 18 | { 19 | public class SuperSpeed : HPlugin 20 | { 21 | public override void PrePatch() 22 | { 23 | CreateHPatchOperation("Terraria.Player", "UpdateEquips", "SuperSpeedPatch", HPatchLocation.Prefix); 24 | } 25 | 26 | public static void SuperSpeedPatch(Terraria.Player __instance) 27 | { 28 | __instance.moveSpeed += 20.0f; 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | ## For plugin authors 35 | Please refer to the [Wiki](https://github.com/TiberiumFusion/TTPlugins/wiki) for primary documentation, including step-by-step tutorials and general library reference. 36 | 37 | ### Example plugins 38 | This repository contains several [example plugins](https://github.com/TiberiumFusion/TTPlugins/tree/master/ExamplePlugins) that cover some common & advanced plugin tasks. 39 | 40 | ### Technical documentation 41 | Complete reference docs for TTPlugins can be found [here](https://www.tiberiumfusion.com/product/ttplugins/reference/latest/). Please note that this material has very sparse descriptions & remarks and may only be helpful to advanced plugin authors. The primary how-to documentation is on the [Wiki](https://github.com/TiberiumFusion/TTPlugins/wiki). 42 | -------------------------------------------------------------------------------- /TTPlugins/Management/SecurityCompliance/LevelTestConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using com.tiberiumfusion.ttplugins.Management; 7 | using Mono.Cecil; 8 | 9 | namespace com.tiberiumfusion.ttplugins.Management.SecurityCompliance 10 | { 11 | /// 12 | /// A configuration object which contains the parameters for testing a single security level. 13 | /// 14 | public class LevelTestConfiguration 15 | { 16 | /// 17 | /// A list of namespaces which code in the is forbidden from using. This includes types, subnamespaces, types of subnamespaces, and recursions thereof. 18 | /// 19 | public List RestrictedNamespaces { get; set; } = new List(); 20 | 21 | /// 22 | /// A list of types (by their full name w/o assembly name) which code in the is forbidden from using. 23 | /// 24 | public List RestrictedTypes { get; set; } = new List(); 25 | 26 | /// 27 | /// A list of methods (by their CLR name) which code in the is forbidden from using. 28 | /// 29 | public List RestrictedMethods { get; set; } = new List(); 30 | 31 | /// 32 | /// A list of namespaces which code in the is allowed to use, including types, subnamespaces, types of subnamespaces, and recursions thereof. 33 | /// 34 | /// 35 | /// The types, subnamespaces, types of subnamespaces, and recursions thereof derived from the items in this list are considered exempt from restriction, even if those explicit and implicit items appear in or . 36 | /// 37 | public List WhitelistedNamespaces { get; set; } = new List(); 38 | 39 | /// 40 | /// A list of types which code in the is allowed to use. 41 | /// 42 | /// 43 | /// The types of the items in this list are considered exempt from restriction, even if those items appear in or belong to namespaces appearing in . 44 | /// 45 | public List WhitelistedTypes { get; set; } = new List(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /ExamplePlugins/Simple/PrecompiledPluginDemo/PrecompiledPluginDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {76A9C6DA-5D66-425E-84F4-27C7D769793A} 8 | Library 9 | Properties 10 | TTPluginsExamples.Simple.PrecompiledPluginDemo 11 | PrecompiledPluginDemo 12 | v4.5.2 13 | 512 14 | true 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 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ..\..\References\Terraria1445.exe 44 | False 45 | 46 | 47 | False 48 | ..\..\References\TTPlugins.dll 49 | False 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /ExamplePlugins/Simple/PrecompiledPluginDemo/SimpleDefenseBoostPlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | using System.Xml.XPath; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using com.tiberiumfusion.ttplugins.HarmonyPlugins; 12 | 13 | namespace TTPluginsExamples.Simple.PrecompiledPluginDemo 14 | { 15 | /* This example plugin will increase player defense by 200. 16 | * The Visual Studio project structure around this class demonstrates how to precompile a plugin. 17 | * 18 | * Don't forget: 19 | * - Add a project reference for TTPlugins.dll 20 | * - If your plugin code statically references any Terraria types, you must add a project reference for Terraria.exe 21 | * - AND you must recompile your plugin every time Terraria updates! 22 | * 23 | * This example is tiny to help understanding, but there is often very little reason to precompile small plugins. 24 | * Typically, you should only precompile plugins that require images, sounds, and other assets that must be embedded as assembly resources. 25 | */ 26 | 27 | public class SimpleDefenseBoostPlugin : HPlugin 28 | { 29 | public override void Initialize() 30 | { 31 | // Establish this plugin's internal Identity within the TTPlugins environment. Every plugin should have a unique internal Identity. 32 | Identity.PluginName = "SimpleDefenseBoostPlugin"; 33 | Identity.PluginDescription = "Example plugin. Increases player defense by 200."; 34 | Identity.PluginAuthor = "TiberiumFusion"; 35 | Identity.PluginVersion = new Version("1.0.0.0"); 36 | 37 | HasPersistentSavedata = false; // This plugin doesn't use persistent savedata. 38 | } 39 | 40 | public override void ConfigurationLoaded(bool successfulConfigLoadFromDisk) 41 | { 42 | // This plugin does not use persistent savedata, so we will ignore our Configuration property. 43 | } 44 | 45 | public override void PrePatch() 46 | { 47 | // Define our single patch operation. 48 | CreateHPatchOperation("Terraria.Player", "ResetEffects", "PostfixPatch", HPatchLocation.Postfix); 49 | // We will patch Terraria.Player.ResetEffects into calling our custom PostfixPatch method after the original method finishes. 50 | } 51 | 52 | 53 | // Our stub method to patch into Terraria 54 | public static void PostfixPatch(Terraria.Player __instance) 55 | { 56 | // The specially named __instance parameter is automatically filled in by Harmony to reference the Player instance that is calling this method. 57 | 58 | __instance.statDefense += 200; // Add 200 defense to the Terraria.Player 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HPluginIdentity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 8 | { 9 | /// 10 | /// Informational class which describes an HPlugin's identity. 11 | /// 12 | public class HPluginIdentity 13 | { 14 | /// 15 | /// The name of this plugin. 16 | /// 17 | public string PluginName 18 | { 19 | get { return _PluginName; } 20 | set 21 | { 22 | _PluginName = value; 23 | HasModifiedName = true; 24 | HasModifiedIDInfo = true; 25 | } 26 | } 27 | private string _PluginName = null; 28 | 29 | /// 30 | /// A brief description of what this plugin does. 31 | /// 32 | public string PluginDescription 33 | { 34 | get { return _PluginDescription; } 35 | set 36 | { 37 | _PluginDescription = value; 38 | HasModifiedIDInfo = true; 39 | } 40 | } 41 | private string _PluginDescription = null; 42 | 43 | /// 44 | /// The creator of this plugin. 45 | /// 46 | public string PluginAuthor 47 | { 48 | get { return _PluginAuthor; } 49 | set 50 | { 51 | _PluginAuthor = value; 52 | HasModifiedIDInfo = true; 53 | } 54 | } 55 | private string _PluginAuthor = null; 56 | 57 | /// 58 | /// The version of this plugin. 59 | /// 60 | public Version PluginVersion 61 | { 62 | get { return _PluginVersion; } 63 | set 64 | { 65 | _PluginVersion = value; 66 | HasModifiedIDInfo = true; 67 | } 68 | } 69 | private Version _PluginVersion = null; 70 | 71 | /// 72 | /// True if the PluginName plugin has been modified (and thus is likely not an empty default value). 73 | /// 74 | public bool HasModifiedName { get; private set; } 75 | 76 | /// 77 | /// True if any of the plugin identification properties in this object have been modified in some way (and thus are likely not empty default values). 78 | /// 79 | public bool HasModifiedIDInfo { get; private set; } 80 | 81 | 82 | /// 83 | /// Creates a new HPluginIdentity with default values. 84 | /// 85 | public HPluginIdentity() 86 | { 87 | PluginName = "Unknown Plugin"; 88 | PluginDescription = "Unknown Description"; 89 | PluginAuthor = "Unknown Author"; 90 | PluginVersion = new Version(0, 0, 0, 0); 91 | 92 | HasModifiedName = false; 93 | HasModifiedIDInfo = false; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HPluginCompilationConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 8 | { 9 | /// 10 | /// The configuration used to compile usercode HPlugins into assemblies. 11 | /// 12 | public class HPluginCompilationConfiguration 13 | { 14 | /// 15 | /// List of paths to all CS source files to use. 16 | /// 17 | public List SourceFiles { get; set; } = new List(); 18 | 19 | /// 20 | /// Path to the root IO.PluginsUserFilesFolder directory that contains that SourceFiles. 21 | /// 22 | public string UserFilesRootDirectory { get; set; } 23 | 24 | /// 25 | /// If true, all source files will be compiled into a single output assembly. 26 | /// If false, each source file will be compiled into its own assembly. 27 | /// 28 | public bool SingleAssemblyOutput { get; set; } = false; 29 | 30 | /// 31 | /// List of paths to all references needed for plugin compilation (i.e. Terraria.exe and its extracted dependencies). 32 | /// Must be file paths on the disk, since CodeDom cannot use in-memory assemblies. 33 | /// 34 | public List ReferencesOnDisk { get; set; } = new List(); 35 | 36 | /// 37 | /// List of all references that are in memory. 38 | /// HPluginAssemblyCompiler.Compile() will write temporary disk copies of these files to a temporary folder for CodeDom to reference. 39 | /// 40 | public List ReferencesInMemory { get; set; } = new List(); 41 | 42 | /// 43 | /// If true, the ReferencesInMemory that were written to temporary disk copies will be deleted once the compile operation is complete. 44 | /// 45 | public bool ClearTemporaryFilesWhenDone { get; set; } = true; 46 | 47 | /// 48 | /// If true, ReferencesInMemory won't be written to temporary disk copies, and the contents of the folder will be re-used instead. 49 | /// 50 | public bool ReuseTemporaryFiles { get; set; } = false; 51 | 52 | /// 53 | /// Directory where the compiled output files will be placed. Set to null to use the default. 54 | /// 55 | public string DiskOutputDirectory { get; set; } = null; 56 | 57 | /// 58 | /// If true, the generated assembly and its pdb will be deleted from the disk once the compilation has finished. 59 | /// 60 | public bool DeleteOutputFilesFromDiskWhenDone { get; set; } = true; 61 | 62 | /// 63 | /// Command-line arguments provided to CompilerParameters.CompilerOptions during plugin compile. By default, this is "/optimize". 64 | /// 65 | public string CompilerArguments { get; set; } = "/optimize"; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /TTPlugins/Forms/PluginReport.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace com.tiberiumfusion.ttplugins.Forms 2 | { 3 | partial class PluginReport 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.richText = new System.Windows.Forms.RichTextBox(); 32 | this.closeButton = new System.Windows.Forms.Button(); 33 | this.SuspendLayout(); 34 | // 35 | // richText 36 | // 37 | this.richText.Location = new System.Drawing.Point(7, 7); 38 | this.richText.Name = "richText"; 39 | this.richText.ReadOnly = true; 40 | this.richText.Size = new System.Drawing.Size(770, 520); 41 | this.richText.TabIndex = 0; 42 | this.richText.Text = ""; 43 | // 44 | // closeButton 45 | // 46 | this.closeButton.Location = new System.Drawing.Point(702, 533); 47 | this.closeButton.Name = "closeButton"; 48 | this.closeButton.Size = new System.Drawing.Size(75, 23); 49 | this.closeButton.TabIndex = 1; 50 | this.closeButton.Text = "Close"; 51 | this.closeButton.UseVisualStyleBackColor = true; 52 | this.closeButton.Click += new System.EventHandler(this.closeButton_Click); 53 | // 54 | // PluginReport 55 | // 56 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 57 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 58 | this.ClientSize = new System.Drawing.Size(784, 564); 59 | this.Controls.Add(this.closeButton); 60 | this.Controls.Add(this.richText); 61 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D; 62 | this.Name = "PluginReport"; 63 | this.ShowIcon = false; 64 | this.ShowInTaskbar = false; 65 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 66 | this.Text = "Plugin Report"; 67 | this.TopMost = true; 68 | this.ResumeLayout(false); 69 | 70 | } 71 | 72 | #endregion 73 | 74 | private System.Windows.Forms.RichTextBox richText; 75 | private System.Windows.Forms.Button closeButton; 76 | } 77 | } -------------------------------------------------------------------------------- /Resources/PluginTemplate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | using System.Xml.XPath; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using com.tiberiumfusion.ttplugins.HarmonyPlugins; 12 | using Microsoft.Xna.Framework; 13 | using Microsoft.Xna.Framework.Graphics; 14 | using Microsoft.Xna.Framework.Input; 15 | 16 | namespace {{TEMPLATE_NAMESPACE}} 17 | { 18 | /// 19 | /// This is the main class of your plugin. 20 | /// Don't forget to use the TTPlugins reference wiki: https://github.com/TiberiumFusion/TTPlugins/wiki 21 | /// 22 | public class {{TEMPLATE_CLASSNAME}} : HPlugin 23 | { 24 | #region Plugin Self-Management 25 | 26 | /// 27 | /// This is called ONCE by the HPlugin applicator immediately after creating an instance of this HPlugin. Setup your plugin here. 28 | /// 1. Set the various fields of the Identity property to identify your plugin. 29 | /// 2. Set HasPersistentData to true or false, depending on your plugin's needs. 30 | /// 31 | public override void Initialize() 32 | { 33 | // Establish this plugin's internal Identity within the TTPlugins environment. Every plugin should have a unique internal Identity. 34 | Identity.PluginName = "{{TEMPLATE_PLUGINIDNAME}}"; 35 | Identity.PluginDescription = "{{TEMPLATE_PLUGINIDDESC}}"; 36 | Identity.PluginAuthor = "{{TEMPLATE_PLUGINIDAUTHOR}}"; 37 | Identity.PluginVersion = new Version("{{TEMPLATE_PLUGINIDVERSION}}"); 38 | 39 | HasPersistentSavedata = false; // Set to true if your plugin uses the persistent savedata system. 40 | 41 | 42 | // TODO: Setup the rest of your plugin here (if necessary) 43 | } 44 | 45 | /// 46 | /// This is called ONCE by the HPlugin applicator some time after Initialize() and after the plugin's on-disk savedata has been loaded (if applicable). 47 | /// At this point, the Configuration property has been populated and is ready to use. 48 | /// Perform more one-time setup logic here, such as loading user preferences from the Configuration's Savedata property. 49 | /// 50 | /// True if the configuration was successfully loaded from the disk (or if there was no prior configuration and a new one was generated). False if the configuration failed to load and a blank configuration was substituted in. 51 | public override void ConfigurationLoaded(bool successfulConfigLoadFromDisk) 52 | { 53 | // TODO: Load persistent savedata (if applicable) 54 | } 55 | 56 | /// 57 | /// This is called ONCE by the HPlugin applicator (some time after ConfigurationLoaded()) immediately before the plugin's PatchOperations are executed. 58 | /// If your plugin has not defined its PatchOperations by this point, it must do so now, or nothing will be patched. 59 | /// 60 | public override void PrePatch() 61 | { 62 | // TODO: Define patch operations for your plugin using the various CreateHPatchOperation() methods. 63 | } 64 | 65 | #endregion 66 | 67 | 68 | #region Plugin Patch Methods 69 | 70 | // TODO: Define some static stub methods that will do things when dynamically patched into Terraria. 71 | 72 | #endregion 73 | } 74 | } -------------------------------------------------------------------------------- /TTPlugins.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.1145 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TTPlugins", "TTPlugins\TTPlugins.csproj", "{A01762B1-4E94-44E0-B4D6-E81949CC09D9}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrecompiledPluginDemo", "ExamplePlugins\Simple\PrecompiledPluginDemo\PrecompiledPluginDemo.csproj", "{76A9C6DA-5D66-425E-84F4-27C7D769793A}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExamplePlugins", "ExamplePlugins", "{D0FE83F7-7451-4874-887A-F9F3A7E5F66A}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Simple", "Simple", "{952D9296-D7D2-4438-8710-0196304D5E6D}" 13 | ProjectSection(SolutionItems) = preProject 14 | ExamplePlugins\Simple\PersistentSavedataDemo.cs = ExamplePlugins\Simple\PersistentSavedataDemo.cs 15 | ExamplePlugins\Simple\SinglePatchDemo.cs = ExamplePlugins\Simple\SinglePatchDemo.cs 16 | EndProjectSection 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompleteWeaponDemo", "ExamplePlugins\Advanced\CompleteWeaponDemo\CompleteWeaponDemo.csproj", "{2440C5D0-4D27-4B07-B9B6-6140AB9C27D0}" 19 | EndProject 20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Advanced", "Advanced", "{29D399A3-147C-4179-9D1E-0C7AD3E737DE}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resources", "Resources", "{4F8B1A4F-6371-4575-A545-9EEE7C308167}" 23 | ProjectSection(SolutionItems) = preProject 24 | Resources\PluginTemplate.cs = Resources\PluginTemplate.cs 25 | EndProjectSection 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|Any CPU = Debug|Any CPU 30 | Release|Any CPU = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {A01762B1-4E94-44E0-B4D6-E81949CC09D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {A01762B1-4E94-44E0-B4D6-E81949CC09D9}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {A01762B1-4E94-44E0-B4D6-E81949CC09D9}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {A01762B1-4E94-44E0-B4D6-E81949CC09D9}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {76A9C6DA-5D66-425E-84F4-27C7D769793A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {76A9C6DA-5D66-425E-84F4-27C7D769793A}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {76A9C6DA-5D66-425E-84F4-27C7D769793A}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {76A9C6DA-5D66-425E-84F4-27C7D769793A}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {2440C5D0-4D27-4B07-B9B6-6140AB9C27D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {2440C5D0-4D27-4B07-B9B6-6140AB9C27D0}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {2440C5D0-4D27-4B07-B9B6-6140AB9C27D0}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {2440C5D0-4D27-4B07-B9B6-6140AB9C27D0}.Release|Any CPU.Build.0 = Release|Any CPU 45 | EndGlobalSection 46 | GlobalSection(SolutionProperties) = preSolution 47 | HideSolutionNode = FALSE 48 | EndGlobalSection 49 | GlobalSection(NestedProjects) = preSolution 50 | {76A9C6DA-5D66-425E-84F4-27C7D769793A} = {952D9296-D7D2-4438-8710-0196304D5E6D} 51 | {952D9296-D7D2-4438-8710-0196304D5E6D} = {D0FE83F7-7451-4874-887A-F9F3A7E5F66A} 52 | {2440C5D0-4D27-4B07-B9B6-6140AB9C27D0} = {29D399A3-147C-4179-9D1E-0C7AD3E737DE} 53 | {29D399A3-147C-4179-9D1E-0C7AD3E737DE} = {D0FE83F7-7451-4874-887A-F9F3A7E5F66A} 54 | EndGlobalSection 55 | GlobalSection(ExtensibilityGlobals) = postSolution 56 | SolutionGuid = {DC144877-29BD-4DD0-925E-685AA9A5FD3F} 57 | EndGlobalSection 58 | EndGlobal 59 | -------------------------------------------------------------------------------- /TTPlugins/Management/SecurityCompliance/PluginTestConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace com.tiberiumfusion.ttplugins.Management.SecurityCompliance 8 | { 9 | /// 10 | /// A configuration object which contains the parameters for CecilTests.TestPluginCompliance() 11 | /// 12 | public class PluginTestConfiguration 13 | { 14 | /// 15 | /// List of PluginFiles to test. If any of these PluginFiles are source files, they will be compiled first. 16 | /// 17 | public List PluginFilesToTest { get; set; } 18 | 19 | /// 20 | /// Specifies how Terraria relates to the assembly load context in which TTPlugins is currently running. This determines the necessity of setting certain other parameters of this configuration. 21 | /// 22 | /// 23 | /// When is specified, and must be specified. 24 | /// When is specified, automatic compilation of plugins in source code during the security tests is disabled (those plugins, if any, are ignored) and and are ignored and can be left null. 25 | /// When is specified, an exception is raised in . 26 | /// Terraria Tweaker 2 and TTApplicator always uses . The Terraria entry point in TTPlugins itself uses 27 | /// 28 | public TerrariaEnvironment TerrariaEnvironment { get; set; } 29 | 30 | /// 31 | /// Path to the root IO.PluginsUserFilesFolder directory that contains that SourceFiles. 32 | /// 33 | public string UserFilesRootDirectory { get; set; } 34 | 35 | /// 36 | /// Path to Terraria.exe, which will be referenced by CodeDom during compilation. 37 | /// 38 | public string TerrariaPath { get; set; } 39 | 40 | /// 41 | /// List of Terraria.exe's embedded dependency assemblies, which will be temporarily written to disk and referenced by CodeDom during on-demand compilation of plugins in source code form. 42 | /// 43 | public List TerrariaDependencyAssemblies { get; set; } 44 | 45 | /// 46 | /// Optional list of on-paths to additional assemblies which plugin compilation may depend upon. 47 | /// 48 | /// 49 | /// TT2 and TTApplicator use this to include 0Harmony.dll as a reference. 50 | /// 51 | public List AdditionalCompileDependencies { get; set; } 52 | 53 | /// 54 | /// Whether or not to perform the Security Level 1 test. 55 | /// 56 | public bool RunLevel1Test = true; 57 | 58 | /// 59 | /// Whether or not to perform the Security Level 2 test. 60 | /// 61 | public bool RunLevel2Test = true; 62 | 63 | /// 64 | /// Whether or not to perform the Security Level 3 test. 65 | /// 66 | public bool RunLevel3Test = true; 67 | 68 | /// 69 | /// Whether or not to perform the Security Level 4 test. 70 | /// 71 | public bool RunLevel4Test = true; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ExamplePlugins/Advanced/CompleteWeaponDemo/CompleteWeaponDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2440C5D0-4D27-4B07-B9B6-6140AB9C27D0} 8 | Library 9 | Properties 10 | TTPluginsExamples.Advanced.CompleteWeaponDemo 11 | CompleteWeaponDemo 12 | v4.5.2 13 | 512 14 | true 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 | False 36 | ..\..\..\ExternalReferences\Microsoft.Xna.Framework.dll 37 | 38 | 39 | False 40 | ..\..\..\ExternalReferences\Microsoft.Xna.Framework.Game.dll 41 | 42 | 43 | False 44 | ..\..\..\ExternalReferences\Microsoft.Xna.Framework.Graphics.dll 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ..\..\References\Terraria.Libraries.ReLogic.ReLogic1445.dll 56 | False 57 | 58 | 59 | ..\..\References\Terraria1445.exe 60 | False 61 | 62 | 63 | ..\..\References\TTPlugins.dll 64 | False 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /TTPlugins/Management/SecurityCompliance/PluginTestResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace com.tiberiumfusion.ttplugins.Management.SecurityCompliance 8 | { 9 | /// 10 | /// Container of secury compliance test results for a single PluginFile. 11 | /// 12 | public class PluginTestResult 13 | { 14 | /// 15 | /// Whether or not an unexpected exception occurred during the test. 16 | /// 17 | public bool GenericTestFailure = false; 18 | 19 | /// 20 | /// Whether or not the PluginFile being tested was a C# source file and it did not compile successfully. 21 | /// 22 | public bool CompileFailure = false; 23 | 24 | /// 25 | /// Messages from the testing process that are not related to any specific level of the security tests. 26 | /// 27 | public List GenericMessages = new List(); 28 | 29 | /// 30 | /// Whether or not compliance with Level 1 security was tested. 31 | /// 32 | public bool TestedLevel1 = false; 33 | 34 | /// 35 | /// Whether or not compliance with Level 2 security was tested. 36 | /// 37 | public bool TestedLevel2 = false; 38 | 39 | /// 40 | /// Whether or not compliance with Level 3 security was tested. 41 | /// 42 | public bool TestedLevel3 = false; 43 | 44 | /// 45 | /// Whether or not compliance with Level 4 security was tested. 46 | /// 47 | public bool TestedLevel4 = false; 48 | 49 | /// 50 | /// Whether or not the compiled plugin is compliant with Level 1 security. 51 | /// 52 | public bool PassLevel1 = false; 53 | 54 | /// 55 | /// Whether or not the compiled plugin is compliant with Level 2 security. 56 | /// 57 | public bool PassLevel2 = false; 58 | 59 | /// 60 | /// Whether or not the compiled plugin is compliant with Level 3 security. 61 | /// 62 | public bool PassLevel3 = false; 63 | 64 | /// 65 | /// Whether or not the compiled plugin is compliant with Level 4 security. 66 | /// 67 | public bool PassLevel4 = false; 68 | 69 | /// 70 | /// Messages from the Level 1 testing procedure, which can be shown to the user in UI if applicable. 71 | /// 72 | public List MessagesLevel1 = new List(); 73 | 74 | /// 75 | /// Messages from the Level 2 testing procedure, which can be shown to the user in UI if applicable. 76 | /// 77 | public List MessagesLevel2 = new List(); 78 | 79 | /// 80 | /// Messages from the Level 3 testing procedure, which can be shown to the user in UI if applicable. 81 | /// 82 | public List MessagesLevel3 = new List(); 83 | 84 | /// 85 | /// Messages from the Level 4 testing procedure, which can be shown to the user in UI if applicable. 86 | /// 87 | public List MessagesLevel4 = new List(); 88 | 89 | /// 90 | /// The PluginFile associated with these test results. 91 | /// 92 | public PluginFile TestedPluginFile { get; private set; } 93 | 94 | 95 | /// 96 | /// Creates a new SecurityLevelComplianceSingleTestResult for the specified PluginFile. 97 | /// 98 | /// The PluginFile to associate with these test results. 99 | public PluginTestResult(PluginFile pluginFileToTest) 100 | { 101 | TestedPluginFile = pluginFileToTest; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /TTPlugins/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Mono.Cecil; 8 | 9 | namespace com.tiberiumfusion.ttplugins 10 | { 11 | internal static class Extensions 12 | { 13 | /// 14 | /// Gets the *real* bytes that constitute an Assembly using a rather ugly hack. 15 | /// 16 | /// The to turn into a byte[]. 17 | /// A byte[] containing the assembly's bytes, or null if the operation failed. 18 | public static byte[] ToByteArray(this Assembly assembly) 19 | { 20 | try 21 | { 22 | MethodInfo asmGetRawBytes = assembly.GetType().GetMethod("GetRawBytes", BindingFlags.Instance | BindingFlags.NonPublic); 23 | object bytesObject = asmGetRawBytes.Invoke(assembly, null); 24 | return (byte[])bytesObject; 25 | } 26 | catch (Exception e) 27 | { 28 | return null; 29 | } 30 | } 31 | 32 | /// 33 | /// Actually useful version of ToString() for Cecil's CustomAttribute type. The ToString() provided by Cecil simply dumps the type of Mono.Cecil.CustomAttribute instead of showing anything about the instanced CustomAttribute itself. 34 | /// 35 | /// The to stringify. 36 | /// A string that resembles the way the attribute would have been typed in its source code. 37 | public static string ToBetterString(this CustomAttribute attribute) 38 | { 39 | if (attribute.HasConstructorArguments) 40 | { 41 | ModuleDefinition module = attribute.AttributeType.Module; 42 | 43 | string[] args = new string[attribute.ConstructorArguments.Count]; 44 | for (int i = 0; i < attribute.ConstructorArguments.Count; i++) 45 | { 46 | CustomAttributeArgument arg = attribute.ConstructorArguments[i]; 47 | if (arg.Type == module.TypeSystem.Boolean) 48 | args[i] = (bool)arg.Value ? "true" : "false"; 49 | else if (arg.Type == module.TypeSystem.Byte) 50 | args[i] = arg.Value.ToString(); 51 | else if (arg.Type == module.TypeSystem.SByte) 52 | args[i] = arg.Value.ToString(); 53 | else if (arg.Type == module.TypeSystem.Char) 54 | args[i] = "'" + arg.Value + "'"; 55 | else if (arg.Type == module.TypeSystem.Int16 || arg.Type == module.TypeSystem.Int32) 56 | args[i] = arg.Value.ToString(); 57 | else if (arg.Type == module.TypeSystem.Int64) 58 | args[i] = arg.Value.ToString(); 59 | else if (arg.Type == module.TypeSystem.UInt16 || arg.Type == module.TypeSystem.UInt32) 60 | args[i] = arg.Value.ToString(); 61 | else if (arg.Type == module.TypeSystem.UInt64) 62 | args[i] = arg.Value.ToString(); 63 | else if (arg.Type == module.TypeSystem.Single) 64 | args[i] = arg.Value.ToString() + "f"; 65 | else if (arg.Type == module.TypeSystem.Double) 66 | args[i] = ((double)arg.Value).ToString(".0###############"); 67 | else if (arg.Type == module.TypeSystem.String) 68 | args[i] = "\"" + arg.Value + "\""; 69 | else 70 | args[i] = arg.Value.ToString(); 71 | } 72 | 73 | return "[" 74 | + attribute.AttributeType.ToString() 75 | + "(" 76 | + string.Join(", ", args) 77 | + ")]"; 78 | } 79 | else 80 | { 81 | return "[" 82 | + attribute.AttributeType.ToString() 83 | + "]"; 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HPatchOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 9 | { 10 | /// 11 | /// Provides the necessary data to apply a single Harmony patch. 12 | /// 13 | public class HPatchOperation 14 | { 15 | /// 16 | /// The target method which will be dynamically patched to use the stub method. 17 | /// 18 | public MethodBase TargetMethod { get; private set; } 19 | 20 | /// 21 | /// The stub method which will be appended or prepended to the target method. 22 | /// 23 | public MethodInfo StubMethod { get; private set; } 24 | 25 | /// 26 | /// Whether the stub method will be patched as a prefix or postfix on the target method. 27 | /// 28 | public HPatchLocation PatchLocation { get; private set; } 29 | 30 | /// 31 | /// The priority of the patched stub method. Higher numbers go first. If -1, the default priority will be used (typically 400). 32 | /// 33 | public int PatchPriority { get; private set; } = -1; 34 | 35 | 36 | /// 37 | /// Creates a new patch operation using the supplied target method, stub method, and patch location. 38 | /// 39 | /// The target method or constructor that will be patched. 40 | /// The stub method that will be either prepended or appended to the target method. Must be a static method! 41 | /// Whether the stub method will be prepended as a prefix, appended as a postfix, or applied as a transpiler to the target method. 42 | /// The priority of this patch, as used by Harmony to order multiple patches on the same method. Patches with higher numbers go first. Set to -1 to use default priority (typically = 400). 43 | public HPatchOperation(MethodBase targetMethod, MethodInfo stubMethod, HPatchLocation patchLocation, int patchPriority = -1) 44 | { 45 | TargetMethod = targetMethod; 46 | StubMethod = stubMethod; 47 | PatchLocation = patchLocation; 48 | PatchPriority = patchPriority; 49 | } 50 | 51 | /// 52 | /// Creates a new patch operation using the supplied target type & method name, stub method, and patch location. 53 | /// 54 | /// The target type that contains the target method. 55 | /// The name of the target method. 56 | /// The stub method that will be either prepended or appended to the target method. Must be a static method! 57 | /// Whether the stub method will be prepended as a prefix, appended as a postfix, or applied as a transpiler to the target method. 58 | /// The priority of this patch, as used by Harmony to order multiple patches on the same method. Patches with higher numbers go first. Set to -1 to use default priority (typically = 400). 59 | public HPatchOperation(Type targetType, string targetMethodName, MethodInfo stubMethod, HPatchLocation patchLocation, int patchPriority = -1) 60 | { 61 | MethodBase targetMethod = targetType.GetRuntimeMethods().Where(m => m.Name == targetMethodName).FirstOrDefault(); // Search regular methods first 62 | if (targetMethod == null) // Search constructors next if nothing was found 63 | targetMethod = targetType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).Where(m => m.Name == targetMethodName).FirstOrDefault(); 64 | if (targetMethod == null) 65 | throw new Exception("No method or constructor with the name \"" + targetMethodName + "\" was found in target type \"" + targetType.FullName + "\""); 66 | 67 | TargetMethod = targetMethod; 68 | StubMethod = stubMethod; 69 | PatchLocation = patchLocation; 70 | PatchPriority = patchPriority; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /TTPlugins/Management/PluginFile.cs: -------------------------------------------------------------------------------- 1 | using com.tiberiumfusion.ttplugins.HarmonyPlugins; 2 | using Mono.Cecil; 3 | using System; 4 | using System.CodeDom.Compiler; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Runtime.Serialization.Formatters.Binary; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using com.tiberiumfusion.ttplugins.Management.SecurityCompliance; 13 | 14 | namespace com.tiberiumfusion.ttplugins.Management 15 | { 16 | /// 17 | /// A representation of a single plugin file in the user's Plugins folder. 18 | /// 19 | public class PluginFile 20 | { 21 | #region Properties 22 | 23 | /// 24 | /// Path to the plugin file on the disk. 25 | /// 26 | public string PathToFile { get; private set; } 27 | 28 | /// 29 | /// What kind of file the plugin is (source or compiled asm). 30 | /// 31 | public PluginFileType FileType { get; private set; } 32 | 33 | #endregion 34 | 35 | 36 | /// 37 | /// Creates a new PluginFile object for use in plugin file management. 38 | /// 39 | /// Path to the file on the disk. 40 | /// What kind of file the plugin is. 41 | public PluginFile(string path, PluginFileType type) 42 | { 43 | PathToFile = path; 44 | FileType = type; 45 | } 46 | 47 | /// 48 | /// Updates this PluginFile when the contents of the file on the disk change. 49 | /// 50 | public void UpdateFromFileChange() 51 | { 52 | // Nothing here for now 53 | } 54 | 55 | /// 56 | /// Update this PluginFile's PathToFile and FileType to correspond to a new path. 57 | /// 58 | /// The new path to use. 59 | public void UpdateFilePath(string newPath) 60 | { 61 | PathToFile = newPath; 62 | 63 | string newExt = Path.GetExtension(Path.GetFullPath(newPath)).ToLowerInvariant(); 64 | if (newExt == ".cs") 65 | FileType = PluginFileType.CSSourceFile; 66 | else if (newExt == ".dll") 67 | FileType = PluginFileType.CompiledAssemblyFile; 68 | } 69 | 70 | /// 71 | /// Returns the relative path of this plugin file on the disk (relative to IO.PluginsUserFilesFolder). 72 | /// 73 | /// The relative path, or null if the file is not actually relative to IO.PluginsUserFilesFolder. 74 | public string GetRelativePath() 75 | { 76 | return IO.GetRelativeUserFilesPathFor(PathToFile); 77 | } 78 | 79 | /// 80 | /// Returns the path to use for reading and writing temporary, on-disk files relating to this PluginFile. 81 | /// 82 | /// The path to the directory to use. The directory will NOT be created if it does not exist. 83 | public string GetTemporaryFilesPath() 84 | { 85 | return IO.GetTemporaryFilePathFor(this); 86 | } 87 | 88 | 89 | /// 90 | /// Tests this PluginFile against all security levels so as to determine the maximum security level that will allow this plugin to function. 91 | /// 92 | /// 93 | /// This method should only be used by management tools (i.e. TT2 and TTApp). 94 | /// 95 | /// Path to Terraria.exe, which will be referenced by CodeDom during compilation. 96 | /// List of Terraria.exe's embedded dependency assemblies, which will be temporarily written to disk and reference by CodeDom during compilation. 97 | /// Optional list of on-disk paths to additional assemblies that may be required for plugin compilation. 98 | /// A SecurityLevelComplianceTestResult object containing the test results. 99 | public MultipleTestsResults TestAllSecurityLevelCompliance(string terrariaPath, List terrariaDependencyAssemblies, List additionalCompileDependencies = null) 100 | { 101 | PluginTestConfiguration config = new PluginTestConfiguration(); 102 | config.PluginFilesToTest = new List() { this }; 103 | config.TerrariaEnvironment = TerrariaEnvironment.Offline; 104 | config.TerrariaPath = terrariaPath; 105 | config.TerrariaDependencyAssemblies = terrariaDependencyAssemblies; 106 | config.AdditionalCompileDependencies = additionalCompileDependencies; 107 | return CecilTests.TestPluginCompliance(config); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HPluginApplicatorResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 8 | { 9 | /// 10 | /// A data bundle of returned information from the HPluginApplicator 11 | /// 12 | public class HPluginApplicatorResult 13 | { 14 | #region Properties 15 | 16 | /// 17 | /// Result code that identifies the type of result. 18 | /// 19 | public HPluginApplicatorResultCodes ResultCode = HPluginApplicatorResultCodes.Success; 20 | 21 | /// 22 | /// Thrown exception (if applicable) 23 | /// 24 | public Exception ThrownException = null; 25 | 26 | /// 27 | /// Error message (if applicable). Used for non-exception errors. 28 | /// 29 | public string ErrorMessage = null; 30 | 31 | /// 32 | /// List of HPlugins (by their full type name) that failed to construct in the Activator. 33 | /// 34 | public List HPluginsThatFailedConstruction = new List(); 35 | 36 | /// 37 | /// List of HPlugins (by their relative source file path) that threw exceptions during their Initialize() (and thus don't have a valid Identity to use). 38 | /// 39 | public Dictionary HPluginsThatFailedInitialize = new Dictionary(); 40 | 41 | /// 42 | /// List of HPlugins (by their relative source file path) that could not have their on-disk configuration loaded and the corresponding exception. 43 | /// 44 | public Dictionary HPluginsWithFailedConfigurationLoads = new Dictionary(); 45 | 46 | /// 47 | /// List of HPlugins (by their relative source file path) that threw exceptions in their ConfigurationLoaded(). 48 | /// 49 | public Dictionary HPluginsThatFailedConfigurationLoaded = new Dictionary(); 50 | 51 | /// 52 | /// List of HPlugins (by their relative source file path) that threw exceptions in their PrePatch(). 53 | /// 54 | public Dictionary HPluginsThatFailedPrePatch = new Dictionary(); 55 | 56 | /// 57 | /// List of HPlugins (by their relative source file path) that tried to do things they shouldn't do. 58 | /// 59 | public Dictionary HPluginsThatBrokeRules = new Dictionary(); 60 | 61 | /// 62 | /// List of HPlugins (by their relative source file path) that tried to patch null MethodInfos. 63 | /// 64 | public List HPluginsThatTriedToPatchNullMethodInfos = new List(); 65 | 66 | /// 67 | /// List of HPlugins (by their relative source file path) that threw exceptions while Harmony.Patch was trying to patch them. 68 | /// 69 | public Dictionary HPluginsThatDidntPatch = new Dictionary(); 70 | 71 | #endregion 72 | 73 | 74 | /// 75 | /// Sets the properties to indicate failure from a caught Exception 76 | /// 77 | /// The result code. 78 | /// The thrown Exception. 79 | public void ConfigureAsFailure(HPluginApplicatorResultCodes resultCode = HPluginApplicatorResultCodes.GenericFailure, Exception error = null) 80 | { 81 | ResultCode = resultCode; 82 | ThrownException = error; 83 | } 84 | 85 | /// 86 | /// Sets the properties to indicate failure from a non-exception error. 87 | /// 88 | /// The result code. 89 | /// The error message. 90 | public void ConfigureAsFailure(HPluginApplicatorResultCodes resultCode = HPluginApplicatorResultCodes.GenericFailure, string error = null) 91 | { 92 | ResultCode = resultCode; 93 | ErrorMessage = error; 94 | } 95 | } 96 | 97 | /// 98 | /// All possible result codes from plugin application. 99 | /// 100 | public enum HPluginApplicatorResultCodes 101 | { 102 | /// 103 | /// No outstanding errors occurred. 104 | /// 105 | Success = 0, 106 | 107 | /// 108 | /// An unexpected error occurred. 109 | /// 110 | GenericFailure = 1000, 111 | 112 | /// 113 | /// Could not instantiate Harmony. 114 | /// 115 | CreateHarmonyInstanceFailure = 1001, 116 | 117 | /// 118 | /// An unexpected error occurred, specifically during plugin application (i.e. after preparation). 119 | /// 120 | GenericHPluginApplicationFailure = 2000, 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HFrameworkPatches.cs: -------------------------------------------------------------------------------- 1 | using com.tiberiumfusion.ttplugins.Forms; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 12 | { 13 | /// 14 | /// Holder of various Harmony patches which provide some framework for HPlugins. 15 | /// 16 | public static class HFrameworkPatches 17 | { 18 | /// 19 | /// Simple logging helper. 20 | /// 21 | private static void DLog(string message) 22 | { 23 | Debug.WriteLine("[TTPlugins] (HFrameworkPatches) Writing all plugin configuration to disk..."); 24 | } 25 | 26 | /// 27 | /// Prefixed onto Terraria.Main.SaveSettings(). 28 | /// Writes all persistent plugin savedata back to disk. 29 | /// 30 | public static void FW_SaveAllPluginConfigs() 31 | { 32 | DLog("Entered FW_SaveAllPluginConfigs()"); 33 | 34 | try 35 | { 36 | DLog("Writing all plugin configuration to disk..."); 37 | HPluginApplicator.WriteAllPluginConfigToDisk(); 38 | DLog("Finished writing plugin configurations."); 39 | } 40 | catch (Exception e) 41 | { 42 | DLog("Generic error while writing plugin configurations. Details: " + e); 43 | } 44 | } 45 | 46 | /// 47 | /// Prefixed onto Terraria.Main.QuitGame(). 48 | /// Sets up hidden cmd task to kill the runtime extract folder after a short delay (after which Terraria should be closed and the files no longer locked). 49 | /// 50 | public static void FW_RemoveRuntimeExtractDirOnQuite() 51 | { 52 | DLog("Entered FW_RemoveRuntimeExtractDirOnQuite()"); 53 | 54 | try 55 | { 56 | if (HPluginApplicator.TerrariaAssembly != null) 57 | { 58 | string fullRuntimeExtractDirPath = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(HPluginApplicator.TerrariaAssembly.Location)), HPluginApplicator.RuntimeExtractFolder); 59 | DLog("Starting cmd delete task for runtime extract dir: " + fullRuntimeExtractDirPath + "..."); 60 | 61 | // Credit: https://stackoverflow.com/questions/41922322/using-winform-c-sharp-delete-the-folder-the-exe-exists-in 62 | // This is kind of ugly, but it is the only simple, reliable way I can think of to do this 63 | ProcessStartInfo procInfo = new ProcessStartInfo("cmd.exe", 64 | String.Format("/k {0} & {1} & {2}", 65 | "timeout /T 5 /NOBREAK >NUL", 66 | "rmdir /s /q \"" + fullRuntimeExtractDirPath + "\"", 67 | "exit") 68 | ); 69 | 70 | procInfo.UseShellExecute = false; 71 | procInfo.CreateNoWindow = true; 72 | procInfo.WindowStyle = ProcessWindowStyle.Hidden; 73 | Process.Start(procInfo); 74 | DLog("Runtime extract dir delete task created."); 75 | } 76 | } 77 | catch (Exception e) 78 | { 79 | DLog("Error while creating cmd delete task for runtime extract dir. Details: " + e); 80 | } 81 | } 82 | 83 | /// 84 | /// Prefixed onto Terraria.TimeLogger.DrawException(). 85 | /// Writes the intercepted exception to Debug. 86 | /// 87 | /// The intercepted exception. 88 | public static void FW_InterceptTimeLoggerDrawException(Exception e) 89 | { 90 | Debug.WriteLine("Exception intercepted from Terraria.TimeLogger.DrawException(): " + e); 91 | } 92 | 93 | /// 94 | /// Postfixed onto Terraria.Chat.ChatCommandProcessor.CreateOutgoingMessage(). 95 | /// Shows the plugin report if the player types /ttplugins in chat. 96 | /// 97 | /// Parameter from original method. 98 | public static void FW_ShowPluginReport(string text) 99 | { 100 | DLog("Entered FW_ShowPluginReport()"); 101 | 102 | if (text == "/ttplugins") 103 | { 104 | try 105 | { 106 | Form terrariaForm = (Form)Form.FromHandle(Terraria.Main.instance.Window.Handle); 107 | 108 | if (HPluginApplicator.LastResult != null) 109 | { 110 | PluginReport reportForm = new PluginReport(); 111 | reportForm.CreateReport(HPluginApplicator.LastResult); 112 | reportForm.Show(terrariaForm); 113 | } 114 | } 115 | catch (Exception e) 116 | { 117 | DLog("Generic error while showing plugins report form. Details: " + e); 118 | } 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ExamplePlugins/Simple/PersistentSavedataDemo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | using System.Xml.XPath; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using com.tiberiumfusion.ttplugins.HarmonyPlugins; 12 | using Microsoft.Xna.Framework.Input; 13 | 14 | namespace TTPluginsExamples.Simple 15 | { 16 | /* This example plugin provides the player with a variable speed boost that can be changed ingame. 17 | * Additionally, the current speed boost intensity is saved between Terraria launches using persistent savedata. 18 | * 19 | * This is the plugin code from the "Creating a Basic Plugin (Part 2: User Input and Persistent Savedata)" tutorial video. (with many added comments) 20 | * 21 | * Key demonstrations: 22 | * - How to detect when the local player presses keys 23 | * - The basics of saving and loading persistent xml savedata using Configuration.Savedata 24 | */ 25 | 26 | public class PersistentSavedataDemo : HPlugin 27 | { 28 | private static PersistentSavedataDemo Singleton; // Static reference to the HPlugin instance so we can access Configuration.Savedata in static patch methods 29 | private static float SpeedBoostAmount = 0f; // Current intensity of the speed boost effect 30 | 31 | public override void Initialize() 32 | { 33 | // Establish this plugin's internal Identity within the TTPlugins environment. Every plugin should have a unique internal Identity. 34 | Identity.PluginName = "PersistentSavedataDemo"; 35 | Identity.PluginDescription = "Example plugin. Gives the player a speed boost that can be customized with ingame hotkeys."; 36 | Identity.PluginAuthor = "TiberiumFusion"; 37 | Identity.PluginVersion = new Version("1.0.0.0"); 38 | 39 | HasPersistentSavedata = true; // We are using persistent savedata to store and load the chosen speed boost intensity between Terraria launches 40 | 41 | Singleton = this; // Assign the singleton so we can access Configuration.Savedata in our static ChangeSpeedBoost() patch method 42 | } 43 | 44 | public override void ConfigurationLoaded(bool successfulConfigLoadFromDisk) 45 | { 46 | // This method will be called after the plugin system loads the persistent savedata for this plugin. 47 | // We will check for an element that will store our speed boost intensity and load its value, if available. 48 | 49 | XElement elementSpeedBoostAmount = Configuration.Savedata.Element("SpeedBoostAmount"); // Look for an element called SpeedBoostAmount 50 | if (elementSpeedBoostAmount != null) // If it exists... 51 | float.TryParse(elementSpeedBoostAmount.Value, out SpeedBoostAmount); // ...try to parse its value 52 | } 53 | 54 | public override void PrePatch() 55 | { 56 | // Define our single patch operation. 57 | CreateHPatchOperation("Terraria.Player", "UpdateEquips", "PatchSpeedBoost", HPatchLocation.Prefix); 58 | // We will patch Terraria.Player.UpdateEquips into calling our custom PatchSpeedBoost method before the original method executes. 59 | } 60 | 61 | 62 | // Helper method that: 63 | // 1. Changes the speed boost amount 64 | // 2. Ensures the speed boost doesn't become negative 65 | // 3. Updates the persistent savedata 66 | private static void ChangeSpeedBoost(float amount) 67 | { 68 | SpeedBoostAmount += amount; // Change the speed boost intensity 69 | 70 | if (SpeedBoostAmount < 0f) // Ensure it doesn't go negative 71 | SpeedBoostAmount = 0f; 72 | 73 | // Check for the SpeedBoostAmount element in our persistent savedata 74 | XElement elementSpeedBoostAmount = Singleton.Configuration.Savedata.Element("SpeedBoostAmount"); 75 | if (elementSpeedBoostAmount == null) // If it doesn't exist, we must create it 76 | { 77 | elementSpeedBoostAmount = new XElement("SpeedBoostAmount"); 78 | Singleton.Configuration.Savedata.Add(elementSpeedBoostAmount); 79 | } 80 | 81 | // Assign the current speed boost intensity to be the value of our SpeedBoostAmount element 82 | elementSpeedBoostAmount.Value = SpeedBoostAmount.ToString(); 83 | } 84 | 85 | // The actual patch method which will be patched into Terraria 86 | public static void PatchSpeedBoost(Terraria.Player __instance) 87 | { 88 | // The specially named __instance parameter is automatically filled in by Harmony to reference the Player instance that is calling this method. 89 | 90 | // First, we use the HHelpers.InputReading.IsKeyPressed() method to check if the local player has pressed one of our hotkeys. 91 | // If the comma is pressed (aka <), we will decrease the speed boost. 92 | // If the period is pressed (aka >), we will increase the speed boost. 93 | if (HHelpers.InputReading.IsKeyPressed(Keys.OemComma)) 94 | ChangeSpeedBoost(-1f); // Change speed boost using helper method 95 | if (HHelpers.InputReading.IsKeyPressed(Keys.OemPeriod)) 96 | ChangeSpeedBoost(1f); // Change speed boost using helper method 97 | 98 | // Then, we apply the speed boost to the Terraria.Player 99 | __instance.moveSpeed += SpeedBoostAmount; // The moveSpeed field directly controls how fast the player runs 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | 263 | 264 | # Reference assemblies (get them from your own install) 265 | ExternalReferences/ 266 | /ExamplePlugins/References/ 267 | 268 | # Asset source files 269 | ExamplePlugins/Advanced/CompleteWeaponDemo/Assets/*.psd 270 | ExamplePlugins/Advanced/CompleteWeaponDemo/Assets/Sourcing/ 271 | 272 | # Docs 273 | Sandcastle/ 274 | 275 | # Local release copies 276 | Releases/ -------------------------------------------------------------------------------- /TTPlugins/Forms/PluginReport.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /TTPlugins/TTPlugins.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A01762B1-4E94-44E0-B4D6-E81949CC09D9} 8 | Library 9 | com.tiberiumfusion.ttplugins 10 | TTPlugins 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 | bin\Debug\TTPlugins.xml 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | bin\Release\TTPlugins.xml 36 | 37 | 38 | 39 | 40 | 41 | 42 | False 43 | ..\ExternalReferences\0Harmony.dll 44 | harmony 45 | 46 | 47 | False 48 | ..\ExternalReferences\Microsoft.Xna.Framework.dll 49 | 50 | 51 | False 52 | ..\ExternalReferences\Microsoft.Xna.Framework.Game.dll 53 | 54 | 55 | False 56 | ..\ExternalReferences\Microsoft.Xna.Framework.GamerServices.dll 57 | 58 | 59 | False 60 | ..\ExternalReferences\Microsoft.Xna.Framework.Graphics.dll 61 | 62 | 63 | False 64 | ..\ExternalReferences\Microsoft.Xna.Framework.Input.Touch.dll 65 | 66 | 67 | False 68 | ..\ExternalReferences\Microsoft.Xna.Framework.Xact.dll 69 | 70 | 71 | False 72 | ..\ExternalReferences\Mono.Cecil.dll 73 | 74 | 75 | False 76 | ..\ExternalReferences\Mono.Cecil.Pdb.dll 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | ..\ExternalReferences\Terraria.Libraries.ReLogic.ReLogic1445.dll 90 | False 91 | 92 | 93 | ..\ExternalReferences\Terraria1445.exe 94 | False 95 | 96 | 97 | 98 | 99 | 100 | 101 | Form 102 | 103 | 104 | PluginReport.cs 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | PluginReport.cs 142 | 143 | 144 | 145 | 146 | xcopy /F /Q /Y "$(TargetDir)TTPlugins.dll" "$(SolutionDir)ExamplePlugins\References\TTPlugins.dll*" 147 | xcopy /F /Q /Y "$(TargetDir)TTPlugins.xml" "$(SolutionDir)ExamplePlugins\References\TTPlugins.xml*" 148 | 149 | 150 | -------------------------------------------------------------------------------- /TTPlugins/Forms/PluginReport.cs: -------------------------------------------------------------------------------- 1 | using com.tiberiumfusion.ttplugins.HarmonyPlugins; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Data; 6 | using System.Diagnostics; 7 | using System.Drawing; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using System.Windows.Forms; 13 | 14 | namespace com.tiberiumfusion.ttplugins.Forms 15 | { 16 | /// 17 | /// Form which can be shown while ingame to provide information on the state of all processed plugins. 18 | /// 19 | internal partial class PluginReport : Form 20 | { 21 | internal PluginReport() 22 | { 23 | InitializeComponent(); 24 | 25 | MinimizeBox = false; 26 | MaximizeBox = false; 27 | } 28 | 29 | internal void CreateReport(HPluginApplicatorResult applicatorResult) 30 | { 31 | richText.Clear(); 32 | Line("===== Plugin Status Report ====="); 33 | 34 | Line(">>> Plugin Launch Configuration:"); 35 | Line("Debug Mode: " + HPluginApplicator.PluginDebugMode); 36 | Line("Security Level: " + HPluginApplicator.SecurityLevel); 37 | 38 | Line("\n>>> Loaded plugin assemblies: (" + HPluginApplicator.LoadedPluginAssemblies.Count + ")"); 39 | if (HPluginApplicator.LoadedPluginAssemblies.Count == 0) 40 | Line("(none)"); 41 | else 42 | { 43 | foreach (Assembly pluginAsm in HPluginApplicator.LoadedPluginAssemblies) 44 | { 45 | Line("- " + pluginAsm.FullName); 46 | foreach (Type t in pluginAsm.DefinedTypes) 47 | Line(" - " + t.FullName); 48 | } 49 | } 50 | 51 | Line("\n>>> Applied plugins (plugins with at least 1 successful patch operation): (" + HPluginApplicator.AppliedHPlugins.Count + ")"); 52 | if (HPluginApplicator.AppliedHPlugins.Count == 0) 53 | Line("(none)"); 54 | else 55 | { 56 | foreach (HSupervisedPlugin supervisedPlugin in HPluginApplicator.AppliedHPlugins) 57 | { 58 | Line("- " + supervisedPlugin.Plugin.GetType().FullName); 59 | Line(" - Source: " + supervisedPlugin.SourceFileRelativePath); 60 | Line(" - Identity:"); 61 | try 62 | { 63 | Line(" - PluginName: " + supervisedPlugin.Plugin.Identity.PluginName); 64 | Line(" - PluginDescription: " + supervisedPlugin.Plugin.Identity.PluginDescription); 65 | Line(" - PluginAuthor: " + supervisedPlugin.Plugin.Identity.PluginAuthor); 66 | Line(" - PluginVersion: " + supervisedPlugin.Plugin.Identity.PluginVersion.ToString()); 67 | } 68 | catch (Exception e) 69 | { 70 | Line(" - [!] Plugin has invalid Identity object. Details: " + e); 71 | } 72 | } 73 | } 74 | 75 | Line("\n>>> HPluginApplicator Result:"); 76 | Line("Result code: " + (int)applicatorResult.ResultCode + " (" + applicatorResult.ResultCode.ToString() + ")"); 77 | Line("Error message: " + (applicatorResult.ErrorMessage ?? "(none)")); 78 | Line("Exception: " + (applicatorResult.ThrownException != null ? applicatorResult.ThrownException.ToString() : "(none)")); 79 | 80 | Line("\n>>> Plugins that failed to construct: (" + applicatorResult.HPluginsThatFailedConstruction.Count + ")"); 81 | if (applicatorResult.HPluginsThatFailedConstruction.Count == 0) 82 | Line("(none)"); 83 | else 84 | { 85 | foreach (string entry in applicatorResult.HPluginsThatFailedConstruction) 86 | Line("- " + entry); 87 | } 88 | 89 | Line("\n>>> Plugins that threw exceptions in their Initialize(): (" + applicatorResult.HPluginsThatFailedInitialize.Count + ")"); 90 | if (applicatorResult.HPluginsThatFailedInitialize.Count == 0) 91 | Line("(none)"); 92 | else 93 | { 94 | foreach (string relpath in applicatorResult.HPluginsThatFailedInitialize.Keys) 95 | { 96 | Line("- " + relpath); 97 | Line(" Exception: " + applicatorResult.HPluginsThatFailedInitialize[relpath].ToString()); 98 | } 99 | } 100 | 101 | Line("\n>>> Plugins whose configuration failed to load: (" + applicatorResult.HPluginsWithFailedConfigurationLoads.Count + ")"); 102 | if (applicatorResult.HPluginsWithFailedConfigurationLoads.Count == 0) 103 | Line("(none)"); 104 | else 105 | { 106 | foreach (string relpath in applicatorResult.HPluginsWithFailedConfigurationLoads.Keys) 107 | { 108 | Line("- " + relpath); 109 | Line(" Exception: " + applicatorResult.HPluginsWithFailedConfigurationLoads[relpath].ToString()); 110 | } 111 | } 112 | 113 | Line("\n>>> Plugins that threw exceptions in their ConfigurationLoaded(): (" + applicatorResult.HPluginsThatFailedConfigurationLoaded.Count + ")"); 114 | if (applicatorResult.HPluginsThatFailedConfigurationLoaded.Count == 0) 115 | Line("(none)"); 116 | else 117 | { 118 | foreach (string relpath in applicatorResult.HPluginsThatFailedConfigurationLoaded.Keys) 119 | { 120 | Line("- " + relpath); 121 | Line(" Exception: " + applicatorResult.HPluginsThatFailedConfigurationLoaded[relpath].ToString()); 122 | } 123 | } 124 | 125 | Line("\n>>> Plugins that threw exceptions in their PrePatch(): (" + applicatorResult.HPluginsThatFailedPrePatch.Count + ")"); 126 | if (applicatorResult.HPluginsThatFailedPrePatch.Count == 0) 127 | Line("(none)"); 128 | else 129 | { 130 | foreach (string relpath in applicatorResult.HPluginsThatFailedPrePatch.Keys) 131 | { 132 | Line("- " + relpath); 133 | Line(" Exception: " + applicatorResult.HPluginsThatFailedPrePatch[relpath].ToString()); 134 | } 135 | } 136 | 137 | Line("\n>>> Plugins that tried to patch a method on a restricted type: (" + applicatorResult.HPluginsThatBrokeRules.Count + ")"); 138 | if (applicatorResult.HPluginsThatBrokeRules.Count == 0) 139 | Line("(none)"); 140 | else 141 | { 142 | foreach (string relpath in applicatorResult.HPluginsThatBrokeRules.Keys) 143 | { 144 | Line("- " + relpath); 145 | Line(" Details: " + applicatorResult.HPluginsThatBrokeRules[relpath].ToString()); 146 | } 147 | } 148 | 149 | Line("\n>>> Plugins that tried to patch null MethodInfos: (" + applicatorResult.HPluginsThatTriedToPatchNullMethodInfos.Count + ")"); 150 | if (applicatorResult.HPluginsThatTriedToPatchNullMethodInfos.Count == 0) 151 | Line("(none)"); 152 | else 153 | { 154 | foreach (string relpath in applicatorResult.HPluginsThatTriedToPatchNullMethodInfos) 155 | Line("- " + relpath); 156 | } 157 | 158 | Line("\n>>> Plugins with patch operations that failed: (" + applicatorResult.HPluginsThatDidntPatch.Count + ")"); 159 | if (applicatorResult.HPluginsThatDidntPatch.Count == 0) 160 | Line("(none)"); 161 | else 162 | { 163 | foreach (string relpath in applicatorResult.HPluginsThatDidntPatch.Keys) 164 | { 165 | Line("- " + relpath); 166 | Line(" Exception: " + applicatorResult.HPluginsThatDidntPatch[relpath].ToString()); 167 | } 168 | } 169 | 170 | richText.SelectionStart = 0; 171 | richText.ScrollToCaret(); 172 | } 173 | 174 | [DebuggerStepThrough] 175 | private void Line(string text) 176 | { 177 | richText.AppendText(text + "\n"); 178 | } 179 | 180 | private void closeButton_Click(object sender, EventArgs e) 181 | { 182 | DialogResult = DialogResult.OK; 183 | Close(); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HPlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 11 | { 12 | /// 13 | /// The base class which provides the means to create a Harmony-based plugin. 14 | /// 15 | public class HPlugin 16 | { 17 | #region Properties 18 | 19 | /// 20 | /// An informational object which describes the identity of this plugin. 21 | /// This property is used to identify plugin savedata and must be unique. 22 | /// 23 | public HPluginIdentity Identity { get; private set; } 24 | 25 | /// 26 | /// The list of patch operations that constitute this HPlugin's functionality. 27 | /// 28 | public List PatchOperations { get; private set; } 29 | 30 | /// 31 | /// Contains the plugin's pesistent savedata, which includes user preferences from savedata (if any). 32 | /// 33 | public HPluginConfiguration Configuration { get; private set; } 34 | 35 | /// 36 | /// Whether or not this plugin needs to write persistent savedata to disk (such as for user preferences). 37 | /// 38 | public bool HasPersistentSavedata { get; protected set; } 39 | 40 | /// 41 | /// The Assembly which contains this plugin. Can be used to get embedded resources and assembly attributes. 42 | /// 43 | protected Assembly PluginAssembly { get; private set; } 44 | 45 | #endregion 46 | 47 | 48 | #region Ctor 49 | 50 | /// 51 | /// Creates a new HPlugin with default values. 52 | /// 53 | public HPlugin() 54 | { 55 | Identity = new HPluginIdentity(); 56 | PatchOperations = new List(); 57 | Configuration = null; 58 | HasPersistentSavedata = false; 59 | } 60 | 61 | #endregion 62 | 63 | 64 | #region Convenient HPatchOperation creators 65 | 66 | /// 67 | /// Creates a new patch operation using the supplied target method, stub method, and patch location. 68 | /// 69 | /// The target method (in Terraria) that will be patched. 70 | /// The stub method (in your plugin) that will be either prepended onto, appended onto, or transpiled with the target method. Must be a static method! 71 | /// Whether the stub method will be prepended as a prefix, appended as a postfix, or applied as a transpiler to the target method. 72 | /// The priority of this patch, as used by Harmony to order multiple patches on the same method. Patches with higher numbers go first. Set to -1 to use default priority (typically = 400). 73 | protected void CreateHPatchOperation(MethodBase targetMethod, MethodInfo stubMethod, HPatchLocation patchLocation, int patchPriority = -1) 74 | { 75 | PatchOperations.Add(new HPatchOperation(targetMethod, stubMethod, patchLocation, patchPriority)); 76 | } 77 | 78 | /// 79 | /// Creates a new patch operation using the supplied target type and method name, stub method, and patch location. 80 | /// 81 | /// The target type (in Terraria) that contains the target method. 82 | /// The name of the target method. 83 | /// The stub method (in your plugin) that will be either prepended onto, appended onto, or transpiled with the target method. Must be a static method! 84 | /// Whether the stub method will be prepended as a prefix, appended as a postfix, or applied as a transpiler to the target method. 85 | /// The priority of this patch, as used by Harmony to order multiple patches on the same method. Patches with higher numbers go first. Set to -1 to use default priority (typically = 400). 86 | protected void CreateHPatchOperation(Type targetType, string targetMethodName, MethodInfo stubMethod, HPatchLocation patchLocation, int patchPriority = -1) 87 | { 88 | PatchOperations.Add(new HPatchOperation(targetType, targetMethodName, stubMethod, patchLocation, patchPriority)); 89 | } 90 | 91 | /// 92 | /// Creates a new patch operation using the supplied target method, stub method name from this class, and patch location. 93 | /// 94 | /// The target method (in Terraria) that will be patched. 95 | /// The name of the stub method IN THIS CLASS that will be either prepended onto, appended onto, or transpiled with the target method. Must be a static method! 96 | /// Whether the stub method will be prepended as a prefix, appended as a postfix, or applied as a transpiler to the target method. 97 | /// The priority of this patch, as used by Harmony to order multiple patches on the same method. Patches with higher numbers go first. Set to -1 to use default priority (typically = 400). 98 | protected void CreateHPatchOperation(MethodBase targetMethod, string stubMethodName, HPatchLocation patchLocation, int patchPriority = -1) 99 | { 100 | MethodInfo stubMethod = this.GetType().GetRuntimeMethods().Where(x => x.Name == stubMethodName).FirstOrDefault(); 101 | if (stubMethod == null) 102 | throw new Exception("Invalid stubMethodName. This type does not contain a method named \"" + stubMethodName + "\"."); 103 | if (!stubMethod.Attributes.HasFlag(MethodAttributes.Static)) 104 | throw new Exception("Invalid stub method. The stub method specified is not static."); 105 | 106 | CreateHPatchOperation(targetMethod, stubMethod, patchLocation, patchPriority); 107 | } 108 | 109 | /// 110 | /// Creates a new patch operation using the supplied target type and method name, stub method name, and patch location. 111 | /// 112 | /// The target type (in Terraria) that contains the target method. 113 | /// The name of the target method. 114 | /// The name of the stub method IN THIS CLASS that will be either prepended onto, appended onto, or transpiled with the target method. Must be a static method! 115 | /// Whether the stub method will be prepended as a prefix, appended as a postfix, or applied as a transpiler to the target method. 116 | /// The priority of this patch, as used by Harmony to order multiple patches on the same method. Patches with higher numbers go first. Set to -1 to use default priority (typically = 400). 117 | protected void CreateHPatchOperation(Type targetType, string targetMethodName, string stubMethodName, HPatchLocation patchLocation, int patchPriority = -1) 118 | { 119 | MethodInfo stubMethod = this.GetType().GetRuntimeMethods().Where(x => x.Name == stubMethodName).FirstOrDefault(); 120 | if (stubMethod == null) 121 | throw new Exception("Invalid stubMethodName. This type does not contain a method named \"" + stubMethodName + "\"."); 122 | if (!stubMethod.Attributes.HasFlag(MethodAttributes.Static)) 123 | throw new Exception("Invalid stub method. The stub method specified is not static."); 124 | 125 | CreateHPatchOperation(targetType, targetMethodName, stubMethod, patchLocation, patchPriority); 126 | } 127 | 128 | /// 129 | /// Creates a new patch operation using the supplied target type name, target method name, stub method, and patch location. 130 | /// 131 | /// The full name of target type (in Terraria) that contains the target method, e.g. "Terraria.Main". 132 | /// The name of the target method. 133 | /// The stub method (in your plugin) that will be either prepended onto, appended onto, or transpiled with the target method. Must be a static method! 134 | /// Whether the stub method will be prepended as a prefix, appended as a postfix, or applied as a transpiler to the target method. 135 | /// The priority of this patch, as used by Harmony to order multiple patches on the same method. Patches with higher numbers go first. Set to -1 to use default priority (typically = 400). 136 | protected void CreateHPatchOperation(string targetTypeFullName, string targetMethodName, MethodInfo stubMethod, HPatchLocation patchLocation, int patchPriority = -1) 137 | { 138 | Type targetType = null; 139 | if (!HHelpers.TryGetTerrariaType(targetTypeFullName, out targetType)) 140 | throw new Exception("Invalid targetTypeFullName. Terraria does not contain a type named \"" + targetTypeFullName + "\"."); 141 | 142 | CreateHPatchOperation(targetType, targetMethodName, stubMethod, patchLocation, patchPriority); 143 | } 144 | 145 | /// 146 | /// Creates a new patch operation using the supplied target type name, target method name, stub method name, and patch location. 147 | /// 148 | /// The full name of target type (in Terraria) that contains the target method, e.g. "Terraria.Main". 149 | /// The name of the target method. 150 | /// The name of the stub method IN THIS CLASS that will be either prepended onto, appended onto, or transpiled with the target method. Must be a static method! 151 | /// Whether the stub method will be prepended as a prefix, appended as a postfix, or applied as a transpiler to the target method. 152 | /// The priority of this patch, as used by Harmony to order multiple patches on the same method. Patches with higher numbers go first. Set to -1 to use default priority (typically = 400). 153 | protected void CreateHPatchOperation(string targetTypeFullName, string targetMethodName, string stubMethodName, HPatchLocation patchLocation, int patchPriority = -1) 154 | { 155 | Type targetType = null; 156 | if (!HHelpers.TryGetTerrariaType(targetTypeFullName, out targetType)) 157 | throw new Exception("Invalid targetTypeFullName. Terraria does not contain a type named \"" + targetTypeFullName + "\"."); 158 | 159 | CreateHPatchOperation(targetType, targetMethodName, stubMethodName, patchLocation, patchPriority); 160 | } 161 | 162 | /// 163 | /// Creates a new patch operation using the supplied target type name, target method name, target method parameter count, stub method name, and patch location. 164 | /// 165 | /// The full name of target type (in Terraria) that contains the target method, e.g. "Terraria.Main". 166 | /// The name of the target method. 167 | /// The number of parameters in the target method. Can be used to help discern between method overloads. 168 | /// The name of the stub method IN THIS CLASS that will be either prepended onto, appended onto, or transpiled with the target method. Must be a static method! 169 | /// Whether the stub method will be prepended as a prefix, appended as a postfix, or applied as a transpiler to the target method. 170 | /// The priority of this patch, as used by Harmony to order multiple patches on the same method. Patches with higher numbers go first. Set to -1 to use default priority (typically = 400). 171 | protected void CreateHPatchOperation(string targetTypeFullName, string targetMethodName, int targetMethodParamCount, string stubMethodName, HPatchLocation patchLocation, int patchPriority = -1) 172 | { 173 | Type targetType = null; 174 | if (!HHelpers.TryGetTerrariaType(targetTypeFullName, out targetType)) 175 | throw new Exception("Invalid targetTypeFullName. Terraria does not contain a type named \"" + targetTypeFullName + "\"."); 176 | 177 | // First look in normal methods 178 | MethodBase targetMethod = targetType.GetRuntimeMethods().Where(x => 179 | x.Name == targetMethodName && 180 | x.GetParameters().Count() == targetMethodParamCount).FirstOrDefault(); 181 | if (targetMethod == null) // If nothing was found, look in constructors next 182 | { 183 | targetMethod = targetType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).Where(x => 184 | x.Name == targetMethodName && 185 | x.GetParameters().Count() == targetMethodParamCount).FirstOrDefault(); 186 | } 187 | if (targetMethod == null) 188 | throw new Exception("Invalid target method. The target type does not contain a method or constructor named \"" + stubMethodName + "\" with " + targetMethodParamCount + " parameters."); 189 | 190 | CreateHPatchOperation(targetMethod, stubMethodName, patchLocation, patchPriority); 191 | } 192 | 193 | /// 194 | /// Creates a new patch operation using the supplied target type name, target method name, target method parameter count, target method last parameter type, stub method name, and patch location. 195 | /// 196 | /// The full name of target type (in Terraria) that contains the target method, e.g. "Terraria.Main". 197 | /// The name of the target method. 198 | /// The number of parameters in the target method. Can be used to help discern between method overloads. 199 | /// The type of the target method's last parameter. Can be used to help discern between method overloads. 200 | /// The name of the stub method IN THIS CLASS that will be either prepended onto, appended onto, or transpiled with the target method. Must be a static method! 201 | /// Whether the stub method will be prepended as a prefix, appended as a postfix, or applied as a transpiler to the target method. 202 | /// The priority of this patch, as used by Harmony to order multiple patches on the same method. Patches with higher numbers go first. Set to -1 to use default priority (typically = 400). 203 | protected void CreateHPatchOperation(string targetTypeFullName, string targetMethodName, int targetMethodParamCount, Type targetMethodLastParamType, string stubMethodName, HPatchLocation patchLocation, int patchPriority = -1) 204 | { 205 | Type targetType = null; 206 | if (!HHelpers.TryGetTerrariaType(targetTypeFullName, out targetType)) 207 | throw new Exception("Invalid targetTypeFullName. Terraria does not contain a type named \"" + targetTypeFullName + "\"."); 208 | 209 | MethodInfo targetMethod = targetType.GetRuntimeMethods().Where(x => 210 | x.Name == targetMethodName && 211 | x.GetParameters().Count() == targetMethodParamCount && 212 | x.GetParameters().Count() > 0 && 213 | x.GetParameters()[x.GetParameters().Count() - 1].ParameterType == targetMethodLastParamType).FirstOrDefault(); 214 | if (targetMethod == null) 215 | throw new Exception("Invalid target method. The target type does not contain a method named \"" + stubMethodName + "\" with " + targetMethodParamCount + " parameters and a final parameter of type \"" + targetMethodLastParamType.FullName + "\""); 216 | 217 | CreateHPatchOperation(targetMethod, stubMethodName, patchLocation, patchPriority); 218 | } 219 | 220 | #endregion 221 | 222 | 223 | #region Security Compliant Helpers 224 | 225 | /// 226 | /// Retrieves the byte[] that constitutes an embedded resouce in this HPlugin's PluginAssembly. 227 | /// This helper is particularly useful when the plugin Security Level is set to Level 3 or higher (which disallows use of System.IO). 228 | /// 229 | /// The name of the embedded resource to retrieve. 230 | /// The embedded resource's bytes. 231 | public byte[] GetPluginAssemblyResourceBytes(string resourceName) 232 | { 233 | using (var resStream = PluginAssembly.GetManifestResourceStream(resourceName)) 234 | { 235 | using (MemoryStream memStream = new MemoryStream()) 236 | { 237 | resStream.CopyTo(memStream); 238 | return memStream.ToArray(); 239 | } 240 | } 241 | } 242 | 243 | #endregion 244 | 245 | 246 | #region Override Methods 247 | 248 | /// 249 | /// Called by the HPlugin applicator immediately after creating an instance of this HPlugin. Setup your plugin here. 250 | /// 1. Set the various fields of the Identity property to identify your plugin. 251 | /// 2. Set HasPersistentData to true or false, depending on the plugin's needs. 252 | /// 253 | public virtual void Initialize() { } 254 | 255 | /// 256 | /// Called by the HPlugin applicator after Initialize and after the plugin's on-disk savedata has been loaded (if applicable). 257 | /// At this point, the Configuration property has been populated and is ready to use. 258 | /// Perform one-time setup logic here, such as loading user preferences from the Configuration property. 259 | /// 260 | /// True if the configuration was successfully loaded from the disk (or if there was no prior configuration and a new one was generated). False if the configuration failed to load and a blank configuration was substituted in. 261 | public virtual void ConfigurationLoaded(bool successfulConfigLoadFromDisk) { } 262 | 263 | /// 264 | /// Called by the HPlugin applicator immediately before the plugin's PatchOperations are executed. 265 | /// If the plugin has not defined its PatchOperations by this point, it must do so now, or nothing will be patched. 266 | /// 267 | public virtual void PrePatch() { } 268 | 269 | #endregion 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /TTPlugins/HarmonyPlugins/HPluginAssemblyCompiler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CSharp; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Cil; 4 | using System; 5 | using System.CodeDom.Compiler; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace com.tiberiumfusion.ttplugins.HarmonyPlugins 14 | { 15 | /// 16 | /// Provider of the compiled assemblies that contain the usercode HPlugins. 17 | /// 18 | public static class HPluginAssemblyCompiler 19 | { 20 | /// 21 | /// Name of the temporary folder which will be created on disk if necessary during the assembly compilation (such as for referencing in-memory assemblies with CodeDom) 22 | /// 23 | public static string TemporaryFilesDirectory { get; set; } = ".TTPlugins_CompileTemp"; 24 | 25 | /// 26 | /// Name of the output folder which will be created to contain the generated dll and pdb files from the compile process. 27 | /// 28 | public static string DefaultOutputFilesDirectory { get; set; } = ".TTPlugins_CompileOutput"; 29 | 30 | 31 | #region Some Assembly Loading 32 | 33 | /// 34 | /// Attempts to load the specific assemblies which Terraria references into the current AppDomain for plugin compilation. 35 | /// Only loads "regular" assemblies, i.e. those that exist on-disk and are not embedded in Terraria itself. 36 | /// 37 | public static void TryLoadDotNetTerrariaReferences() 38 | { 39 | List terrariaReferences = new List() 40 | { 41 | "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 42 | "System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", 43 | "System.Runtime.Serialization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 44 | "System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 45 | "WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", 46 | "Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553", 47 | "Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553", 48 | "Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553", 49 | "Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553", 50 | }; 51 | foreach (string asmName in terrariaReferences) 52 | { 53 | try { Assembly.Load(asmName); } 54 | catch (Exception e) { } // This shouldn't happen, but just in case. 55 | } 56 | } 57 | 58 | /// 59 | /// Attempts to load some of the more common .NET assemblies into the current AppDomain for plugin compilation. 60 | /// 61 | public static void TryLoadCommonDotNetAssemblies() 62 | { 63 | List commonDotNetAsms = new List() 64 | { 65 | "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 66 | "System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 67 | "System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 68 | "System.Data.DataSetExtensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 69 | "System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 70 | "System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", 71 | "System.Net, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", 72 | "System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", 73 | "System.Numerics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 74 | "System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", 75 | "System.Windows, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", 76 | "System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 77 | "System.Windows.Presentation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 78 | "System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 79 | "System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 80 | "System.Xml.Serialization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 81 | }; 82 | foreach (string asmName in commonDotNetAsms) 83 | { 84 | try { Assembly.Load(asmName); } 85 | catch (Exception e) { } // This shouldn't happen, but just in case. 86 | } 87 | } 88 | 89 | #endregion 90 | 91 | 92 | /// 93 | /// Compiles and returns a list of assemblies using the provided configuration. 94 | /// 95 | /// The configuration to use when compiling. 96 | /// The compiled assemblies. 97 | public static HPluginCompilationResult Compile(HPluginCompilationConfiguration configuration) 98 | { 99 | HPluginCompilationResult result = new HPluginCompilationResult(); 100 | 101 | // Output directory 102 | string outputDir = configuration.DiskOutputDirectory ?? DefaultOutputFilesDirectory; 103 | result.OutputDirectory = outputDir; 104 | 105 | try 106 | { 107 | // Load all potentially required assemblies into our appdomain 108 | TryLoadDotNetTerrariaReferences(); 109 | 110 | // Compiler configuration 111 | CompilerParameters compilerParams = new CompilerParameters(); 112 | compilerParams.GenerateInMemory = true; // This just affects the compilation process (should be faster than using the disk). The output assembly and its pdb are always written to a file. 113 | compilerParams.GenerateExecutable = false; 114 | compilerParams.CompilerOptions = configuration.CompilerArguments; 115 | compilerParams.IncludeDebugInformation = true; 116 | compilerParams.TreatWarningsAsErrors = false; 117 | // References on disk 118 | foreach (string filePath in configuration.ReferencesOnDisk) 119 | compilerParams.ReferencedAssemblies.Add(filePath); 120 | // References in memory 121 | if (configuration.ReuseTemporaryFiles) 122 | { 123 | if (Directory.Exists(TemporaryFilesDirectory)) 124 | { 125 | foreach (string refAsmPath in Directory.GetFiles(TemporaryFilesDirectory)) 126 | compilerParams.ReferencedAssemblies.Add(refAsmPath); 127 | } 128 | } 129 | else 130 | { 131 | int refAsmNum = 0; 132 | foreach (byte[] asmBytes in configuration.ReferencesInMemory) 133 | { 134 | Directory.CreateDirectory(TemporaryFilesDirectory); 135 | string asmFullPath = Path.Combine(Directory.GetCurrentDirectory(), TemporaryFilesDirectory, "RefAsm" + refAsmNum + ".dll"); 136 | File.WriteAllBytes(asmFullPath, asmBytes); 137 | compilerParams.ReferencedAssemblies.Add(asmFullPath); 138 | refAsmNum++; 139 | } 140 | } 141 | // Reference self (TTPlugins) 142 | compilerParams.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location); 143 | // Reference whatever is loaded in our appdomain, which will likely contain most of the common types and namespaces from System. 144 | foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) 145 | { 146 | if (!asm.IsDynamic && !asm.ReflectionOnly && !String.IsNullOrEmpty(asm.Location)) 147 | compilerParams.ReferencedAssemblies.Add(asm.Location); 148 | } 149 | 150 | CSharpCodeProvider csProvider = new CSharpCodeProvider(); 151 | 152 | // If output directory already exists, clear it 153 | if (Directory.Exists(outputDir)) 154 | { 155 | DirectoryInfo outputDirInfo = new DirectoryInfo(outputDir); 156 | try { outputDirInfo.Delete(true); } 157 | catch (Exception e) { } // Swallow (for now) 158 | } 159 | Directory.CreateDirectory(outputDir); 160 | 161 | // Setup output and compile 162 | if (configuration.SingleAssemblyOutput) 163 | { 164 | string dllName = "TTPlugins_CompiledConglomerate.dll"; 165 | string pdbName = Path.GetFileNameWithoutExtension(dllName) + ".pdb"; 166 | string dllOutput = Path.Combine(outputDir, dllName); 167 | string pdbOutput = Path.Combine(outputDir, pdbName); 168 | compilerParams.OutputAssembly = dllOutput; 169 | if (CompileOnce(configuration.SourceFiles, configuration, compilerParams, csProvider, result)) 170 | { 171 | result.OutputFilesOnDisk.Add(dllOutput); 172 | result.OutputFilesOnDisk.Add(pdbOutput); 173 | } 174 | } 175 | else 176 | { 177 | foreach (string sourceFile in configuration.SourceFiles) 178 | { 179 | int numShift = 0; 180 | string originDllName = "TTPlugins_CompiledAsm_" + Path.GetFileNameWithoutExtension(sourceFile).Replace(' ', '_'); 181 | string dllName = originDllName; 182 | string checkDllOutput = Path.Combine(outputDir, dllName + ".dll"); 183 | while (File.Exists(checkDllOutput)) // Ensure no file conflicts 184 | { 185 | numShift++; 186 | dllName = originDllName + numShift; 187 | checkDllOutput = Path.Combine(outputDir, dllName + ".dll"); 188 | } 189 | dllName += ".dll"; 190 | string pdbName = Path.GetFileNameWithoutExtension(dllName) + ".pdb"; 191 | string dllOutput = Path.Combine(outputDir, dllName); 192 | string pdbOutput = Path.Combine(outputDir, pdbName); 193 | compilerParams.OutputAssembly = dllOutput; 194 | if (CompileOnce(new List() { sourceFile }, configuration, compilerParams, csProvider, result)) 195 | { 196 | result.OutputFilesOnDisk.Add(dllOutput); 197 | result.OutputFilesOnDisk.Add(pdbOutput); 198 | } 199 | } 200 | } 201 | } 202 | catch (Exception e) 203 | { 204 | result.GenericCompilationFailure = true; 205 | } 206 | 207 | // Clear temporary reference assembly files if config says to or if there was a compile failure 208 | if (configuration.ClearTemporaryFilesWhenDone || result.GenericCompilationFailure) 209 | ClearTemporaryCompileFiles(); 210 | 211 | // Clear output files if config says to or if there was a compile failure 212 | if (configuration.DeleteOutputFilesFromDiskWhenDone || result.GenericCompilationFailure) 213 | { 214 | TryRemoveDirectory(outputDir); 215 | result.OutputFilesOnDisk.Clear(); 216 | } 217 | 218 | return result; 219 | } 220 | 221 | private static bool CompileOnce(List sourceFiles, HPluginCompilationConfiguration configuration, CompilerParameters compilerParams, CSharpCodeProvider csProvider, HPluginCompilationResult result) 222 | { 223 | CompilerResults compileResult = csProvider.CompileAssemblyFromFile(compilerParams, sourceFiles.ToArray()); 224 | bool compileSuccess = true; 225 | 226 | if (compileResult.Errors.HasErrors) 227 | { 228 | foreach (CompilerError error in compileResult.Errors) 229 | result.CompileErrors.Add(error); 230 | compileSuccess = false; 231 | } 232 | else 233 | { 234 | // Get the compiled assembly 235 | Assembly asm = compileResult.CompiledAssembly; 236 | result.CompiledAssemblies.Add(asm); 237 | 238 | // Then go through all compiled HPlugin types and deduce the relative path of each one so that it can be associated with its savedata 239 | // Because of some less-than-great techniques required to do this, it may fail. If that happens, we can at least let the plugin run anyways, just without access to persistent savedata. 240 | try 241 | { 242 | // We need Cecil to do this. Possible solutions with Reflection and CompilerServices get close (i.e. using StackTrace or CallerFilePath), but don't work on unknown subclassed code. 243 | byte[] asmBytes = asm.ToByteArray(); // We could load the on-disk assembly, but this should be much faster, especially for large assemblies with embedded resources. 244 | // Turn the compiled assembly into a stream (so we can load it with cecil) 245 | using (MemoryStream memStream = new MemoryStream(asmBytes)) 246 | { 247 | // Also load the generated pdb file (which is always placed on the disk and is not held in memory). The pdb should be in the working directory. 248 | string pdbFilePath = null; 249 | int spot = compilerParams.OutputAssembly.LastIndexOf(".dll"); 250 | if (spot > -1) 251 | pdbFilePath = compilerParams.OutputAssembly.Substring(0, spot) + ".pdb"; 252 | using (FileStream pdbStream = new FileStream(pdbFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) 253 | { 254 | // Load the assembly with its symbols from the pdb 255 | ReaderParameters readerParameters = new ReaderParameters(); 256 | readerParameters.ReadSymbols = true; 257 | readerParameters.SymbolStream = pdbStream; 258 | AssemblyDefinition cecilAsmDef = AssemblyDefinition.ReadAssembly(memStream, readerParameters); 259 | 260 | // Find the source file path of each type, using SequencePoints 261 | foreach (Type type in asm.GetTypes().Where(t => t.IsClass && t.IsSubclassOf(typeof(HPlugin))).ToList()) 262 | { 263 | string relPath = ""; 264 | 265 | try 266 | { 267 | string foundSourcePath = null; 268 | 269 | // Use any method defined in the user's plugin file to get a SequencePoint and thus the source file path 270 | TypeDefinition typeDef = cecilAsmDef.MainModule.GetTypes().Where(x => x.FullName == type.FullName).FirstOrDefault(); 271 | foreach (MethodDefinition methodDef in typeDef.Methods) 272 | { 273 | if (foundSourcePath != null) 274 | break; 275 | 276 | foreach (Instruction ins in methodDef.Body.Instructions) 277 | { 278 | if (foundSourcePath != null) 279 | break; 280 | 281 | // Cecil 0.10+ breaking API change: Instruction.SequencePoint is removed 282 | SequencePoint seqPoint = null; 283 | try { seqPoint = methodDef.DebugInformation.GetSequencePoint(ins); } // In case the method is missing debug information or it is corrupt in some way 284 | catch (Exception e) { /* Swallow */ } 285 | if (seqPoint != null) 286 | { 287 | if (seqPoint.Document != null) 288 | { 289 | if (!String.IsNullOrEmpty(seqPoint.Document.Url)) 290 | foundSourcePath = seqPoint.Document.Url; 291 | } 292 | } 293 | } 294 | } 295 | 296 | string standardizedSourcePath = Path.GetFullPath(foundSourcePath).ToLowerInvariant(); 297 | string standardizedRootDir = Path.GetFullPath(configuration.UserFilesRootDirectory).ToLowerInvariant(); 298 | int spot2 = standardizedSourcePath.IndexOf(standardizedRootDir); 299 | if (spot2 >= 0) 300 | relPath = (standardizedSourcePath.Substring(0, spot2) + standardizedSourcePath.Substring(spot2 + standardizedRootDir.Length)).TrimStart('\\', '/'); 301 | } 302 | catch (Exception e2) { } // Just swallow it. The plugin probably broke some protocol and thus will not have persistent savedata. 303 | 304 | result.CompiledTypesSourceFileRelativePaths[type.FullName] = relPath; 305 | } 306 | } 307 | } 308 | } 309 | catch (Exception e) { } // Swallow. Persistent savedata will not work, but the plugin(s) themself will be fine. 310 | } 311 | 312 | return compileSuccess; 313 | } 314 | 315 | /// 316 | /// Deletes all files inside the TemporaryFilesDirectory, then removes the directory. 317 | /// 318 | /// True if no errors occured, false if otherwise. 319 | public static bool ClearTemporaryCompileFiles() 320 | { 321 | return TryRemoveDirectory(TemporaryFilesDirectory); 322 | } 323 | 324 | /// 325 | /// Deletes all files inside the specified directory, then removes the directory. 326 | /// 327 | /// The directory to fully delete. 328 | /// True if no errors occured, false if otherwise. 329 | public static bool TryRemoveDirectory(string directory) 330 | { 331 | try 332 | { 333 | if (Directory.Exists(directory)) 334 | { 335 | DirectoryInfo topDirInfo = new DirectoryInfo(directory); 336 | topDirInfo.Delete(true); 337 | } 338 | 339 | return true; 340 | } 341 | catch (Exception e) 342 | { 343 | return false; 344 | } 345 | } 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /TTPlugins/Management/IO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using com.tiberiumfusion.ttplugins.Management.SecurityCompliance; 9 | 10 | namespace com.tiberiumfusion.ttplugins.Management 11 | { 12 | /// 13 | /// IO-related aspects of plugin management. 14 | /// 15 | public static class IO 16 | { 17 | #region Properties 18 | 19 | /// 20 | /// Absolute path to the folder containing the user's plugins, whether in .cs or .dll form. 21 | /// 22 | public static string PluginsUserFilesFolder { get; private set; } // Should be %APPDATA%/Terraria Tweaker 2/Plugins 23 | 24 | /// 25 | /// Absolute path to the top-level working folder for plugin data, where things like temporary savedata are stored. 26 | /// 27 | public static string PluginsDataFolder { get; private set; } // Should be %APPDATA%/Terraria Tweaker 2/ttplugins 28 | 29 | /// 30 | /// Absolute path to the root folder where plugin data is temporarily written, such as for a tweak launch or to edit configuration. 31 | /// 32 | public static string PluginsTempFilesFolder { get { return Path.Combine(PluginsDataFolder, "TempPluginFiles"); } } 33 | 34 | /// 35 | /// List of all PluginFiles that were found in the PluginsUserFilesFolder. 36 | /// 37 | public static List FoundUserPluginFiles { get; private set; } = new List(); 38 | 39 | #endregion 40 | 41 | 42 | #region Fields 43 | 44 | /// 45 | /// The FileSystemWatcher that watches the PluginsUserFilesFolder. 46 | /// 47 | private static FileSystemWatcher FSWatcherUserFiles; 48 | 49 | #endregion 50 | 51 | 52 | #region Events 53 | 54 | /// 55 | /// Generic event args class for the first three UserPluginFile events. 56 | /// 57 | public class UserPluginFileEventArgs : EventArgs 58 | { 59 | public string FilePath { get; private set; } 60 | public PluginFile AffectedPluginFile { get; private set; } 61 | public UserPluginFileEventArgs(string path, PluginFile pluginFile) 62 | { 63 | FilePath = path; 64 | AffectedPluginFile = pluginFile; 65 | } 66 | } 67 | 68 | /// 69 | /// Event raised immediately after a new user plugin file has been added to the FoundUserPluginFiles list. 70 | /// 71 | public static event EventHandler UserPluginFileAdded; 72 | internal static void OnUserPluginFileAdded(UserPluginFileEventArgs e) 73 | { 74 | UserPluginFileAdded?.Invoke(null, e); 75 | } 76 | 77 | /// 78 | /// Event raised immediately after an existing user plugin file has been removed from the FoundUserPluginFiles list. 79 | /// 80 | public static event EventHandler UserPluginFileRemoved; 81 | internal static void OnUserPluginFileRemoved(UserPluginFileEventArgs e) 82 | { 83 | UserPluginFileRemoved?.Invoke(null, e); 84 | } 85 | 86 | /// 87 | /// Event raised immediately after an existing user plugin file has experienced a change, such as being saved. 88 | /// 89 | public static event EventHandler UserPluginFileChanged; 90 | internal static void OnUserPluginFileChanged(UserPluginFileEventArgs e) 91 | { 92 | UserPluginFileChanged?.Invoke(null, e); 93 | } 94 | 95 | /// 96 | /// Event args class for the renamed UserPluginFile event. 97 | /// 98 | public class UserPluginFileRenamedEventArgs : EventArgs 99 | { 100 | public string OldFilePath { get; private set; } 101 | public string NewFilePath { get; private set; } 102 | public PluginFile AffectedPluginFile { get; private set; } 103 | public UserPluginFileRenamedEventArgs(string oldPath, string newPath, PluginFile pluginFile) 104 | { 105 | OldFilePath = oldPath; 106 | NewFilePath = newPath; 107 | AffectedPluginFile = pluginFile; 108 | } 109 | } 110 | 111 | /// 112 | /// Event raised immediately after an existing user plugin file has been renamed. 113 | /// 114 | public static event EventHandler UserPluginFileRenamed; 115 | internal static void OnUserPluginFileRenamed(UserPluginFileRenamedEventArgs e) 116 | { 117 | UserPluginFileRenamed?.Invoke(null, e); 118 | } 119 | 120 | #endregion 121 | 122 | 123 | /// 124 | /// Sets up the bulk of the plugin management system and ensures all the necessary paths exist. 125 | /// 126 | /// Absolute path where Terraria Tweaker 2 stores its savedata. 127 | public static void Initialize(string tt2SavedataDirectory) 128 | { 129 | // Find root folder 130 | PluginsUserFilesFolder = Path.Combine(tt2SavedataDirectory, "Plugins"); // tt2SavedataPath should be %APPDATA%/Terraria Tweaker 2 131 | PluginsDataFolder = Path.Combine(tt2SavedataDirectory, "ttplugins"); 132 | 133 | // Create folders 134 | Directory.CreateDirectory(PluginsUserFilesFolder); 135 | Directory.CreateDirectory(PluginsDataFolder); 136 | Directory.CreateDirectory(PluginsTempFilesFolder); 137 | 138 | // Initial user file scan 139 | RescanAll(); 140 | 141 | // File system watcher(s) for the user files 142 | CreateFileSystemWatchers(); 143 | } 144 | 145 | 146 | /// 147 | /// Returns the PluginFile in FoundUserPluginFiles that has the provided relpath. 148 | /// 149 | /// The relpath identifier to use. 150 | /// The matched PluginFile, no null if none was found. 151 | public static PluginFile GetPluginFileByRelPath(string relpath) 152 | { 153 | foreach (PluginFile pluginFile in FoundUserPluginFiles) 154 | { 155 | if (pluginFile.GetRelativePath().ToLowerInvariant() == relpath.ToLowerInvariant()) 156 | { 157 | return pluginFile; 158 | } 159 | } 160 | return null; 161 | } 162 | 163 | 164 | #region File System Watchers 165 | 166 | /// 167 | /// Creates the FileSystemWatcher(s) that monitor the plugin user files and plugin data folder. 168 | /// 169 | private static void CreateFileSystemWatchers(bool recreateUserFilesWatcher = true) 170 | { 171 | if (recreateUserFilesWatcher) 172 | { 173 | FSWatcherUserFiles = new FileSystemWatcher(PluginsUserFilesFolder); 174 | FSWatcherUserFiles.NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastWrite; 175 | FSWatcherUserFiles.Changed += FSWatcherUserFiles_Changed; 176 | FSWatcherUserFiles.Created += FSWatcherUserFiles_Created; 177 | FSWatcherUserFiles.Deleted += FSWatcherUserFiles_Deleted; 178 | FSWatcherUserFiles.Renamed += FSWatcherUserFiles_Renamed; 179 | FSWatcherUserFiles.Error += FSWatcherUserFiles_Error; 180 | FSWatcherUserFiles.IncludeSubdirectories = true; 181 | FSWatcherUserFiles.EnableRaisingEvents = true; 182 | } 183 | } 184 | 185 | ///// FileSystemWatcher events for the plugin user files folder 186 | private static void FSWatcherUserFiles_Created(object sender, FileSystemEventArgs e) 187 | { 188 | if (Path.GetExtension(e.FullPath).ToLowerInvariant() == ".cs") 189 | TryAddUserFileCS(e.FullPath); 190 | 191 | if (Path.GetExtension(e.FullPath).ToLowerInvariant() == ".dll") 192 | TryAddUserFileDLL(e.FullPath); 193 | } 194 | private static void FSWatcherUserFiles_Deleted(object sender, FileSystemEventArgs e) 195 | { 196 | if (Path.GetExtension(e.FullPath).ToLowerInvariant() == ".cs" || Path.GetExtension(e.FullPath).ToLowerInvariant() == ".dll") 197 | TryRemoveUserFile(e.FullPath); 198 | else if (!Directory.Exists(e.FullPath)) // FileSystemWatcher is pretty crap and doesn't tell the difference between a file or folder event. So any time a 'thing' that isnt a .cs or .dll is deleted, we have to treat it like a directory in case there are files inside its path. 199 | TryRemoveUserFilesInDirectory(e.FullPath); 200 | } 201 | private static void FSWatcherUserFiles_Changed(object sender, FileSystemEventArgs e) 202 | { 203 | if (Path.GetExtension(e.FullPath).ToLowerInvariant() == ".cs" || Path.GetExtension(e.FullPath).ToLowerInvariant() == ".dll") 204 | TryUpdateUserFile(e.FullPath); 205 | } 206 | private static void FSWatcherUserFiles_Renamed(object sender, RenamedEventArgs e) 207 | { 208 | if (Path.GetExtension(e.OldFullPath).ToLowerInvariant() == ".cs" || Path.GetExtension(e.OldFullPath).ToLowerInvariant() == ".dll") 209 | TryRenameUserFile(e.OldFullPath, e.FullPath); 210 | else if (!Directory.Exists(e.OldFullPath) && Directory.Exists(e.FullPath)) // See comment in FSWatcherUserFiles_Deleted 211 | TryRenameUserFilesInDirectory(e.OldFullPath, e.FullPath); 212 | } 213 | private static void FSWatcherUserFiles_Error(object sender, ErrorEventArgs e) 214 | { 215 | // Try to recreate the file system watcher if it gets borked somehow 216 | CreateFileSystemWatchers(true); 217 | } 218 | 219 | #endregion 220 | 221 | 222 | #region File Processing 223 | 224 | /// 225 | /// Rescans all files in the PluginsUserFilesFolder folder for .cs source files and .dll compiled assemblies. 226 | /// 227 | public static void RescanAll() 228 | { 229 | FoundUserPluginFiles.Clear(); 230 | 231 | // Find CS source plugin files 232 | string[] filesA = Directory.GetFiles(PluginsUserFilesFolder, "*.cs", SearchOption.AllDirectories); 233 | foreach (string file in filesA) 234 | TryAddUserFileCS(file); 235 | 236 | // Find compiled assembly plugin files 237 | string[] filesB = Directory.GetFiles(PluginsUserFilesFolder, "*.dll", SearchOption.AllDirectories); 238 | foreach (string file in filesB) 239 | TryAddUserFileDLL(file); 240 | } 241 | 242 | 243 | /// 244 | /// Adds the specified .cs file to the FoundUserPluginFiles list if it is valid. If a plugin file already exists at the specified path, it will be replaced. 245 | /// 246 | /// The full path to the file. 247 | /// True if the plugin file was added, false if otherwise. 248 | private static bool TryAddUserFileCS(string path) 249 | { 250 | List existing = FoundUserPluginFiles.Where(x => PathsAreEqual(path, x.PathToFile)).ToList(); 251 | foreach (PluginFile pluginFile in existing) 252 | FoundUserPluginFiles.Remove(pluginFile); 253 | 254 | // In this case, there is no file contents checking (for now) 255 | PluginFile newPluginFile = new PluginFile(path, PluginFileType.CSSourceFile); 256 | FoundUserPluginFiles.Add(newPluginFile); 257 | OnUserPluginFileAdded(new UserPluginFileEventArgs(path, newPluginFile)); 258 | return true; 259 | } 260 | 261 | /// 262 | /// Adds the specified .dll file to the FoundUserPluginFiles list if it is a valid .net assembly. If a plugin file already exists at the specified path, it will be replaced. 263 | /// 264 | /// The full path to the file. 265 | /// True if the plugin file was added, false if otherwise. 266 | private static bool TryAddUserFileDLL(string path) 267 | { 268 | List existing = FoundUserPluginFiles.Where(x => PathsAreEqual(path, x.PathToFile)).ToList(); 269 | foreach (PluginFile pluginFile in existing) 270 | FoundUserPluginFiles.Remove(pluginFile); 271 | 272 | // Check if the DLL is a valid .NET assembly 273 | bool valid = false; 274 | try 275 | { 276 | AssemblyName asmName = AssemblyName.GetAssemblyName(path); 277 | valid = true; 278 | } 279 | catch (Exception e) { } 280 | 281 | if (valid) 282 | { 283 | PluginFile newPluginFile = new PluginFile(path, PluginFileType.CompiledAssemblyFile); 284 | FoundUserPluginFiles.Add(newPluginFile); 285 | OnUserPluginFileAdded(new UserPluginFileEventArgs(path, newPluginFile)); 286 | return true; 287 | } 288 | else 289 | return false; 290 | } 291 | 292 | /// 293 | /// Removes the specified file from the FoundUserPluginFiles list if it is valid. 294 | /// 295 | /// The full path to the file. 296 | /// True if the plugin file was removed, false if otherwise. 297 | private static bool TryRemoveUserFile(string path) 298 | { 299 | PluginFile toRemove = null; 300 | foreach (PluginFile pluginFile in FoundUserPluginFiles) 301 | { 302 | if (PathsAreEqual(path, pluginFile.PathToFile)) 303 | { 304 | toRemove = pluginFile; 305 | break; 306 | } 307 | } 308 | if (toRemove != null) 309 | { 310 | FoundUserPluginFiles.Remove(toRemove); 311 | OnUserPluginFileRemoved(new UserPluginFileEventArgs(toRemove.PathToFile, toRemove)); 312 | return true; 313 | } 314 | else 315 | return false; 316 | } 317 | 318 | /// 319 | /// Removes all files in the provided directory from the FoundUserPluginFiles list if they are valid. Necessary because FileSystemWatcher does not fire for the individual files that are deleted when their parent folder is deleted. 320 | /// 321 | /// The full path to the file. 322 | /// True if any plugin files were removed, false if otherwise. 323 | private static bool TryRemoveUserFilesInDirectory(string path) 324 | { 325 | List toRemove = new List(); 326 | foreach (PluginFile pluginFile in FoundUserPluginFiles) 327 | { 328 | if (IsPathInPath(pluginFile.PathToFile, path)) 329 | { 330 | toRemove.Add(pluginFile); 331 | } 332 | } 333 | 334 | bool removedAny = false; 335 | foreach (PluginFile pluginFile in toRemove) 336 | { 337 | FoundUserPluginFiles.Remove(pluginFile); 338 | removedAny = true; 339 | OnUserPluginFileRemoved(new UserPluginFileEventArgs(pluginFile.PathToFile, pluginFile)); 340 | } 341 | return removedAny; 342 | } 343 | 344 | /// 345 | /// Updates the PluginFile specified by the provided path if it exists in the FoundUserPluginFiles list. 346 | /// 347 | /// The full path to the file. 348 | /// True if the plugin existed and was updated, false if otherwise. 349 | private static bool TryUpdateUserFile(string path) 350 | { 351 | // Try to update existing user file first 352 | foreach (PluginFile pluginFile in FoundUserPluginFiles) 353 | { 354 | if (PathsAreEqual(path, pluginFile.PathToFile)) 355 | { 356 | pluginFile.UpdateFromFileChange(); 357 | OnUserPluginFileChanged(new UserPluginFileEventArgs(pluginFile.PathToFile, pluginFile)); 358 | return true; 359 | } 360 | } 361 | 362 | // If there is no existing user file, then we need to create a new PluginFile for this 363 | // (This is the result of the situation where a 0 byte file is created, e.g. "new.txt", which is not deteced by OnCreated; then it is changede to "new.cs" (also not deteced b/c still 0 bytes) and finally edited to be >0 bytes) 364 | string ext = Path.GetExtension(Path.GetFullPath(path)).ToLowerInvariant(); ; 365 | if (ext == ".cs") 366 | TryAddUserFileCS(path); 367 | else if (ext == ".dll") 368 | TryAddUserFileDLL(path); 369 | 370 | return false; 371 | } 372 | 373 | 374 | /// 375 | /// Updates the PluginFile specified by the provided path if it exists in the FoundUserPluginFiles list. 376 | /// 377 | /// The old file path. 378 | /// The new file path. 379 | /// True if a plugin was existing and updated/removed or if a new plugin was added; false if otherwise. 380 | private static bool TryRenameUserFile(string oldPath, string newPath) 381 | { 382 | string oldExtention = Path.GetExtension(Path.GetFullPath(oldPath)).ToLowerInvariant(); 383 | string newExtension = Path.GetExtension(Path.GetFullPath(newPath)).ToLowerInvariant(); 384 | 385 | // Check for an existing plugin being updated 386 | foreach (PluginFile pluginFile in FoundUserPluginFiles) 387 | { 388 | if (PathsAreEqual(oldPath, pluginFile.PathToFile)) 389 | { 390 | // If the extension did not change, then the plugin file is still valid 391 | if (oldExtention == newExtension) 392 | { 393 | pluginFile.UpdateFilePath(newPath); 394 | OnUserPluginFileRenamed(new UserPluginFileRenamedEventArgs(oldPath, newPath, pluginFile)); 395 | return true; 396 | } 397 | // If the extension switched from cs to dll or vice versa, then the plugin is still valid but needs updating too 398 | else if (newExtension == ".cs" || newExtension == ".dll") 399 | { 400 | // Validate dlls 401 | bool valid = false; 402 | if (newExtension == ".cs") 403 | valid = true; 404 | else if (newExtension == ".dll") 405 | { 406 | try 407 | { 408 | AssemblyName asmName = AssemblyName.GetAssemblyName(newPath); 409 | valid = true; 410 | } 411 | catch (Exception e) { } 412 | } 413 | 414 | if (valid) 415 | { 416 | pluginFile.UpdateFilePath(newPath); 417 | OnUserPluginFileRenamed(new UserPluginFileRenamedEventArgs(oldPath, newPath, pluginFile)); 418 | pluginFile.UpdateFromFileChange(); 419 | OnUserPluginFileChanged(new UserPluginFileEventArgs(pluginFile.PathToFile, pluginFile)); 420 | return true; 421 | } 422 | } 423 | 424 | // Otherwise, the extension is no longer .cs or .dll and so the plugin is no longer valid 425 | return TryRemoveUserFile(oldPath); 426 | } 427 | } 428 | 429 | // If not an existing plugin file, then check if this is a file that was just renamed to be a .cs or .dll and is thus now a plugin file 430 | if (newExtension == ".cs") 431 | return TryAddUserFileCS(newPath); 432 | else if (newExtension == ".dll") 433 | return TryAddUserFileDLL(newPath); 434 | 435 | return false; 436 | } 437 | 438 | /// 439 | /// Similar to TryRemoveUserFilesInDirectory, this renames all PluginFiles in the provided directory which was just renamed itself (thus changing the full path of its contents). 440 | /// 441 | /// The old path to directory. 442 | /// The new path to directory. 443 | /// True if any plugin files were renamed, false if otherwise. 444 | private static bool TryRenameUserFilesInDirectory(string oldPath, string newPath) 445 | { 446 | List toRename = new List(); 447 | foreach (PluginFile pluginFile in FoundUserPluginFiles) 448 | { 449 | if (IsPathInPath(pluginFile.PathToFile, oldPath)) 450 | { 451 | toRename.Add(pluginFile); 452 | } 453 | } 454 | 455 | bool renamedAny = false; 456 | foreach (PluginFile pluginFile in toRename) 457 | { 458 | // Derive the new path simply by replacing part of the path string 459 | string simulatedNewPath = pluginFile.PathToFile; 460 | int spot = simulatedNewPath.IndexOf(oldPath); 461 | if (spot >= 0) 462 | simulatedNewPath = simulatedNewPath.Substring(0, spot) + newPath + simulatedNewPath.Substring(spot + oldPath.Length); 463 | 464 | TryRenameUserFile(pluginFile.PathToFile, simulatedNewPath); 465 | renamedAny = true; 466 | } 467 | return renamedAny; 468 | } 469 | 470 | #endregion 471 | 472 | 473 | /// 474 | /// Tests all found PluginFiles against all security levels so as to determine the maximum security level that will allow each plugin to function. 475 | /// 476 | /// 477 | /// This method should only be used by management tools (i.e. TT2 and TTApp). 478 | /// 479 | /// Path to Terraria.exe, which will be referenced by CodeDom during compilation. 480 | /// List of Terraria.exe's embedded dependency assemblies, which will be temporarily written to disk and reference by CodeDom during compilation. 481 | /// Optional list of on-disk paths to additional assemblies that may be required for plugin compilation. 482 | /// A SecurityLevelComplianceTestResult object containing the test results. 483 | public static MultipleTestsResults TestAllSecurityLevelComplianceForAllPlugins(string terrariaPath, List terrariaDependencyAssemblies, List additionalCompileDependencies = null) 484 | { 485 | PluginTestConfiguration config = new PluginTestConfiguration(); 486 | config.PluginFilesToTest = new List(); 487 | config.PluginFilesToTest.AddRange(FoundUserPluginFiles); 488 | config.TerrariaEnvironment = TerrariaEnvironment.Offline; 489 | config.TerrariaPath = terrariaPath; 490 | config.TerrariaDependencyAssemblies = terrariaDependencyAssemblies; 491 | config.AdditionalCompileDependencies = additionalCompileDependencies; 492 | return CecilTests.TestPluginCompliance(config); 493 | } 494 | 495 | 496 | /// 497 | /// Returns the temporary folder path to use for reading and writing temporary, on-disk files relating to the specific PluginFile. 498 | /// 499 | /// The PluginFile whose PathToFile will be used. 500 | /// The path to the directory to use. The directory will NOT be created if it does not exist. 501 | public static string GetTemporaryFilePathFor(PluginFile pluginFile) 502 | { 503 | return Path.Combine(PluginsTempFilesFolder, pluginFile.GetRelativePath()); 504 | } 505 | 506 | /// 507 | /// Returns the relative path of provided file on the disk (relative to IO.PluginsUserFilesFolder). 508 | /// 509 | /// The full path to be transformed into a relative path. 510 | /// The relative path. 511 | public static string GetRelativeUserFilesPathFor(string fullPath) 512 | { 513 | string standardizedFullPath = Path.GetFullPath(fullPath).ToLowerInvariant(); 514 | string standardizedRootDir = Path.GetFullPath(PluginsUserFilesFolder).ToLowerInvariant(); 515 | int spot = standardizedFullPath.IndexOf(standardizedRootDir); 516 | if (spot >= 0) 517 | return (standardizedFullPath.Substring(0, spot) + standardizedFullPath.Substring(spot + standardizedRootDir.Length)).TrimStart('\\', '/'); 518 | else 519 | return null; // Shouldn't happen 520 | } 521 | 522 | 523 | #region Helpers 524 | 525 | /// 526 | /// Standardizes paths into absolute paths with identical format before comparing them. 527 | /// 528 | /// The first path to compare. 529 | /// The second path to compare. 530 | /// True if the paths are considered equivalent, false if otherwise. 531 | private static bool PathsAreEqual(string pathA, string pathB) 532 | { 533 | return (Path.GetFullPath(pathA).ToLowerInvariant() == Path.GetFullPath(pathB).ToLowerInvariant()); // This isn't 100% correct on Linux, but I'm only targeting Windows 534 | } 535 | 536 | /// 537 | /// Checks if a deeper path is part of a shallower path, using standardized, absolute paths for comparison. 538 | /// 539 | /// The deeper path, i.e. C:\Users\MyUsername\SomeFile.txt 540 | /// The shallower path, i.e. C:\Users\ 541 | /// True if the the deeper path path is contained by the shallower path, false if otherwise 542 | private static bool IsPathInPath(string deeperPath, string shallowerPath) 543 | { 544 | string deeperPathStandardized = Path.GetFullPath(deeperPath).ToLowerInvariant(); 545 | string shallowerPathStandardized = Path.GetFullPath(shallowerPath).ToLowerInvariant(); 546 | return (deeperPath.IndexOf(shallowerPath) == 0); 547 | } 548 | 549 | #endregion 550 | } 551 | } 552 | --------------------------------------------------------------------------------