From 3906bf40eb850e858a41e0371d403718cc689675 Mon Sep 17 00:00:00 2001 From: wannkunstbeikor <93538252+wannkunstbeikor@users.noreply.github.com> Date: Sat, 25 Mar 2023 22:18:08 +0100 Subject: [PATCH] [Plugins] Extracts DifficultyWeaponTableData from test plugin --- FrostyEditor/FrostyEditor.sln | 17 +- .../Controls/DifficultyWeaponTableEditor.cs | 70 ++++++++ ...ifficultyWeaponTableDataAssetDefinition.cs | 31 ++++ .../DifficultyWeaponTableDataPlugin.csproj | 66 ++++++++ .../DifficultyWeaponTableActionHandler.cs | 150 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 32 ++++ .../Resources/DifficultyWeaponTableData.cs | 114 +++++++++++++ .../Themes/Generic.xaml | 33 ++++ Plugins/TestPlugin/Properties/AssemblyInfo.cs | 4 +- 9 files changed, 513 insertions(+), 4 deletions(-) create mode 100644 Plugins/DifficultyWeaponTableDataPlugin/Controls/DifficultyWeaponTableEditor.cs create mode 100644 Plugins/DifficultyWeaponTableDataPlugin/Definitions/DifficultyWeaponTableDataAssetDefinition.cs create mode 100644 Plugins/DifficultyWeaponTableDataPlugin/DifficultyWeaponTableDataPlugin.csproj create mode 100644 Plugins/DifficultyWeaponTableDataPlugin/Handlers/DifficultyWeaponTableActionHandler.cs create mode 100644 Plugins/DifficultyWeaponTableDataPlugin/Properties/AssemblyInfo.cs create mode 100644 Plugins/DifficultyWeaponTableDataPlugin/Resources/DifficultyWeaponTableData.cs create mode 100644 Plugins/DifficultyWeaponTableDataPlugin/Themes/Generic.xaml diff --git a/FrostyEditor/FrostyEditor.sln b/FrostyEditor/FrostyEditor.sln index 7fb2d860f..bf8fc8d59 100644 --- a/FrostyEditor/FrostyEditor.sln +++ b/FrostyEditor/FrostyEditor.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29911.84 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33318.248 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostyEditor", "FrostyEditor.csproj", "{355B078B-45A5-472D-9CCD-688D8E30BAED}" ProjectSection(ProjectDependencies) = postProject @@ -93,6 +93,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LaunchPlatformPlugin", "..\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostyCmd", "..\FrostyCmd\FrostyCmd.csproj", "{500ACAB7-D948-4BCA-8208-20812D712DDB}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DifficultyWeaponTableDataPlugin", "..\Plugins\DifficultyWeaponTableDataPlugin\DifficultyWeaponTableDataPlugin.csproj", "{F49314ED-C795-4A39-9748-68D936B35D2C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Developer - Debug|x64 = Developer - Debug|x64 @@ -512,6 +514,16 @@ Global {500ACAB7-D948-4BCA-8208-20812D712DDB}.Release - Final|x64.Build.0 = Release - Final|x64 {500ACAB7-D948-4BCA-8208-20812D712DDB}.Release|x64.ActiveCfg = Release - Final|x64 {500ACAB7-D948-4BCA-8208-20812D712DDB}.Release|x64.Build.0 = Release - Final|x64 + {F49314ED-C795-4A39-9748-68D936B35D2C}.Developer - Debug|x64.ActiveCfg = Developer - Debug|x64 + {F49314ED-C795-4A39-9748-68D936B35D2C}.Developer - Debug|x64.Build.0 = Developer - Debug|x64 + {F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Alpha|x64.ActiveCfg = Release - Alpha|x64 + {F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Alpha|x64.Build.0 = Release - Alpha|x64 + {F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Beta|x64.ActiveCfg = Release - Beta|x64 + {F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Beta|x64.Build.0 = Release - Beta|x64 + {F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Final|x64.ActiveCfg = Release - Final|x64 + {F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Final|x64.Build.0 = Release - Final|x64 + {F49314ED-C795-4A39-9748-68D936B35D2C}.Release|x64.ActiveCfg = Release - Alpha|x64 + {F49314ED-C795-4A39-9748-68D936B35D2C}.Release|x64.Build.0 = Release - Alpha|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -550,6 +562,7 @@ Global {B1FE3166-BBD2-4112-8618-1C5587E32CC4} = {492048B6-848D-4675-A9BF-34A4751303F2} {FE2D780C-8FBC-4797-9ACD-05DE2507F1AA} = {492048B6-848D-4675-A9BF-34A4751303F2} {7F280BF3-F794-4715-9D32-770D36454B8C} = {492048B6-848D-4675-A9BF-34A4751303F2} + {F49314ED-C795-4A39-9748-68D936B35D2C} = {492048B6-848D-4675-A9BF-34A4751303F2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C0B22EF1-4C3F-45B9-9F08-9793ED912199} diff --git a/Plugins/DifficultyWeaponTableDataPlugin/Controls/DifficultyWeaponTableEditor.cs b/Plugins/DifficultyWeaponTableDataPlugin/Controls/DifficultyWeaponTableEditor.cs new file mode 100644 index 000000000..26dd1cca3 --- /dev/null +++ b/Plugins/DifficultyWeaponTableDataPlugin/Controls/DifficultyWeaponTableEditor.cs @@ -0,0 +1,70 @@ +using DifficultyWeaponTableDataPlugin.Resources; +using Frosty.Core; +using Frosty.Core.Controls; +using FrostySdk.Interfaces; +using FrostySdk.IO; +using FrostySdk.Managers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace DifficultyWeaponTableDataPlugin.Controls +{ + // Classes that derive from FrostyAssetEditor are used to edit assets specifically, they have a lot of boiler plate + // code for loading assets and their dependencies. + + // This editor is used to edit assets of type DifficultyWeaponTableData, it is instantiated from the GetEditor + // function of the corresponding AssetDefinition + [TemplatePart(Name = "PART_AssetPropertyGrid", Type = typeof(FrostyPropertyGrid))] + public class DifficultyWeaponTableEditor : FrostyAssetEditor + { + private FrostyPropertyGrid propertyGrid; + + static DifficultyWeaponTableEditor() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DifficultyWeaponTableEditor), new FrameworkPropertyMetadata(typeof(DifficultyWeaponTableEditor))); + } + + public DifficultyWeaponTableEditor(ILogger inLogger) + : base(inLogger) + { + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + propertyGrid = GetTemplateChild("PART_AssetPropertyGrid") as FrostyPropertyGrid; + propertyGrid.OnModified += PropertyGrid_OnModified; + } + + // This function is used to override the editors load function to ensure that the asset is loaded + // with a specific subclass of EbxAsset + protected override EbxAsset LoadAsset(EbxAssetEntry entry) + { + DifficultyWeaponTableData loadedAsset = App.AssetManager.GetEbxAs(entry); + return loadedAsset; + } + + // Functionality to perform when a property in the property grid is modified, in this case + // the modified binding has been removed, as the editor will handle the modified data specifically + // and then invoke the OnAssetModified function manually + private void PropertyGrid_OnModified(object sender, ItemModifiedEventArgs e) + { + // obtain the row and column of the edit via the item property paths + string colIndex = e.Item.Parent.Name.Trim('[', ']'); + string rowIndex = e.Item.Parent.Parent.Parent.Name.Trim('[', ']'); + + // add or modify the value on the asset + DifficultyWeaponTableData assetData = asset as DifficultyWeaponTableData; + assetData.ModifyValue(int.Parse(rowIndex), int.Parse(colIndex), (float)e.NewValue); + + // invoke the OnAssetModified function + App.AssetManager.ModifyEbx(AssetEntry.Name, assetData); + InvokeOnAssetModified(); + } + } +} diff --git a/Plugins/DifficultyWeaponTableDataPlugin/Definitions/DifficultyWeaponTableDataAssetDefinition.cs b/Plugins/DifficultyWeaponTableDataPlugin/Definitions/DifficultyWeaponTableDataAssetDefinition.cs new file mode 100644 index 000000000..f9a4b8857 --- /dev/null +++ b/Plugins/DifficultyWeaponTableDataPlugin/Definitions/DifficultyWeaponTableDataAssetDefinition.cs @@ -0,0 +1,31 @@ +using DifficultyWeaponTableDataPlugin.Controls; +using Frosty.Core; +using Frosty.Core.Controls; +using FrostySdk.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; + +namespace DifficultyWeaponTableDataPlugin.Definitions +{ + // This asset defintion is about as simple as they come. It defines a generic icon that will be used in all cases + // for all instances of the registered asset type. And provides its own custom editor. + + public class DifficultyWeaponTableDataAssetDefinition : AssetDefinition + { + protected static ImageSource imageSource = new ImageSourceConverter().ConvertFromString("pack://application:,,,/TestPlugin;component/Images/SpreadsheetFileType.png") as ImageSource; + + public override ImageSource GetIcon() + { + return imageSource; + } + + public override FrostyAssetEditor GetEditor(ILogger logger) + { + return new DifficultyWeaponTableEditor(logger); + } + } +} diff --git a/Plugins/DifficultyWeaponTableDataPlugin/DifficultyWeaponTableDataPlugin.csproj b/Plugins/DifficultyWeaponTableDataPlugin/DifficultyWeaponTableDataPlugin.csproj new file mode 100644 index 000000000..27ed739ba --- /dev/null +++ b/Plugins/DifficultyWeaponTableDataPlugin/DifficultyWeaponTableDataPlugin.csproj @@ -0,0 +1,66 @@ + + + Developer - Debug;Release - Alpha;Release - Beta;Release - Final + x64 + net48 + DifficultyWeaponTableDataPlugin + DifficultyWeaponTableDataPlugin + Copyright © 2020 + MinimumRecommendedRules.ruleset + false + true + Library + + + + true + bin\Developer\Debug\ + DEBUG;TRACE + + + + bin\Release\Alpha\ + TRACE + true + + + + bin\Release\Beta\ + TRACE + true + + + + bin\Release\Final\ + TRACE + true + + + + + + + + + + + + false + + + false + + + false + + + false + + + + + + + + + \ No newline at end of file diff --git a/Plugins/DifficultyWeaponTableDataPlugin/Handlers/DifficultyWeaponTableActionHandler.cs b/Plugins/DifficultyWeaponTableDataPlugin/Handlers/DifficultyWeaponTableActionHandler.cs new file mode 100644 index 000000000..1620a5506 --- /dev/null +++ b/Plugins/DifficultyWeaponTableDataPlugin/Handlers/DifficultyWeaponTableActionHandler.cs @@ -0,0 +1,150 @@ +using DifficultyWeaponTableDataPlugin.Resources; +using Frosty.Core.IO; +using Frosty.Core.Mod; +using Frosty.Hash; +using FrostySdk; +using FrostySdk.IO; +using FrostySdk.Managers; +using FrostySdk.Resources; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DifficultyWeaponTableDataPlugin.Handlers +{ + public class DifficultyWeaponTableActionHandler : ICustomActionHandler + { + // This is purely for the mod managers action view and has no impact on how the handler actually executes. + // It tells the mod manager actions view what type of action this handler performs, wether it replaces (Modify) + // data from one mod with another, or does it merge the two together. + public HandlerUsage Usage => HandlerUsage.Merge; + + // A mod is comprised of a series of base resources, embedded, ebx, res, and chunks. Embedded are used internally + // for the icon and images of a mod. Ebx/Res/Chunks are the core resources used for applying data to the game. + // When you create a custom handler, you need to provide your own resources for your custom handled data. This + // resource is unique however it is based on one of the three core types. + private class DifficultyWeaponTableResource : EditorModResource + { + // Defines which type of resource this resource is. + public override ModResourceType Type => ModResourceType.Ebx; + + // Creates a new resource of the specified type and adds its data to the mod manifest. + public DifficultyWeaponTableResource(EbxAssetEntry entry, FrostyModWriter.Manifest manifest) + : base(entry) + { + // obtain the modified data + ModifiedResource md = entry.ModifiedEntry.DataObject as ModifiedResource; + byte[] data = md.Save(); + + // store data and details about resource + name = entry.Name.ToLower(); + sha1 = Utils.GenerateSha1(data); + resourceIndex = manifest.Add(sha1, data); + size = data.Length; + + // set the handler hash to the hash of the ebx type name + handlerHash = Fnv1.HashString(entry.Type.ToLower()); + } + } + + // The below functions are specific to the editor, it is used to save the modified data to a mod. + + #region -- Editor Specific -- + + // This function is for writing resources to the mod file, this is where you would add your custom + // resources to be written. + public void SaveToMod(FrostyModWriter writer, AssetEntry entry) + { + writer.AddResource(new DifficultyWeaponTableResource(entry as EbxAssetEntry, writer.ResourceManifest)); + } + + #endregion + + // The below functions are specific to the mod manager, it revolves around loading and potentially merging + // of the data loaded from a mod. + + #region -- Mod Specific -- + + // This function is for the mod managers action view, to allow a handler to describe detailed actions performed + // format of the action string is ;; where action can be Modify or Merge + // and ResourceType can be Ebx,Res,Chunk @todo + public IEnumerable GetResourceActions(string name, byte[] data) + { + var newTable = ModifiedResource.Read(data) as ModifiedDifficultyWeaponTableData; + List resourceActions = new List(); + + foreach (var value in newTable.Values) + { + string resourceName = name + " (Row: " + value.Row + "/Col: " + value.Column + ")"; + string resourceType = "ebx"; + string action = "Modify"; + + resourceActions.Add(resourceName + ";" + resourceType + ";" + action); + } + + return resourceActions; + } + + // This function is invoked when a mod with such a handler is loaded, if a previous mod with a handler for this + // particular asset was loaded previously, then existing will be populated with that data, allowing this function + // the chance to merge the two datasets together + public object Load(object existing, byte[] newData) + { + // load the existing modified data (from any previous mods) + var oldTable = (ModifiedDifficultyWeaponTableData)existing; + + // load the new modified data from the current mod + var newTable = ModifiedResource.Read(newData) as ModifiedDifficultyWeaponTableData; + + // return the new data if there was no previous data + if (oldTable == null) + return newTable; + + // otherwise merge the two together + foreach (var value in newTable.Values) + { + // each change is stored as a row/column/value set, when merged with another mod, each individual + // row/column change is merged with the previous changes + oldTable.ModifyValue(value.Row, value.Column, value.Value); + } + + return oldTable; + } + + // This function is invoked at the end of the mod loading, to actually modify the existing game data with the end + // result of the mod loaded data, it also allows for a handler to add new Resources to be replaced. + // ie. an Ebx handler might want to add a new Chunk resource that it is dependent on. + public void Modify(AssetEntry origEntry, AssetManager am, RuntimeResources runtimeResources, object data, out byte[] outData) + { + // obtain the modified data that has been loaded and merged from the mods + ModifiedDifficultyWeaponTableData modifiedData = data as ModifiedDifficultyWeaponTableData; + + // load the original game ebx asset + EbxAsset asset = am.GetEbx(am.GetEbxEntry(origEntry.Name)); + dynamic rootTable = asset.RootObject; + + // replace data from the game ebx data with the modified data + foreach (var value in modifiedData.Values) + { + rootTable.WeaponTable[value.Row].Values[value.Column].Value = value.Value; + } + + // write out the new ebx data + using (EbxBaseWriter writer = EbxBaseWriter.CreateWriter(new MemoryStream())) + { + writer.WriteAsset(asset); + origEntry.OriginalSize = writer.Length; + outData = Utils.CompressFile(writer.ToByteArray()); + } + + // update relevant asset entry values + origEntry.Size = outData.Length; + origEntry.Sha1 = Utils.GenerateSha1(outData); + } + + #endregion + } +} diff --git a/Plugins/DifficultyWeaponTableDataPlugin/Properties/AssemblyInfo.cs b/Plugins/DifficultyWeaponTableDataPlugin/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..ca6c8ba56 --- /dev/null +++ b/Plugins/DifficultyWeaponTableDataPlugin/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using DifficultyWeaponTableDataPlugin.Definitions; +using DifficultyWeaponTableDataPlugin.Handlers; +using Frosty.Core.Attributes; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4b612468-9b6a-4304-88a5-055c3575eb3d")] + +[assembly: PluginDisplayName("DifficultyWeaponTableDataPlugin")] +[assembly: PluginAuthor("GalaxyMan2015")] +[assembly: PluginVersion("1.0.0.0")] + +[assembly: RegisterAssetDefinition("DifficultyWeaponTableData", typeof(DifficultyWeaponTableDataAssetDefinition))] +[assembly: RegisterCustomHandler(CustomHandlerType.Ebx, typeof(DifficultyWeaponTableActionHandler), ebxType: "DifficultyWeaponTableData")] + diff --git a/Plugins/DifficultyWeaponTableDataPlugin/Resources/DifficultyWeaponTableData.cs b/Plugins/DifficultyWeaponTableDataPlugin/Resources/DifficultyWeaponTableData.cs new file mode 100644 index 000000000..1d7de9396 --- /dev/null +++ b/Plugins/DifficultyWeaponTableDataPlugin/Resources/DifficultyWeaponTableData.cs @@ -0,0 +1,114 @@ +using FrostySdk.IO; +using FrostySdk.Resources; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DifficultyWeaponTableDataPlugin.Resources +{ + // Classes that derive from EbxAsset are used to specify any custom handling of ebx related data, through the use of a + // specialized ModifiedResource object. Refer to App.AssetManager.GetEbxAs to obtain the ebx data + public class DifficultyWeaponTableData : EbxAsset + { + // holds the modified data for this ebx asset + private ModifiedDifficultyWeaponTableData modified; + + // invoked during asset load to give the asset a chance to perform any logic required + // when loading the modified data + public override void ApplyModifiedResource(ModifiedResource modifiedResource) + { + // obtains the modified data + modified = modifiedResource as ModifiedDifficultyWeaponTableData; + + // applies the modified data to the ebx data + dynamic rootTable = RootObject; + foreach (var value in modified.Values) + { + rootTable.WeaponTable[value.Row].Values[value.Column].Value = value.Value; + } + } + + // invoked during asset save to save the modified data + public override ModifiedResource SaveModifiedResource() + { + return modified; + } + + public void ModifyValue(int row, int column, float value) + { + // create a new modified object if necessary + if (modified == null) + modified = new ModifiedDifficultyWeaponTableData(); + + // pass call over to modified object + modified.ModifyValue(row, column, value); + } + } + + // ModifiedResource and any class that derives from it are the classes used when it is required to store + // data in a serializable fashion as opposed to a regular ebx/res byte array, it provides its own Read/Write + // functions, and allows for the user to store any data they see fit, as long as they can apply it onto + // an asset/resource. + public class ModifiedDifficultyWeaponTableData : ModifiedResource + { + public IEnumerable Values => values; + + // stores modifications as a series of row/column/value sets + private List values = new List(); + + public void ModifyValue(int row, int column, float value) + { + // check to see if row/column set already exists + int index = values.FindIndex((ColumnRowValue crv) => crv.Row == row && crv.Column == column); + if (index == -1) + { + // if not, then create it + values.Add(new ColumnRowValue() { Column = column, Row = row }); + index = values.Count - 1; + } + + values[index].Value = value; + } + + // This function is responsible for reading in the modified data from the project file + public override void ReadInternal(NativeReader reader) + { + int count = reader.ReadInt(); + for (int i = 0; i < count; i++) + { + ColumnRowValue crv = new ColumnRowValue + { + Row = reader.ReadInt(), + Column = reader.ReadInt(), + Value = reader.ReadFloat() + }; + values.Add(crv); + } + } + + // This function is responsible for writing out the modified data to the project file + public override void SaveInternal(NativeWriter writer) + { + writer.Write(values.Count); + foreach (var value in values) + { + writer.Write(value.Row); + writer.Write(value.Column); + writer.Write(value.Value); + } + } + } + + #region -- DifficultyWeaponTableData objects -- + + public class ColumnRowValue + { + public int Column; + public int Row; + public float Value; + } + + #endregion +} diff --git a/Plugins/DifficultyWeaponTableDataPlugin/Themes/Generic.xaml b/Plugins/DifficultyWeaponTableDataPlugin/Themes/Generic.xaml new file mode 100644 index 000000000..e5342a0a2 --- /dev/null +++ b/Plugins/DifficultyWeaponTableDataPlugin/Themes/Generic.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Plugins/TestPlugin/Properties/AssemblyInfo.cs b/Plugins/TestPlugin/Properties/AssemblyInfo.cs index 43968abf5..3c6d5e53c 100644 --- a/Plugins/TestPlugin/Properties/AssemblyInfo.cs +++ b/Plugins/TestPlugin/Properties/AssemblyInfo.cs @@ -83,7 +83,7 @@ // will be used, as well as specify import/export logic. //[assembly: RegisterAssetDefinition("SvgImage", typeof(SvgImageAssetDefinition))] -[assembly: RegisterAssetDefinition("DifficultyWeaponTableData", typeof(DifficultyWeaponTableDataAssetDefinition))] +//[assembly: RegisterAssetDefinition("DifficultyWeaponTableData", typeof(DifficultyWeaponTableDataAssetDefinition))] // Custom handlers are a way to override the way data is handled for a specific type. // There are different handlers for Res and Ebx types, used in conjunction with ModifiedResource @@ -91,7 +91,7 @@ // different mods from the mod manager. //[assembly: RegisterCustomHandler(CustomHandlerType.Ebx, typeof(SvgImageCustomActionHandler), ebxType: "SvgImage")] -[assembly: RegisterCustomHandler(CustomHandlerType.Ebx, typeof(DifficultyWeaponTableActionHandler), ebxType: "DifficultyWeaponTableData")] +//[assembly: RegisterCustomHandler(CustomHandlerType.Ebx, typeof(DifficultyWeaponTableActionHandler), ebxType: "DifficultyWeaponTableData")] //[assembly: RegisterCustomHandler(CustomHandlerType.Ebx, typeof(NetworkRegistryActionHandler), ebxType: "NetworkRegistryAsset")] // Editor executions allow you to run custom code prior or after the mod applying process.