├── 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 |
--------------------------------------------------------------------------------