├── README.md ├── Source └── BPCorruptionFix │ ├── Public │ └── BPCorruptionFix.h │ ├── BPCorruptionFix.Build.cs │ └── Private │ └── BPCorruptionFix.cpp └── BPCorruptionFix.uplugin /README.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Simply clone the repo into your project's Plugins directory. 4 | This plugin only works for UE5 projects. 5 | 6 | # Usage 7 | 8 | Please read the Wiki here: https://github.com/rweber89/BPCorruptionFix/wiki 9 | 10 | # Contributions 11 | https://github.com/Zubius - fixing a compile issue 12 | https://github.com/Nimaoth - providing UE4 version (not pushed yet) 13 | -------------------------------------------------------------------------------- /Source/BPCorruptionFix/Public/BPCorruptionFix.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | class FBPCorruptionFixModule : public IModuleInterface 6 | { 7 | public: 8 | void StartupModule() override; 9 | void ShutdownModule() override; 10 | 11 | private: 12 | void RegisterMenus(); 13 | 14 | void RegisterCopyAction( FToolMenuSection& Section ); 15 | void RegisterPasteAction( FToolMenuSection& Section ); 16 | }; 17 | -------------------------------------------------------------------------------- /BPCorruptionFix.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 1, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "BPCorruptionFix", 6 | "Description": "", 7 | "Category": "", 8 | "CreatedBy": "", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": false, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "BPCorruptionFix", 20 | "Type": "UncookedOnly", 21 | "LoadingPhase": "PostEngineInit" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /Source/BPCorruptionFix/BPCorruptionFix.Build.cs: -------------------------------------------------------------------------------- 1 | using UnrealBuildTool; 2 | 3 | public class BPCorruptionFix : ModuleRules 4 | { 5 | public BPCorruptionFix(ReadOnlyTargetRules Target) : base(Target) 6 | { 7 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 8 | 9 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); 10 | 11 | PrivateDependencyModuleNames.AddRange(new string[] { "UnrealEd", "EditorStyle", "PropertyEditor", "Slate", "SlateCore", "SubobjectEditor", "SubobjectDataInterface", "ToolMenus", "Kismet" }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Source/BPCorruptionFix/Private/BPCorruptionFix.cpp: -------------------------------------------------------------------------------- 1 | #include "BPCorruptionFix.h" 2 | 3 | #include "ObjectEditorUtils.h" 4 | #include "ToolMenus.h" 5 | #include "SSubobjectEditor.h" 6 | #include "SSubobjectInstanceEditor.h" 7 | #include "SubobjectEditorMenuContext.h" 8 | #include "BlueprintEditor.h" 9 | #include "Kismet2/BlueprintEditorUtils.h" 10 | #include "Serialization/BufferWriter.h" 11 | 12 | #define LOCTEXT_NAMESPACE "BPCorruptionFix" 13 | 14 | const UObject* g_ToCopy = nullptr; 15 | const FObjectProperty* g_PropToCopy = nullptr; 16 | 17 | namespace 18 | { 19 | bool GetSubobjectEditorFromContext( const FToolMenuContext& InContext, TSharedPtr& SubobjectEditor ) 20 | { 21 | USubobjectEditorMenuContext* ContextObject = InContext.FindContext(); 22 | if( !ContextObject ) 23 | { 24 | return false; 25 | } 26 | 27 | SubobjectEditor = ContextObject->SubobjectEditor.Pin(); 28 | if ( !SubobjectEditor.IsValid() || !StaticCastSharedPtr(SubobjectEditor)) 29 | { 30 | return false; 31 | } 32 | 33 | return true; 34 | } 35 | 36 | FObjectProperty* GetPropertyForNode(FSubobjectEditorTreeNodePtrType Node) 37 | { 38 | UObject* ComponentOuter = Node->GetObject()->GetOuter(); 39 | UClass* OwnerClass = ComponentOuter->GetClass(); 40 | 41 | for (TFieldIterator It(OwnerClass); It; ++It) 42 | { 43 | FObjectProperty* ObjectProp = *It; 44 | 45 | // Must be visible - note CPF_Edit is set for all properties that should be visible, not just those that are editable 46 | if ((ObjectProp->PropertyFlags & (CPF_Edit)) == 0) 47 | { 48 | //UE_LOG(LogTemp, Error, TEXT("NotEditable: %s"), *ObjectProp->GetName()); 49 | continue; 50 | } 51 | 52 | if (Node->GetVariableName().ToString() == ObjectProp->GetName()) 53 | { 54 | return ObjectProp; 55 | } 56 | 57 | // NOTE [RW] keep here for posterity. If ever built out to automatically detect, this would be how, whilst iterating over the properties 58 | // if( !ObjectProp->GetObjectPropertyValue(ObjectProp->ContainerPtrToValuePtr(ComponentOuter)) ) 59 | // { Code here to signal detected broken property } 60 | } 61 | 62 | return nullptr; 63 | } 64 | 65 | bool GetEditorsFromContext( const FToolMenuContext& InContext, TSharedPtr& SubobjectEditor, TSharedPtr& Editor ) 66 | { 67 | if( !GetSubobjectEditorFromContext( InContext, SubobjectEditor ) ) 68 | { 69 | return false; 70 | } 71 | 72 | const auto& BlueprintEditorModule = FModuleManager::LoadModuleChecked("Kismet"); 73 | for( const auto& OpenEditor : BlueprintEditorModule.GetBlueprintEditors() ) 74 | { 75 | const TSharedRef& OpenBlueprintEditor = StaticCastSharedRef(OpenEditor); 76 | if( OpenBlueprintEditor->GetSubobjectEditor() == SubobjectEditor ) 77 | { 78 | Editor = OpenBlueprintEditor; 79 | return true; 80 | } 81 | } 82 | 83 | return false; 84 | } 85 | 86 | auto GetIsVisibleLambda() 87 | { 88 | return FToolMenuIsActionButtonVisible::CreateLambda([](const FToolMenuContext& InContext) 89 | { 90 | TSharedPtr SubobjectEditor; 91 | if( !GetSubobjectEditorFromContext( InContext, SubobjectEditor ) ) 92 | { 93 | return false; 94 | } 95 | 96 | return SubobjectEditor->GetSelectedNodes().Num() == 1; 97 | }); 98 | } 99 | } 100 | 101 | void FBPCorruptionFixModule::StartupModule() 102 | { 103 | RegisterMenus(); 104 | } 105 | 106 | void FBPCorruptionFixModule::ShutdownModule() 107 | { 108 | } 109 | 110 | void FBPCorruptionFixModule::RegisterMenus() 111 | { 112 | FToolMenuOwnerScoped OwnerScoped(this); 113 | 114 | UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("Kismet.SubobjectEditorContextMenu"); 115 | FToolMenuSection& Section = Menu->AddSection("BPCorruptionFixctions", LOCTEXT("BPCorruptionFixActionsHeader", "BPCorruptionFix")); 116 | 117 | RegisterCopyAction( Section ); 118 | RegisterPasteAction( Section ); 119 | } 120 | 121 | void FBPCorruptionFixModule::RegisterCopyAction(FToolMenuSection& Section) 122 | { 123 | FToolUIAction Action; 124 | Action.CanExecuteAction = FToolMenuCanExecuteAction::CreateLambda([&](const FToolMenuContext& InContext) 125 | { 126 | TSharedPtr SubobjectEditor; 127 | if (!GetSubobjectEditorFromContext(InContext, SubobjectEditor)) 128 | { 129 | return false; 130 | } 131 | 132 | const auto Node = SubobjectEditor->GetSelectedNodes()[0]; 133 | const auto* TargetProperty = GetPropertyForNode(Node); 134 | if (!TargetProperty) 135 | { 136 | return false; 137 | } 138 | 139 | // Make sure to not copy a broken property 140 | if (!TargetProperty->GetObjectPropertyValue( 141 | TargetProperty->ContainerPtrToValuePtr(Node->GetObject()->GetOuter()))) 142 | { 143 | return false; 144 | } 145 | 146 | return true; 147 | }); 148 | 149 | Action.ExecuteAction = FToolMenuExecuteAction::CreateLambda([&](const FToolMenuContext& InContext) 150 | { 151 | TSharedPtr SubobjectEditor; 152 | TSharedPtr Editor; 153 | if( !GetEditorsFromContext( InContext, SubobjectEditor, Editor ) ) 154 | { 155 | return; 156 | } 157 | 158 | const auto Node = SubobjectEditor->GetSelectedNodes()[0]; 159 | const auto* TargetProperty = GetPropertyForNode( Node ); 160 | if( !TargetProperty ) 161 | { 162 | return; 163 | } 164 | 165 | g_PropToCopy = TargetProperty; 166 | g_ToCopy = Node->GetObject()->GetOuter(); 167 | }); 168 | 169 | Action.IsActionVisibleDelegate = GetIsVisibleLambda(); 170 | 171 | FToolMenuEntry& Entry = Section.AddMenuEntry( 172 | "CopyIntactProperties", 173 | LOCTEXT( "CopyIntactLabel", "Copy Intact Subobject"), 174 | LOCTEXT( "CopyAllTooltip", "Copies the intact suboject so that it may be pasted onto a broken one"), 175 | FSlateIcon(), 176 | Action, 177 | EUserInterfaceActionType::Button); 178 | } 179 | 180 | void FBPCorruptionFixModule::RegisterPasteAction(FToolMenuSection& Section) 181 | { 182 | FToolUIAction Action; 183 | Action.CanExecuteAction = FToolMenuCanExecuteAction::CreateLambda([&](const FToolMenuContext& InContext) 184 | { 185 | if( !g_ToCopy ) 186 | { 187 | return false; 188 | } 189 | 190 | TSharedPtr SubobjectEditor; 191 | if( !GetSubobjectEditorFromContext( InContext, SubobjectEditor ) ) 192 | { 193 | return false; 194 | } 195 | 196 | const auto Node = SubobjectEditor->GetSelectedNodes()[0]; 197 | const auto* TargetProperty = GetPropertyForNode( Node ); 198 | if( !TargetProperty ) 199 | { 200 | return false; 201 | } 202 | 203 | // Make sure that we never try to copy across different classes 204 | if( g_PropToCopy->PropertyClass != TargetProperty->PropertyClass ) 205 | { 206 | return false; 207 | } 208 | 209 | // The names need to match to make sure we copy the right data 210 | if( g_PropToCopy->GetName() != TargetProperty->GetName() ) 211 | { 212 | return false; 213 | } 214 | 215 | // Only allow fixing broken properties 216 | if( TargetProperty->GetObjectPropertyValue(TargetProperty->ContainerPtrToValuePtr(Node->GetObject()->GetOuter())) ) 217 | { 218 | return false; 219 | } 220 | 221 | return true; 222 | }); 223 | 224 | Action.ExecuteAction = FToolMenuExecuteAction::CreateLambda([&](const FToolMenuContext& InContext) 225 | { 226 | TSharedPtr SubobjectEditor; 227 | TSharedPtr Editor; 228 | if( !GetEditorsFromContext( InContext, SubobjectEditor, Editor ) ) 229 | { 230 | return; 231 | } 232 | 233 | const auto Node = SubobjectEditor->GetSelectedNodes()[0]; 234 | 235 | // Copy from the intact to the broken memory 236 | const uint8* SourcePtr = g_PropToCopy->ContainerPtrToValuePtr(g_ToCopy); 237 | uint8* DestPtr = g_PropToCopy->ContainerPtrToValuePtr(Node->GetObject()->GetOuter()); 238 | g_PropToCopy->CopyCompleteValue(DestPtr, SourcePtr); 239 | 240 | // Refresh UI 241 | SubobjectEditor->RefreshSelectionDetails(); 242 | }); 243 | 244 | Action.IsActionVisibleDelegate = GetIsVisibleLambda(); 245 | 246 | FToolMenuEntry& Entry = Section.AddMenuEntry( 247 | "PasteIntactProperties", 248 | LOCTEXT( "PasteIntactLabel", "Paste Intact Subobject"), 249 | LOCTEXT( "PasteIntactTooltip", "Pastes the intact Subobject inplace to fix a broken one (Inheriting BPs still need to be fixed manaully)"), 250 | FSlateIcon(), 251 | Action, 252 | EUserInterfaceActionType::Button); 253 | } 254 | 255 | #undef LOCTEXT_NAMESPACE 256 | 257 | IMPLEMENT_MODULE( FBPCorruptionFixModule, BPCorruptionFix) 258 | --------------------------------------------------------------------------------