├── .gitignore ├── Build ├── MacNoEditor │ └── FileOpenOrder │ │ ├── CookerOpenOrder.log │ │ └── EditorOpenOrder.log └── WindowsNoEditor │ └── FileOpenOrder │ ├── CookerOpenOrder.log │ └── EditorOpenOrder.log ├── Config ├── DefaultEditor.ini ├── DefaultEngine.ini └── DefaultGame.ini ├── Content ├── DefaultMap.umap ├── ModListUI │ └── ModListContainer.uasset └── ModSkeletonGameMode.uasset ├── LICENSE ├── ModSkeleton.uproject ├── Plugins └── ModSkeletonExamplePluginA │ ├── Content │ ├── ExamplePluginA.umap │ ├── ExampleStringGeneratorHelper.uasset │ ├── Icon128.uasset │ ├── MOD_SKELETON.uasset │ └── ModDescriptionWidget.uasset │ ├── ModSkeletonExamplePluginA.uplugin │ ├── Resources │ └── Icon128.png │ └── Source │ └── ModSkeletonExamplePluginA │ ├── ModSkeletonExamplePluginA.Build.cs │ ├── Private │ └── ModSkeletonExamplePluginA.cpp │ └── Public │ └── ModSkeletonExamplePluginA.h ├── README.md ├── Source ├── ModSkeleton.Target.cs ├── ModSkeleton │ ├── BPVariant.cpp │ ├── BPVariant.h │ ├── ModSkeleton.Build.cs │ ├── ModSkeleton.cpp │ ├── ModSkeleton.h │ ├── ModSkeletonBpFunctionLib.cpp │ ├── ModSkeletonBpFunctionLib.h │ ├── ModSkeletonGameInstance.cpp │ ├── ModSkeletonGameInstance.h │ ├── ModSkeletonPluginInterface.cpp │ ├── ModSkeletonPluginInterface.h │ ├── ModSkeletonRegistry.cpp │ └── ModSkeletonRegistry.h └── ModSkeletonEditor.Target.cs ├── doc └── build_profiles │ ├── build_profiles.md │ ├── main_1_name.jpg │ ├── main_2_build.jpg │ ├── main_3_cook_book.jpg │ ├── main_4_cook_release.jpg │ ├── main_5_cook_advanced.jpg │ ├── main_6_package.jpg │ ├── main_7_deploy.jpg │ ├── new_profile.jpg │ ├── open_project_launcher.jpg │ ├── plugin_1_name.jpg │ └── plugin_4_cook_release.jpg └── ue4build.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Visual Studio 2015 database file 5 | *.VC.db 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | 25 | # Compiled Static libraries 26 | *.lai 27 | *.la 28 | *.a 29 | *.lib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.ipa 36 | 37 | # These project files can be generated by the engine 38 | *.xcodeproj 39 | *.xcworkspace 40 | *.sln 41 | *.suo 42 | *.opensdf 43 | *.sdf 44 | *.VC.db 45 | *.VC.opendb 46 | 47 | # Precompiled Assets 48 | SourceArt/**/*.png 49 | SourceArt/**/*.tga 50 | 51 | # Binary Files 52 | Binaries/* 53 | 54 | # Builds 55 | Build/* 56 | 57 | # Whitelist PakBlacklist-.txt files 58 | !Build/*/ 59 | Build/*/** 60 | !Build/*/PakBlacklist*.txt 61 | 62 | # Don't ignore icon files in Build 63 | !Build/**/*.ico 64 | 65 | # Built data for maps 66 | *_BuiltData.uasset 67 | 68 | # Configuration files generated by the Editor 69 | Saved/* 70 | 71 | # Compiled source files for the engine to use 72 | Intermediate/* 73 | 74 | # Cache files for the editor to use 75 | DerivedDataCache/* 76 | 77 | 78 | # Json File Build Content 79 | .ue4build* 80 | -------------------------------------------------------------------------------- /Build/MacNoEditor/FileOpenOrder/CookerOpenOrder.log: -------------------------------------------------------------------------------- 1 | "../../../ModSkeleton/Plugins/ModSkeletonExamplePluginA/Content/ExampleStringGeneratorHelper.uasset" 0 2 | "../../../ModSkeleton/Plugins/ModSkeletonExamplePluginA/Content/Icon128.uasset" 1 3 | "../../../ModSkeleton/Plugins/ModSkeletonExamplePluginA/Content/ModDescriptionWidget.uasset" 2 4 | "../../../ModSkeleton/Plugins/ModSkeletonExamplePluginA/Content/MOD_SKELETON.uasset" 3 5 | -------------------------------------------------------------------------------- /Build/WindowsNoEditor/FileOpenOrder/CookerOpenOrder.log: -------------------------------------------------------------------------------- 1 | "../../../ModSkeleton/Plugins/ModSkeletonExamplePluginA/Content/ExampleStringGeneratorHelper.uasset" 0 2 | "../../../ModSkeleton/Plugins/ModSkeletonExamplePluginA/Content/Icon128.uasset" 1 3 | "../../../ModSkeleton/Plugins/ModSkeletonExamplePluginA/Content/ModDescriptionWidget.uasset" 2 4 | "../../../ModSkeleton/Plugins/ModSkeletonExamplePluginA/Content/MOD_SKELETON.uasset" 3 5 | -------------------------------------------------------------------------------- /Config/DefaultEditor.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/Config/DefaultEditor.ini -------------------------------------------------------------------------------- /Config/DefaultEngine.ini: -------------------------------------------------------------------------------- 1 | [URL] 2 | [/Script/Engine.RendererSettings] 3 | r.MobileHDR=True 4 | r.Mobile.DisableVertexFog=False 5 | r.Shadow.CSM.MaxMobileCascades=2 6 | r.MobileMSAA=1 7 | r.DiscardUnusedQuality=False 8 | r.AllowOcclusionQueries=True 9 | r.MinScreenRadiusForLights=0.030000 10 | r.MinScreenRadiusForDepthPrepass=0.030000 11 | r.MinScreenRadiusForCSMDepth=0.010000 12 | r.PrecomputedVisibilityWarning=False 13 | r.TextureStreaming=True 14 | Compat.UseDXT5NormalMaps=False 15 | r.ClearCoatNormal=False 16 | r.ReflectionCaptureResolution=128 17 | r.ReflectionEnvironmentLightmapMixBasedOnRoughness=True 18 | r.ForwardShading=False 19 | r.VertexFoggingForOpaque=True 20 | r.AllowStaticLighting=True 21 | r.NormalMapsForStaticLighting=False 22 | r.GenerateMeshDistanceFields=False 23 | r.GenerateLandscapeGIData=True 24 | r.TessellationAdaptivePixelsPerTriangle=48.000000 25 | r.SeparateTranslucency=True 26 | r.TranslucentSortPolicy=0 27 | TranslucentSortAxis=(X=0.000000,Y=-1.000000,Z=0.000000) 28 | r.CustomDepth=1 29 | r.CustomDepthTemporalAAJitter=True 30 | r.DefaultFeature.Bloom=True 31 | r.DefaultFeature.AmbientOcclusion=True 32 | r.DefaultFeature.AmbientOcclusionStaticFraction=True 33 | r.DefaultFeature.AutoExposure=False 34 | r.DefaultFeature.AutoExposure.Method=0 35 | r.DefaultFeature.MotionBlur=False 36 | r.DefaultFeature.LensFlare=False 37 | r.DefaultFeature.AntiAliasing=0 38 | r.StencilForLODDither=False 39 | r.EarlyZPass=3 40 | r.EarlyZPassMovable=True 41 | r.EarlyZPassOnlyMaterialMasking=False 42 | r.DBuffer=True 43 | r.ClearSceneMethod=1 44 | r.BasePassOutputsVelocity=False 45 | r.SelectiveBasePassOutputs=False 46 | bDefaultParticleCutouts=False 47 | r.AllowGlobalClipPlane=False 48 | r.GBufferFormat=1 49 | r.MorphTarget.Mode=False 50 | vr.InstancedStereo=False 51 | vr.MultiView=False 52 | vr.MobileMultiView=False 53 | vr.MonoscopicFarField=False 54 | r.WireframeCullThreshold=5.000000 55 | r.SupportStationarySkylight=True 56 | r.SupportLowQualityLightmaps=True 57 | r.SupportPointLightWholeSceneShadows=True 58 | r.SupportAtmosphericFog=True 59 | r.SkinCache.CompileShaders=False 60 | r.Mobile.EnableStaticAndCSMShadowReceivers=True 61 | r.Mobile.AllowDistanceFieldShadows=True 62 | r.Mobile.AllowMovableDirectionalLights=True 63 | r.MobileNumDynamicPointLights=4 64 | r.MobileDynamicPointLightsUseStaticBranch=True 65 | 66 | [/Script/HardwareTargeting.HardwareTargetingSettings] 67 | TargetedHardwareClass=Desktop 68 | AppliedTargetedHardwareClass=Desktop 69 | DefaultGraphicsPerformance=Scalable 70 | AppliedDefaultGraphicsPerformance=Scalable 71 | 72 | [/Script/EngineSettings.GameMapsSettings] 73 | GameInstanceClass=/Script/ModSkeleton.ModSkeletonGameInstance 74 | GlobalDefaultGameMode=/Game/ModSkeletonGameMode.ModSkeletonGameMode_C 75 | GameDefaultMap=/Game/DefaultMap.DefaultMap 76 | EditorStartupMap=/Game/DefaultMap.DefaultMap 77 | 78 | [/Script/Engine.PhysicsSettings] 79 | DefaultGravityZ=-980.000000 80 | DefaultTerminalVelocity=4000.000000 81 | DefaultFluidFriction=0.300000 82 | SimulateScratchMemorySize=262144 83 | RagdollAggregateThreshold=4 84 | TriangleMeshTriangleMinAreaThreshold=5.000000 85 | bEnableAsyncScene=False 86 | bEnableShapeSharing=False 87 | bEnablePCM=False 88 | bEnableStabilization=False 89 | bWarnMissingLocks=True 90 | bEnable2DPhysics=False 91 | LockedAxis=Invalid 92 | DefaultDegreesOfFreedom=Full3D 93 | BounceThresholdVelocity=200.000000 94 | FrictionCombineMode=Average 95 | RestitutionCombineMode=Average 96 | MaxAngularVelocity=3600.000000 97 | MaxDepenetrationVelocity=0.000000 98 | ContactOffsetMultiplier=0.010000 99 | MinContactOffset=0.000100 100 | MaxContactOffset=1.000000 101 | bSimulateSkeletalMeshOnDedicatedServer=True 102 | DefaultShapeComplexity=CTF_UseSimpleAndComplex 103 | bDefaultHasComplexCollision=True 104 | bSuppressFaceRemapTable=False 105 | bSupportUVFromHitResults=False 106 | bDisableActiveActors=False 107 | bDisableCCD=False 108 | bEnableEnhancedDeterminism=False 109 | MaxPhysicsDeltaTime=0.033333 110 | bSubstepping=False 111 | bSubsteppingAsync=False 112 | MaxSubstepDeltaTime=0.016667 113 | MaxSubsteps=6 114 | SyncSceneSmoothingFactor=0.000000 115 | AsyncSceneSmoothingFactor=0.990000 116 | InitialAverageFrameRate=0.016667 117 | PhysXTreeRebuildRate=10 118 | 119 | 120 | -------------------------------------------------------------------------------- /Config/DefaultGame.ini: -------------------------------------------------------------------------------- 1 | [/Script/EngineSettings.GeneralProjectSettings] 2 | ProjectID=8B6AD5F94DBFC8D8BDB06B90CF1B6E40 3 | ProjectName=ModSkeleton 4 | ProjectVersion=0.2 5 | CompanyName=Smogworks 6 | Homepage="http://www.smogworks.com" 7 | CopyrightNotice=Copyright 2017 Smogworks. 8 | LicensingTerms="Copyright 2017 Smogworks. Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License." 9 | 10 | [/Script/UnrealEd.ProjectPackagingSettings] 11 | bCompressed=True 12 | -------------------------------------------------------------------------------- /Content/DefaultMap.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/Content/DefaultMap.umap -------------------------------------------------------------------------------- /Content/ModListUI/ModListContainer.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/Content/ModListUI/ModListContainer.uasset -------------------------------------------------------------------------------- /Content/ModSkeletonGameMode.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/Content/ModSkeletonGameMode.uasset -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /ModSkeleton.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "4.18", 4 | "Category": "", 5 | "Description": "", 6 | "Modules": [ 7 | { 8 | "Name": "ModSkeleton", 9 | "Type": "Runtime", 10 | "LoadingPhase": "Default", 11 | "AdditionalDependencies": [ 12 | "CoreUObject", 13 | "Engine" 14 | ] 15 | } 16 | ], 17 | "Plugins": [ 18 | { 19 | "Name": "ModSkeletonExamplePluginA", 20 | "Enabled": true 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /Plugins/ModSkeletonExamplePluginA/Content/ExamplePluginA.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/Plugins/ModSkeletonExamplePluginA/Content/ExamplePluginA.umap -------------------------------------------------------------------------------- /Plugins/ModSkeletonExamplePluginA/Content/ExampleStringGeneratorHelper.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/Plugins/ModSkeletonExamplePluginA/Content/ExampleStringGeneratorHelper.uasset -------------------------------------------------------------------------------- /Plugins/ModSkeletonExamplePluginA/Content/Icon128.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/Plugins/ModSkeletonExamplePluginA/Content/Icon128.uasset -------------------------------------------------------------------------------- /Plugins/ModSkeletonExamplePluginA/Content/MOD_SKELETON.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/Plugins/ModSkeletonExamplePluginA/Content/MOD_SKELETON.uasset -------------------------------------------------------------------------------- /Plugins/ModSkeletonExamplePluginA/Content/ModDescriptionWidget.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/Plugins/ModSkeletonExamplePluginA/Content/ModDescriptionWidget.uasset -------------------------------------------------------------------------------- /Plugins/ModSkeletonExamplePluginA/ModSkeletonExamplePluginA.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "ModSkeletonExamplePluginA", 6 | "Description": "", 7 | "Category": "ModSkeleton", 8 | "CreatedBy": "", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "Installed": false, 16 | "Modules": [ 17 | { 18 | "Name": "ModSkeletonExamplePluginA", 19 | "Type": "Developer", 20 | "LoadingPhase": "Default" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /Plugins/ModSkeletonExamplePluginA/Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/Plugins/ModSkeletonExamplePluginA/Resources/Icon128.png -------------------------------------------------------------------------------- /Plugins/ModSkeletonExamplePluginA/Source/ModSkeletonExamplePluginA/ModSkeletonExamplePluginA.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using UnrealBuildTool; 16 | 17 | public class ModSkeletonExamplePluginA : ModuleRules 18 | { 19 | public ModSkeletonExamplePluginA(ReadOnlyTargetRules Target) : base(Target) 20 | { 21 | 22 | PublicIncludePaths.AddRange( 23 | new string[] { 24 | "ModSkeletonExamplePluginA/Public" 25 | // ... add public include paths required here ... 26 | } 27 | ); 28 | 29 | 30 | PrivateIncludePaths.AddRange( 31 | new string[] { 32 | "ModSkeletonExamplePluginA/Private", 33 | // ... add other private include paths required here ... 34 | } 35 | ); 36 | 37 | 38 | PublicDependencyModuleNames.AddRange( 39 | new string[] 40 | { 41 | "Core", 42 | // ... add other public dependencies that you statically link with here ... 43 | } 44 | ); 45 | 46 | 47 | PrivateDependencyModuleNames.AddRange( 48 | new string[] 49 | { 50 | "CoreUObject", 51 | "Engine", 52 | "Slate", 53 | "SlateCore", 54 | // ... add private dependencies that you statically link with here ... 55 | } 56 | ); 57 | 58 | 59 | DynamicallyLoadedModuleNames.AddRange( 60 | new string[] 61 | { 62 | // ... add any modules that your module loads dynamically here ... 63 | } 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Plugins/ModSkeletonExamplePluginA/Source/ModSkeletonExamplePluginA/Private/ModSkeletonExamplePluginA.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "ModSkeletonExamplePluginA.h" 16 | 17 | #define LOCTEXT_NAMESPACE "FModSkeletonExamplePluginAModule" 18 | 19 | void FModSkeletonExamplePluginAModule::StartupModule() 20 | { 21 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 22 | } 23 | 24 | void FModSkeletonExamplePluginAModule::ShutdownModule() 25 | { 26 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 27 | // we call this function before unloading the module. 28 | } 29 | 30 | #undef LOCTEXT_NAMESPACE 31 | 32 | IMPLEMENT_MODULE(FModSkeletonExamplePluginAModule, ModSkeletonExamplePluginA) -------------------------------------------------------------------------------- /Plugins/ModSkeletonExamplePluginA/Source/ModSkeletonExamplePluginA/Public/ModSkeletonExamplePluginA.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include "ModuleManager.h" 18 | 19 | class FModSkeletonExamplePluginAModule : public IModuleInterface 20 | { 21 | public: 22 | 23 | /** IModuleInterface implementation */ 24 | virtual void StartupModule() override; 25 | virtual void ShutdownModule() override; 26 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModSkeleton 2 | 3 | - Targets UE 4.19 Preview 4 4 | 5 | ``` 6 | Copyright 2017 Smogworks 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | ``` 20 | 21 | ## Goals 22 | 23 | - To be a go-to example for Unreal Engine 4 modding support. 24 | - To not require core engine changes - work with prebuilt UE4 Editor from Epig Games Launcher. 25 | 26 | ## Get it Running (Scripted) 27 | 28 | 1. Download NodeJS - https://nodejs.org 29 | 1. Clone the Repo 30 | 1. Run the build script once to generate a config: `node ue4build.js` 31 | 1. Inspect the config (`.ue4build.json`), if you built ue4 from source, you may need to change the `uatCommand` path. 32 | 1. Run the build script again to build: `node ue4build.js` 33 | 1. If there are errors, AutomationTool.exe output can be found in the log file specified in `.ue4build.json` 34 | 1. If it succeeds, a runable project complete with mod paks should be found in the `outputPath` directory specified in `.ue4build.json` 35 | 36 | ## Get it Running (Manually) 37 | 38 | 1. Clone the Repo 39 | 1. Open ModSkeleton.uproject 40 | 1. Be sure to select "View Options" -> "Show Plugin Content" in the Content browser to see Example Plugin Content 41 | 1. Follow [Instruction to Build Custom Launcher Profiles](doc/build_profiles/build_profiles.md) 42 | 1. Disable the ModSkeletonExamplePluginA plugin (Edit -> Plugins -> "ModSkeleton" section) 43 | 1. Execute the Full Game launch profile for your platform 44 | 1. Enable the ModSkeletonExamplePluginA plugin 45 | 1. Execute the Mod launch profile for you platform - note this may fail, but not before generating the needed .pak and AssetRegistry files 46 | 1. Move/Rename "Plugins/ModSkeletonExamplePluginA/Saved/Cooked/[platform]/ModSkeleton/AssetRegistry.bin" to "Saved/StagedBuilds/[platform]/ModSkeleton/Content/Paks/ModSkeletonExamplePluginA.bin" 47 | 1. Move/Rename "Plugins/ModSkeletonExamplePluginA/Saved/StagedBuilds/[platform]/ModSkeleton/Content/Paks/ModSkeleton-[platform].pak" to "Saved/StagedBuilds/[platform]/ModSkeleton/Content/Paks/ModSkeletonExamplePluginA.pak" 48 | 1. Execute "Saved/StagedBuilds/[platform]/[ModSkeleton executable] 49 | 50 | ## Architecture 51 | 52 | ### Startup 53 | 54 | - ModSkeletonGameInstance initializes and keeps a reference to a single ModSkeletonRegistry instance 55 | - ModSkeletonRegistry scans the Content/Paks directory for matching AssetRegistry (".bin") files and Content (".pak") files loading all. 56 | - ModSkeletonRegistry searches the in-memory AssetRegistry for all classes whos name begins with "MOD_SKELETON" and who implement ModSkeletonPluginInterface 57 | - The plugin interface is invoked once as "ModSkeletonInit" allowing these mods to register, connect, and/or invoke mod Hooks. 58 | 59 | ### ModSkeleton Hooks 60 | 61 | - BPVariant is a uobject based blueprint friendly variant class to support easy data interchange through hook invokes 62 | - Hooks marked "Always Invoke" (like the "ModSkeletonInit" hook) will be called once for every loaded MOD_SKELETON init interface 63 | - Hooks NOT marked "Always Invoke" will only be called if they have been Connected, and will be called in priority order 64 | - Hooks will be passed a reference to an array of BPVariants. This "HookIO" will be used as both input and output, and allows hooks to modify core behavior: 65 | 66 | Imagine a registered hook that is requesting a list of main menu items. The base game could begin this list with buttons labeled "New Game", "Load Game", and "Exit". Someone could create a mod that adjusts this list, replacing the "New Game" button with one that leads to a different character creation screen. Psuedo Code: 67 | 68 | ``` 69 | class CoreGame implements ModSkeletonPluginInterface 70 | function ModSkeletonHook(String HookName, BPVariantArray HookIO) 71 | if HookName == "PopulateMainMenu" 72 | HookIO[0].Add( NewGameButton ) 73 | HookIO[0].Add( LoadGameButton ) 74 | HookIO[0].Add( ExitButton ) 75 | end if 76 | end function 77 | end class 78 | 79 | class MyNewCharacterMod implements ModSkeletonPluginInterface 80 | function ModSkeletonHook(String HookName, BPVariantArray HookIO) 81 | if HookName == "PopulateMainMenu" 82 | HookIO[0].RemoveItem( NewGameButton ) 83 | HookIO[0].Prepend( MyBetterNewGameButton ) 84 | end if 85 | end function 86 | end class 87 | ``` 88 | 89 | ## TODO 90 | 91 | - switch to using `FCoreDelegates::OnMountPak` (I haven't figured out the mountpoint paths with this method) 92 | - Example CPP plugin 93 | 94 | ## Questions 95 | 96 | - Better way to distribute? Could make the core stuff a plugin... but then other plugins would have to depend on headers in it... 97 | - BPVariant blueprint constructor helpers seem a little kludgy (especially with the hidden world context -> outer pins) any way to make that better? 98 | - HookIO TArray< BPVariant* > in-out pins cause more copying than I was hoping for... better way to solve that? I'd like to just modify the passed in reference, but then it shows up as an output on the return node... 99 | -------------------------------------------------------------------------------- /Source/ModSkeleton.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using UnrealBuildTool; 16 | using System.Collections.Generic; 17 | 18 | public class ModSkeletonTarget : TargetRules 19 | { 20 | public ModSkeletonTarget(TargetInfo Target) : base(Target) 21 | { 22 | Type = TargetType.Game; 23 | 24 | ExtraModuleNames.AddRange( new string[] { "ModSkeleton" } ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/ModSkeleton/BPVariant.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "BPVariant.h" 16 | #include "ModSkeleton.h" 17 | 18 | EBPVariantType UBPVariant::GetType() const 19 | { 20 | return Type; 21 | } 22 | 23 | void UBPVariant::SetType(EBPVariantType NewType) 24 | { 25 | if (Type == EBPVariantType::VT_String) 26 | { 27 | delete StoreUnion.StoreString; 28 | } 29 | Type = NewType; 30 | AsArray.Empty(); 31 | StoreObject = nullptr; 32 | FMemory::Memset(&StoreUnion, 0, sizeof(PrivStoreUnion)); 33 | if (Type == EBPVariantType::VT_String) { 34 | StoreUnion.StoreString = new FString(); 35 | } 36 | } 37 | 38 | FString UBPVariant::GetDebugValue() const 39 | { 40 | switch (Type) 41 | { 42 | case EBPVariantType::VT_Boolean: 43 | return FString::Printf(TEXT("Boolean(%s)"), GetAsBoolean() ? TEXT("true") : TEXT("false")); 44 | case EBPVariantType::VT_Integer: 45 | return FString::Printf(TEXT("Integer(%d)"), GetAsInteger()); 46 | case EBPVariantType::VT_Float: 47 | return FString::Printf(TEXT("Float(%f)"), GetAsFloat()); 48 | case EBPVariantType::VT_String: 49 | return FString::Printf(TEXT("String(\"%s\")"), *GetAsString()); 50 | case EBPVariantType::VT_Class: 51 | return FString::Printf(TEXT("Class(%s)"), *GetAsClass()->GetFullName()); 52 | case EBPVariantType::VT_Object: 53 | return FString::Printf(TEXT("Object(%s)"), *GetAsObject()->GetFullName()); 54 | case EBPVariantType::VT_Array: 55 | { 56 | FString Out("Array["); 57 | for (int32 i = 0; i < AsArray.Num(); ++i) 58 | { 59 | if (i > 0) Out.Append(","); 60 | Out.Append("\n "); 61 | if (AsArray[i] == nullptr) 62 | { 63 | Out.Append("None"); 64 | } 65 | else 66 | { 67 | Out.Append(AsArray[i]->GetDebugValue()); 68 | } 69 | } 70 | Out.Append("\n]"); 71 | return Out; 72 | } 73 | default: 74 | case EBPVariantType::VT_None: 75 | return TEXT("None"); 76 | } 77 | } 78 | 79 | UBPVariant* UBPVariant::NewBPVariantAsBoolean(UObject* Outer, bool Value) 80 | { 81 | UBPVariant* Out = NewObject(Outer, UBPVariant::StaticClass()); 82 | Out->SetAsBoolean(Value); 83 | return Out; 84 | } 85 | 86 | bool UBPVariant::GetAsBoolean() const 87 | { 88 | if (Type == EBPVariantType::VT_Boolean) 89 | { 90 | return StoreUnion.StoreBool; 91 | } 92 | else 93 | { 94 | return false; 95 | } 96 | } 97 | 98 | bool UBPVariant::SetAsBoolean(bool Value) 99 | { 100 | SetType(EBPVariantType::VT_Boolean); 101 | StoreUnion.StoreBool = Value; 102 | return Value; 103 | } 104 | 105 | UBPVariant* UBPVariant::NewBPVariantAsInteger(UObject* Outer, int32 Value) 106 | { 107 | UBPVariant* Out = NewObject(Outer, UBPVariant::StaticClass()); 108 | Out->SetAsInteger(Value); 109 | return Out; 110 | } 111 | 112 | int32 UBPVariant::GetAsInteger() const 113 | { 114 | if (Type == EBPVariantType::VT_Integer) 115 | { 116 | return StoreUnion.StoreInt32; 117 | } 118 | else 119 | { 120 | return 0; 121 | } 122 | } 123 | 124 | int32 UBPVariant::SetAsInteger(int32 Value) 125 | { 126 | SetType(EBPVariantType::VT_Integer); 127 | StoreUnion.StoreInt32 = Value; 128 | return Value; 129 | } 130 | 131 | 132 | UBPVariant* UBPVariant::NewBPVariantAsFloat(UObject* Outer, float Value) 133 | { 134 | UBPVariant* Out = NewObject(Outer, UBPVariant::StaticClass()); 135 | Out->SetAsFloat(Value); 136 | return Out; 137 | } 138 | 139 | float UBPVariant::GetAsFloat() const 140 | { 141 | if (Type == EBPVariantType::VT_Float) 142 | { 143 | return StoreUnion.StoreFloat; 144 | } 145 | else 146 | { 147 | return 0.0f; 148 | } 149 | } 150 | 151 | float UBPVariant::SetAsFloat(float Value) 152 | { 153 | SetType(EBPVariantType::VT_Float); 154 | StoreUnion.StoreFloat = Value; 155 | return Value; 156 | } 157 | 158 | UBPVariant* UBPVariant::NewBPVariantAsString(UObject* Outer, const FString& Value) 159 | { 160 | UBPVariant* Out = NewObject(Outer, UBPVariant::StaticClass()); 161 | Out->SetAsString(Value); 162 | return Out; 163 | } 164 | 165 | const FString& UBPVariant::GetAsString() const 166 | { 167 | if (Type == EBPVariantType::VT_String) 168 | { 169 | return *StoreUnion.StoreString; 170 | } 171 | else 172 | { 173 | // TODO - BAD 174 | return *((FString*)nullptr); 175 | } 176 | } 177 | 178 | const FString& UBPVariant::SetAsString(const FString& Value) 179 | { 180 | SetType(EBPVariantType::VT_String); 181 | *StoreUnion.StoreString = Value; 182 | return Value; 183 | } 184 | 185 | UBPVariant* UBPVariant::NewBPVariantAsClass(UObject* Outer, UClass* Value) 186 | { 187 | UBPVariant* Out = NewObject(Outer, UBPVariant::StaticClass()); 188 | Out->SetAsClass(Value); 189 | return Out; 190 | } 191 | 192 | UClass* UBPVariant::GetAsClass() const 193 | { 194 | if (Type == EBPVariantType::VT_Class) 195 | { 196 | return StoreUnion.StoreClass; 197 | } 198 | else 199 | { 200 | return nullptr; 201 | } 202 | } 203 | 204 | UClass* UBPVariant::SetAsClass(UClass* Value) 205 | { 206 | SetType(EBPVariantType::VT_Class); 207 | StoreUnion.StoreClass = Value; 208 | return Value; 209 | } 210 | 211 | UBPVariant* UBPVariant::NewBPVariantAsObject(UObject* Outer, UObject* Value) 212 | { 213 | UBPVariant* Out = NewObject(Outer, UBPVariant::StaticClass()); 214 | Out->SetAsObject(Value); 215 | return Out; 216 | } 217 | 218 | UObject* UBPVariant::GetAsObject() const 219 | { 220 | if (Type == EBPVariantType::VT_Object) 221 | { 222 | return StoreObject; 223 | } 224 | else 225 | { 226 | return nullptr; 227 | } 228 | } 229 | 230 | UObject* UBPVariant::SetAsObject(UObject* Value) 231 | { 232 | SetType(EBPVariantType::VT_Object); 233 | StoreObject = Value; 234 | return Value; 235 | } 236 | 237 | UBPVariant* UBPVariant::NewBPVariantAsArray(UObject* Outer) 238 | { 239 | UBPVariant* Out = NewObject(Outer, UBPVariant::StaticClass()); 240 | Out->SetAsArray(); 241 | return Out; 242 | } 243 | 244 | void UBPVariant::SetAsArray() 245 | { 246 | SetType(EBPVariantType::VT_Array); 247 | } -------------------------------------------------------------------------------- /Source/ModSkeleton/BPVariant.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include "UObject/NoExportTypes.h" 18 | #include "BPVariant.generated.h" 19 | 20 | /** 21 | */ 22 | UENUM(BlueprintType) 23 | enum class EBPVariantType : uint8 24 | { 25 | VT_None UMETA(DisplayName="None"), 26 | VT_Boolean UMETA(DisplayName="Boolean"), 27 | VT_Integer UMETA(DisplayName="Integer"), 28 | VT_Float UMETA(DisplayName="Float"), 29 | VT_String UMETA(DisplayName="String"), 30 | VT_Class UMETA(DisplayName="Class"), 31 | VT_Object UMETA(DisplayName="Object"), 32 | VT_Array UMETA(DisplayName="Array") 33 | }; 34 | 35 | /** 36 | * 37 | */ 38 | UCLASS(Blueprintable, BlueprintType) 39 | class MODSKELETON_API UBPVariant : public UObject 40 | { 41 | GENERATED_BODY() 42 | 43 | public: 44 | UFUNCTION(BlueprintCallable, BlueprintPure, meta = (CompactNodeTitle = "Type")) 45 | virtual EBPVariantType GetType() const; 46 | 47 | UFUNCTION(BlueprintCallable, BlueprintPure, meta = (CompactNodeTitle = "DebugValue")) 48 | virtual FString GetDebugValue() const; 49 | 50 | UFUNCTION(BlueprintCallable, meta = (HidePin = Outer, DefaultToSelf = Outer)) 51 | static UBPVariant* NewBPVariantAsBoolean(UObject* Outer, bool Value); 52 | 53 | UFUNCTION(BlueprintCallable, BlueprintPure, meta = (CompactNodeTitle = "Boolean")) 54 | virtual bool GetAsBoolean() const; 55 | 56 | UFUNCTION(BlueprintCallable) 57 | virtual bool SetAsBoolean(bool Value); 58 | 59 | UFUNCTION(BlueprintCallable, meta = (HidePin = Outer, DefaultToSelf = Outer)) 60 | static UBPVariant* NewBPVariantAsInteger(UObject* Outer, int32 Value); 61 | 62 | UFUNCTION(BlueprintCallable, BlueprintPure, meta = (CompactNodeTitle = "Integer")) 63 | virtual int32 GetAsInteger() const; 64 | 65 | UFUNCTION(BlueprintCallable) 66 | virtual int32 SetAsInteger(int32 Value); 67 | 68 | UFUNCTION(BlueprintCallable, meta = (HidePin = Outer, DefaultToSelf = Outer)) 69 | static UBPVariant* NewBPVariantAsFloat(UObject* Outer, float Value); 70 | 71 | UFUNCTION(BlueprintCallable, BlueprintPure, meta = (CompactNodeTitle = "Float")) 72 | virtual float GetAsFloat() const; 73 | 74 | UFUNCTION(BlueprintCallable) 75 | virtual float SetAsFloat(float Value); 76 | 77 | UFUNCTION(BlueprintCallable, meta = (HidePin = Outer, DefaultToSelf = Outer)) 78 | static UBPVariant* NewBPVariantAsString(UObject* Outer, const FString& Value); 79 | 80 | UFUNCTION(BlueprintCallable, BlueprintPure, meta = (CompactNodeTitle = "String")) 81 | virtual const FString& GetAsString() const; 82 | 83 | UFUNCTION(BlueprintCallable) 84 | virtual const FString& SetAsString(const FString& Value); 85 | 86 | UFUNCTION(BlueprintCallable, meta = (HidePin = Outer, DefaultToSelf = Outer)) 87 | static UBPVariant* NewBPVariantAsClass(UObject* Outer, UClass* Value); 88 | 89 | UFUNCTION(BlueprintCallable, BlueprintPure, meta = (CompactNodeTitle = "Class")) 90 | virtual UClass* GetAsClass() const; 91 | 92 | UFUNCTION(BlueprintCallable) 93 | virtual UClass* SetAsClass(UClass* Value); 94 | 95 | UFUNCTION(BlueprintCallable, meta = (HidePin = Outer, DefaultToSelf = Outer)) 96 | static UBPVariant* NewBPVariantAsObject(UObject* Outer, UObject* Value); 97 | 98 | UFUNCTION(BlueprintCallable, BlueprintPure, meta = (CompactNodeTitle = "Object")) 99 | virtual UObject* GetAsObject() const; 100 | 101 | UFUNCTION(BlueprintCallable) 102 | virtual UObject* SetAsObject(UObject* Value); 103 | 104 | UFUNCTION(BlueprintCallable, meta = (HidePin = Outer, DefaultToSelf = Outer)) 105 | static UBPVariant* NewBPVariantAsArray(UObject* Outer); 106 | 107 | UFUNCTION(BlueprintCallable) 108 | virtual void SetAsArray(); 109 | 110 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BPVariant") 111 | TArray< UBPVariant* > AsArray; 112 | 113 | private: 114 | EBPVariantType Type; 115 | void SetType(EBPVariantType NewType); 116 | 117 | union PrivStoreUnion 118 | { 119 | bool StoreBool; 120 | int32 StoreInt32; 121 | float StoreFloat; 122 | FString* StoreString; 123 | UClass* StoreClass; 124 | }; 125 | PrivStoreUnion StoreUnion; 126 | 127 | UPROPERTY() 128 | UObject* StoreObject; 129 | }; 130 | -------------------------------------------------------------------------------- /Source/ModSkeleton/ModSkeleton.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using UnrealBuildTool; 16 | 17 | public class ModSkeleton : ModuleRules 18 | { 19 | public ModSkeleton(ReadOnlyTargetRules Target) : base(Target) 20 | { 21 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 22 | 23 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG", "PakFile"}); 24 | 25 | PrivateDependencyModuleNames.AddRange(new string[] { }); 26 | 27 | // Uncomment if you are using Slate UI 28 | // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); 29 | 30 | // Uncomment if you are using online features 31 | // PrivateDependencyModuleNames.Add("OnlineSubsystem"); 32 | 33 | // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/ModSkeleton/ModSkeleton.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "ModSkeleton.h" 16 | 17 | IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ModSkeleton, "ModSkeleton" ); 18 | 19 | DEFINE_LOG_CATEGORY(ModSkeletonLog); 20 | -------------------------------------------------------------------------------- /Source/ModSkeleton/ModSkeleton.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include "Engine.h" 18 | 19 | DECLARE_LOG_CATEGORY_EXTERN(ModSkeletonLog, Log, All); 20 | -------------------------------------------------------------------------------- /Source/ModSkeleton/ModSkeletonBpFunctionLib.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "ModSkeletonBpFunctionLib.h" 16 | #include "ModSkeleton.h" 17 | 18 | UModSkeletonRegistry* UModSkeletonBpFunctionLib::GlobalModRegistryRef = nullptr; 19 | 20 | UModSkeletonRegistry* UModSkeletonBpFunctionLib::ModSkeletonRegistryGet() 21 | { 22 | return GlobalModRegistryRef; 23 | } 24 | 25 | FString UModSkeletonBpFunctionLib::GetFullDescription(const FModSkeletonHookDescription& HookDescription) 26 | { 27 | FString out(HookDescription.HookName); 28 | out.Append(" : "); 29 | out.Append(HookDescription.HookDescription); 30 | out.Append("\nHookIO:"); 31 | for (auto P : HookDescription.HookIODescription) 32 | { 33 | out.Append("\n - "); 34 | out.Append(P); 35 | } 36 | return out; 37 | } -------------------------------------------------------------------------------- /Source/ModSkeleton/ModSkeletonBpFunctionLib.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include "ModSkeletonRegistry.h" 18 | #include "ModSkeletonGameInstance.h" 19 | 20 | #include "Kismet/BlueprintFunctionLibrary.h" 21 | #include "ModSkeletonBpFunctionLib.generated.h" 22 | 23 | /** 24 | * Some blueprint helper functions to make working with ModSkeleton easier 25 | */ 26 | UCLASS() 27 | class MODSKELETON_API UModSkeletonBpFunctionLib : public UBlueprintFunctionLibrary 28 | { 29 | friend class UModSkeletonGameInstance; 30 | 31 | GENERATED_BODY() 32 | 33 | public: 34 | 35 | /** 36 | * Get access to the Registry singleton 37 | */ 38 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "ModSkeleton") 39 | static UModSkeletonRegistry* ModSkeletonRegistryGet(); 40 | 41 | /** 42 | * Helper compact BP node to dump the hook description struct 43 | */ 44 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "ModSkeletonHookDescription", meta = (CompactNodeTitle = "FullDescription")) 45 | static FString GetFullDescription(const FModSkeletonHookDescription& HookDescription); 46 | 47 | private: 48 | static UModSkeletonRegistry* GlobalModRegistryRef; 49 | }; 50 | -------------------------------------------------------------------------------- /Source/ModSkeleton/ModSkeletonGameInstance.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "ModSkeletonGameInstance.h" 16 | #include "ModSkeleton.h" 17 | #include "ModSkeletonBpFunctionLib.h" 18 | 19 | void UModSkeletonGameInstance::Init() 20 | { 21 | ModRegistry = NewObject(this, UModSkeletonRegistry::StaticClass()); 22 | UModSkeletonBpFunctionLib::GlobalModRegistryRef = ModRegistry; 23 | ModRegistry->ScanForModPlugins(); 24 | UPlatformGameInstance::Init(); 25 | } 26 | 27 | void UModSkeletonGameInstance::Shutdown() 28 | { 29 | ModRegistry = nullptr; 30 | UModSkeletonBpFunctionLib::GlobalModRegistryRef = nullptr; 31 | UPlatformGameInstance::Shutdown(); 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /Source/ModSkeleton/ModSkeletonGameInstance.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include "ModSkeletonRegistry.h" 18 | 19 | #include "Kismet/BlueprintPlatformLibrary.h" 20 | #include "ModSkeletonGameInstance.generated.h" 21 | 22 | /** 23 | * Helper game instance to create a registry object and allow access to it through the bp function lib 24 | */ 25 | UCLASS() 26 | class MODSKELETON_API UModSkeletonGameInstance : public UPlatformGameInstance 27 | { 28 | GENERATED_BODY() 29 | 30 | public: 31 | 32 | virtual void Init() override; 33 | virtual void Shutdown() override; 34 | 35 | UPROPERTY(BlueprintReadOnly, Category = "ModSkeleton") 36 | UModSkeletonRegistry* ModRegistry; 37 | }; 38 | -------------------------------------------------------------------------------- /Source/ModSkeleton/ModSkeletonPluginInterface.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "ModSkeletonPluginInterface.h" 16 | #include "ModSkeleton.h" 17 | 18 | 19 | UModSkeletonPluginInterface::UModSkeletonPluginInterface(const class FObjectInitializer& ObjectInitializer) 20 | : Super(ObjectInitializer) 21 | { 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Source/ModSkeleton/ModSkeletonPluginInterface.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include "BPVariant.h" 18 | 19 | #include "ModSkeletonPluginInterface.generated.h" 20 | 21 | UINTERFACE(MinimalAPI) 22 | class UModSkeletonPluginInterface : public UInterface 23 | { 24 | GENERATED_UINTERFACE_BODY() 25 | }; 26 | 27 | /** 28 | * This Interface allows communication between mods that have not yet been written 29 | */ 30 | class MODSKELETON_API IModSkeletonPluginInterface 31 | { 32 | GENERATED_IINTERFACE_BODY() 33 | 34 | public: 35 | 36 | /** 37 | * Any "Connected" Hook that is invoked will invoke this function if you implement it. 38 | * If your uclass begins with the case-sensitive string "MOD_SKELETON" then "ModSkeletonInit" will also be invoked. 39 | */ 40 | UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "ModSkeleton") 41 | TArray< UBPVariant*> ModSkeletonHook(const FString& HookName, const TArray< UBPVariant* >& HookIO); 42 | }; 43 | -------------------------------------------------------------------------------- /Source/ModSkeleton/ModSkeletonRegistry.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "ModSkeletonRegistry.h" 16 | #include "ModSkeleton.h" 17 | 18 | #include "Modules/ModuleInterface.h" 19 | #include "Modules/ModuleManager.h" 20 | 21 | #include "Runtime/PakFile/Public/IPlatformFilePak.h" 22 | 23 | #include "AssetRegistryModule.h" 24 | 25 | #include "ModSkeletonPluginInterface.h" 26 | 27 | UModSkeletonRegistry::UModSkeletonRegistry() 28 | { 29 | FModSkeletonHookDescription InitHook; 30 | InitHook.AlwaysInvoke = true; 31 | InitHook.HookName = "ModSkeletonInit"; 32 | InitHook.HookDescription = "ModSkeleton Bootstrap Entrypoint. This will be invoked on every Mod as they are loaded."; 33 | 34 | InstallHook(InitHook); 35 | } 36 | 37 | void UModSkeletonRegistry::ScanForModPlugins() 38 | { 39 | FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); 40 | IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); 41 | 42 | // Uncomment this and the "PakPlatform->IterateDirectoryRecursively" below to dump out pak contents on load 43 | //struct StructDumpVisitor : public IPlatformFile::FDirectoryVisitor 44 | //{ 45 | // virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) 46 | // { 47 | // if (bIsDirectory) 48 | // { 49 | // UE_LOG(ModSkeletonLog, Log, TEXT(" - DumpVisitor Directory: %s"), FilenameOrDirectory); 50 | // } 51 | // else 52 | // { 53 | // UE_LOG(ModSkeletonLog, Log, TEXT(" - DumpVisitor File: %s"), FilenameOrDirectory); 54 | // } 55 | // return true; 56 | // } 57 | //}; 58 | //StructDumpVisitor DumpVisitor; 59 | 60 | IPlatformFile& InnerPlatform = FPlatformFileManager::Get().GetPlatformFile(); 61 | FPakPlatformFile* PakPlatform = new FPakPlatformFile(); 62 | PakPlatform->Initialize(&InnerPlatform, TEXT("")); 63 | FPlatformFileManager::Get().SetPlatformFile(*PakPlatform); 64 | 65 | IFileManager& FileManager = IFileManager::Get(); 66 | FString PakPath = FPaths::GameContentDir() + TEXT("Paks"); 67 | FPaths::NormalizeDirectoryName(PakPath); 68 | FString BinSearch = PakPath + "/*.bin"; 69 | 70 | // First, search for all AssetRegistry *.bin files in the Paks directory 71 | TArray Files; 72 | FileManager.FindFiles(Files, *BinSearch, true, false); 73 | UE_LOG(ModSkeletonLog, Log, TEXT("Searching for Pak AssetRegistries: %s"), *BinSearch); 74 | 75 | for (int32 i = 0; i < Files.Num(); ++i) 76 | { 77 | FString BinFilename = PakPath + TEXT("/") + Files[i]; 78 | FPaths::MakeStandardFilename(BinFilename); 79 | UE_LOG(ModSkeletonLog, Log, TEXT(" - AssetRegistry: %s"), *BinFilename); 80 | 81 | FString PathPart; 82 | FString FilenamePart; 83 | FString ExtensionPart; 84 | FPaths::Split(BinFilename, PathPart, FilenamePart, ExtensionPart); 85 | FString PakFilename = PathPart + "/" + FilenamePart + ".pak"; 86 | FPaths::MakeStandardFilename(PakFilename); 87 | 88 | if (LoadedPaks.Contains(PakFilename)) 89 | { 90 | continue; 91 | } 92 | 93 | // Only process Mods that have BOTH the .bin registry and the .pak content files 94 | if (FPaths::FileExists(PakFilename)) 95 | { 96 | UE_LOG(ModSkeletonLog, Log, TEXT("Attempting PakLoad: %s"), *PakFilename); 97 | 98 | // Mount the .pak content file 99 | 100 | // TODO - Would prefer to use this, but I cannot seem to make the mount paths correct for it 101 | //if (!FCoreDelegates::OnMountPak.Execute(PakFilename, 0, &DumpVisitor)) 102 | //{ 103 | // UE_LOG(ModSkeletonLog, Error, TEXT("Failed to mount pak file: %s"), *PakFilename); 104 | // GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Failed to mount pak file: %s"), *PakFilename)); 105 | // continue; 106 | //} 107 | 108 | FString MountPoint(FPaths::GetPath(PakFilename)); 109 | 110 | FPakFile PakFile(&InnerPlatform, *PakFilename, false); 111 | if (!PakFile.IsValid()) 112 | { 113 | UE_LOG(ModSkeletonLog, Error, TEXT("Invalid pak file: %s"), *PakFilename); 114 | GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Invalid pak file: %s"), *PakFilename)); 115 | continue; 116 | } 117 | 118 | PakFile.SetMountPoint(*MountPoint); 119 | if (!PakPlatform->Mount(*PakFilename, 0, *MountPoint)) 120 | { 121 | UE_LOG(ModSkeletonLog, Error, TEXT("Failed to mount pak file: %s"), *PakFilename); 122 | GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Failed to mount pak file: %s"), *PakFilename)); 123 | continue; 124 | } 125 | 126 | LoadedPaks.Add(PakFilename, true); 127 | 128 | FString MountTarget = FPaths::Combine(*MountPoint, TEXT("Plugins"), *FilenamePart, TEXT("Content/")); 129 | UE_LOG(ModSkeletonLog, Log, TEXT(" - Mounting At: %s"), *MountTarget); 130 | FPackageName::RegisterMountPoint(TEXT("/") + FilenamePart + TEXT("/"), MountTarget); 131 | 132 | // Load the asset registry .bin file into the in-memory AssetRegistry 133 | 134 | FArrayReader SerializedAssetData; 135 | if (FFileHelper::LoadFileToArray(SerializedAssetData, *BinFilename)) 136 | { 137 | AssetRegistry.Serialize(SerializedAssetData); 138 | UE_LOG(ModSkeletonLog, Log, TEXT(" - AssetRegistry Loaded (%d bytes): %s"), SerializedAssetData.Num(), *BinFilename); 139 | //GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Emerald, FString::Printf(TEXT(" - AssetRegistry Loaded (%d bytes): %s"), SerializedAssetData.Num(), *BinFilename)); 140 | } 141 | 142 | //PakPlatform->IterateDirectoryRecursively(*MountTarget, DumpVisitor); 143 | } 144 | } 145 | 146 | // now that the content assets have been added, and the asset registry has been updated 147 | // we need to search the in-memory AssetRegistry to find any MOD_SKELETON init interfaces 148 | 149 | TArray AssetData; 150 | AssetRegistry.GetAllAssets(AssetData); 151 | 152 | UE_LOG(ModSkeletonLog, Log, TEXT("Searching for ModSkeleton Mod Assets:")); 153 | 154 | for (int32 i = 0; i < AssetData.Num(); ++i) 155 | { 156 | FString name = AssetData[i].AssetName.ToString(); 157 | if (name.StartsWith("MOD_SKELETON", ESearchCase::CaseSensitive)) 158 | { 159 | UE_LOG(ModSkeletonLog, Log, TEXT(" - Asset: %s %s %s %s"), *name, *AssetData[i].PackagePath.ToString(), *AssetData[i].ObjectPath.ToString(), *AssetData[i].AssetClass.ToString()); 160 | 161 | if (LoadedPlugins.Contains(AssetData[i].ObjectPath)) 162 | { 163 | continue; 164 | } 165 | 166 | // TODO - this is loading Blueprint Interfaces 167 | // make this work with C++ interfaces as well! 168 | 169 | UClass* AssetClass = LoadObject(nullptr, *(TEXT("Class'") + AssetData[i].ObjectPath.ToString() + TEXT("_C'"))); 170 | if (AssetClass != nullptr) 171 | { 172 | UObject *RealObj = NewObject(this, AssetClass); 173 | if (RealObj->GetClass()->ImplementsInterface(UModSkeletonPluginInterface::StaticClass())) 174 | { 175 | // Invoke the ModSkeletonInit hook - this is invoked exactly once for every mod right at load. 176 | 177 | TArray< UBPVariant* > HookIO; 178 | IModSkeletonPluginInterface::Execute_ModSkeletonHook(RealObj, TEXT("ModSkeletonInit"), HookIO); 179 | 180 | LoadedPlugins.Add(AssetData[i].ObjectPath, RealObj); 181 | } 182 | } 183 | } 184 | } 185 | } 186 | 187 | void UModSkeletonRegistry::ListModPlugins(TArray< UObject* >& OutPluginList) 188 | { 189 | LoadedPlugins.GenerateValueArray(OutPluginList); 190 | } 191 | 192 | bool UModSkeletonRegistry::InstallHook(FModSkeletonHookDescription HookDescription) 193 | { 194 | if (RegisteredHooks.Contains(HookDescription.HookName)) 195 | { 196 | return false; 197 | } 198 | RegisteredHooks.Add(HookDescription.HookName, HookDescription); 199 | return true; 200 | } 201 | 202 | TArray< FModSkeletonHookDescription > UModSkeletonRegistry::ListHooks() 203 | { 204 | TArray< FModSkeletonHookDescription > OutArray; 205 | RegisteredHooks.GenerateValueArray(OutArray); 206 | return OutArray; 207 | } 208 | 209 | FModSkeletonHookDescription UModSkeletonRegistry::GetHookDescription(FString HookName) 210 | { 211 | if (RegisteredHooks.Contains(HookName)) 212 | { 213 | return RegisteredHooks[HookName]; 214 | } 215 | return FModSkeletonHookDescription(); 216 | } 217 | 218 | void UModSkeletonRegistry::ConnectHook(FString HookName, int32 Priority, UObject *ModSkeletonPluginInterface) 219 | { 220 | if (!ModSkeletonPluginInterface->GetClass()->ImplementsInterface(UModSkeletonPluginInterface::StaticClass())) { 221 | return; 222 | } 223 | 224 | FModSkeletonConnectHook NewHook; 225 | NewHook.HookName = HookName; 226 | NewHook.Priority = Priority; 227 | NewHook.ModSkeletonPluginInterface = ModSkeletonPluginInterface; 228 | 229 | ConnectedHooks.HeapPush(NewHook, FModSkeletonConnectHookPredicate()); 230 | } 231 | 232 | TArray< UBPVariant* > UModSkeletonRegistry::InvokeHook(FString HookName, const TArray< UBPVariant * >& HookIO) 233 | { 234 | if (!RegisteredHooks.Contains(HookName)) 235 | { 236 | UE_LOG(ModSkeletonLog, Warning, TEXT("Ignoring Unregistered HookName: %s"), *HookName); 237 | return HookIO; 238 | } 239 | UE_LOG(ModSkeletonLog, Log, TEXT("Invoke HookName: %s"), *HookName); 240 | 241 | // We want to pass the RESULTS from the previous invocation in as the PARAMETERS to the next 242 | TArray< UBPVariant* > CurHookIO = HookIO; 243 | 244 | FModSkeletonHookDescription HookDescription = RegisteredHooks[HookName]; 245 | if (HookDescription.AlwaysInvoke) { 246 | for (auto PlugRef : LoadedPlugins) 247 | { 248 | CurHookIO = IModSkeletonPluginInterface::Execute_ModSkeletonHook(PlugRef.Value, HookName, CurHookIO); 249 | } 250 | } 251 | else 252 | { 253 | TArray ConnectedHooksClone = ConnectedHooks; 254 | FModSkeletonConnectHook Next; 255 | while (ConnectedHooksClone.Num() > 0) 256 | { 257 | ConnectedHooksClone.HeapPop(Next, FModSkeletonConnectHookPredicate()); 258 | if (Next.HookName != HookName) continue; 259 | CurHookIO = IModSkeletonPluginInterface::Execute_ModSkeletonHook(Next.ModSkeletonPluginInterface, HookName, CurHookIO); 260 | } 261 | } 262 | return CurHookIO; 263 | } 264 | -------------------------------------------------------------------------------- /Source/ModSkeleton/ModSkeletonRegistry.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include "UObject/NoExportTypes.h" 18 | #include "ModSkeletonRegistry.generated.h" 19 | 20 | /** 21 | * This struct describes the API of an individual hook 22 | */ 23 | USTRUCT(BlueprintType, Category = "ModSkeleton") 24 | struct FModSkeletonHookDescription 25 | { 26 | GENERATED_BODY() 27 | 28 | /** 29 | * AlwaysInvoke Hooks to not participate in prioritization, and, when invoked, 30 | * are called on every registered ModPlugin. 31 | * "ModSkeletonInit" is an AlwaysInvoke Hook. 32 | */ 33 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ModSkeletonHookDescription") 34 | bool AlwaysInvoke; 35 | 36 | /** 37 | * Globally unique name of the hook 38 | */ 39 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ModSkeletonHookDescription") 40 | FString HookName; 41 | 42 | /** 43 | * Description of what the hook intends to accomplish 44 | */ 45 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ModSkeletonHookDescription") 46 | FString HookDescription; 47 | 48 | /** 49 | * Descriptions for each parameter/result in the HookIO array 50 | * Recommend including type information, like: 51 | * [ 52 | * "ButtonList {Array} - Array of buttons to be added to the menu", 53 | * "LaunchMapping {Array} - Input actions that cause this menu to be shown" 54 | * ] 55 | */ 56 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ModSkeletonHookDescription") 57 | TArray HookIODescription; 58 | }; 59 | 60 | /** 61 | * This is an internal structure for tracking hook connections 62 | */ 63 | USTRUCT() 64 | struct FModSkeletonConnectHook 65 | { 66 | GENERATED_BODY() 67 | 68 | /** 69 | * Connected HookName 70 | */ 71 | UPROPERTY() 72 | FString HookName; 73 | 74 | /** 75 | * Priority - higher numbers are invoked first 76 | */ 77 | UPROPERTY() 78 | int32 Priority; 79 | 80 | /** 81 | * Link to the UObject on which to invoke this hook. 82 | * Must implement ModSkeletonPluginInterface 83 | */ 84 | UPROPERTY() 85 | UObject *ModSkeletonPluginInterface; 86 | }; 87 | 88 | /** 89 | * Heap priority sort predicate helper 90 | */ 91 | struct FModSkeletonConnectHookPredicate 92 | { 93 | bool operator()(const FModSkeletonConnectHook& A, const FModSkeletonConnectHook& B) const 94 | { 95 | // higher priorities to the top 96 | return A.Priority > B.Priority; 97 | } 98 | }; 99 | 100 | /** 101 | * This object loads all mod packages, invokes any MOD_SKELETON ModSkeletonInit interfaces found 102 | * And keeps track of all registered mod hooks and connections. 103 | */ 104 | UCLASS(BlueprintType) 105 | class MODSKELETON_API UModSkeletonRegistry : public UObject 106 | { 107 | GENERATED_BODY() 108 | 109 | public: 110 | UModSkeletonRegistry(); 111 | 112 | /** 113 | * Invoked automaticall by ModSkeletonGameInstance... 114 | * Should be safe to invoke at runtime to load any new modules added to directory 115 | * will not re-load any mods that have changed or un-load any deleted ones 116 | */ 117 | UFUNCTION(BlueprintCallable, Category = "ModSkeleton") 118 | virtual void ScanForModPlugins(); 119 | 120 | /** 121 | * Get a list of all loaded plugin init interfaces 122 | */ 123 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "ModSkeleton") 124 | virtual void ListModPlugins(TArray< UObject* >& OutPluginList); 125 | 126 | /** 127 | * Install a new hook to the mod system 128 | */ 129 | UFUNCTION(BlueprintCallable, Category = "ModSkeleton") 130 | virtual bool InstallHook(FModSkeletonHookDescription HookDescription); 131 | 132 | /** 133 | * List all hooks that have been installed 134 | */ 135 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "ModSkeleton") 136 | virtual TArray< FModSkeletonHookDescription > ListHooks(); 137 | 138 | /** 139 | * Get a HookDescription for a single hook 140 | */ 141 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "ModSkeleton") 142 | virtual FModSkeletonHookDescription GetHookDescription(FString HookName); 143 | 144 | /** 145 | * Connect a plugin interface to a HookName at a given priority 146 | */ 147 | UFUNCTION(BlueprintCallable, Category = "ModSkeleton") 148 | virtual void ConnectHook(FString HookName, int32 Priority, UObject *ModSkeletonPluginInterface); 149 | 150 | /** 151 | * Invoke an installed hook 152 | */ 153 | UFUNCTION(BlueprintCallable, Category = "ModSkeleton") 154 | virtual TArray< UBPVariant* > InvokeHook(FString HookName, const TArray< UBPVariant* >& HookIO); 155 | 156 | private: 157 | /** 158 | * Keep track of loaded pak names so we don't re-load one we've loaded before 159 | */ 160 | UPROPERTY() 161 | TMap LoadedPaks; 162 | 163 | /** 164 | * Keep track of all initialized MOD_SKELETON init interfaces, so we don't re-init any 165 | */ 166 | UPROPERTY() 167 | TMap LoadedPlugins; 168 | 169 | /** 170 | * Keep track of all installed hook descriptions 171 | */ 172 | UPROPERTY() 173 | TMap RegisteredHooks; 174 | 175 | /** 176 | * USE AS A HEAP - always access with heap functions using FModSkeletonConnectHookPredicate 177 | * The priority heap of connected hooks 178 | */ 179 | UPROPERTY() 180 | TArray ConnectedHooks; 181 | }; 182 | -------------------------------------------------------------------------------- /Source/ModSkeletonEditor.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Smogworks 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http ://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using UnrealBuildTool; 16 | using System.Collections.Generic; 17 | 18 | public class ModSkeletonEditorTarget : TargetRules 19 | { 20 | public ModSkeletonEditorTarget(TargetInfo Target) : base(Target) 21 | { 22 | Type = TargetType.Editor; 23 | 24 | ExtraModuleNames.AddRange( new string[] { "ModSkeleton" } ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /doc/build_profiles/build_profiles.md: -------------------------------------------------------------------------------- 1 | # Custom Build Profile For Generating ModSkeleton Mods 2 | 3 | ModSkeleton Mods build on top of Unreal Engine 4's DLC system. In order to cook and package a Mod, we're going to create two Custom Project Launcher Profiles. First, one to build the base game. Second, we're going to create a Profile for our DLC / Plugin / Mod, referencing the base game version. 4 | 5 | ## 1 - The Base Game 6 | 7 | #### 1.1 - Open the Project Launcher 8 | 9 | ![Open Project Launcher](open_project_launcher.jpg) 10 | 11 | #### 1.2 - Create a New Custom Profile 12 | 13 | ![Create Custom Profile](new_profile.jpg) 14 | 15 | #### 1.3 - Give Your Profile A Name 16 | 17 | ![Name Profile](main_1_name.jpg) 18 | 19 | #### 1.4 - Tell the Profile to Build 20 | 21 | ![Build](main_2_build.jpg) 22 | 23 | #### 1.5 - Tell it to Cook "By the Book" 24 | 25 | - pick the platform you would like to cook for 26 | 27 | ![Cook By the Book](main_3_cook_book.jpg) 28 | 29 | #### 1.6 - Cooking "Release" Settings 30 | 31 | - Check "Create a release version of the game for distribution" 32 | - Give a version name under "Name of the new release to create" 33 | 34 | ![Cook Release Settings](main_4_cook_release.jpg) 35 | 36 | #### 1.7 - Cooking "Advanced" Settings 37 | 38 | - Uncheck "Save packages without versions" 39 | - Check "Store all contents in a single file (UnrealPak)" 40 | 41 | ![Cook Advanced Settings](main_5_cook_advanced.jpg) 42 | 43 | #### 1.8 - Package Locally 44 | 45 | - Set to Package and Store Locally 46 | 47 | ![Package and Store Locally](main_6_package.jpg) 48 | 49 | #### 1.9 - Do Not Deploy 50 | 51 | ![Do Not Deploy](main_7_deploy.jpg) 52 | 53 | ## 2 - The Mod (ModSkeletonExamplePluginA) 54 | 55 | #### 2.1 - Open the Project Launcher 56 | 57 | ![Open Project Launcher](open_project_launcher.jpg) 58 | 59 | #### 2.2 - Create a New Custom Profile 60 | 61 | ![Create Custom Profile](new_profile.jpg) 62 | 63 | #### 2.3 - Give Your Profile A Name 64 | 65 | ![Name Profile](plugin_1_name.jpg) 66 | 67 | #### 2.4 - Tell the Profile to Build 68 | 69 | ![Build](main_2_build.jpg) 70 | 71 | #### 2.5 - Tell it to Cook "By the Book" 72 | 73 | - pick the platform you would like to cook for 74 | 75 | ![Cook By the Book](main_3_cook_book.jpg) 76 | 77 | #### 2.6 - Cooking "Release" Settings 78 | 79 | - Un-Check "Create a release version of the game for distribution" 80 | - Leave "Name of the new release to create" blank 81 | - Set "Release version this is based on." the same version string as in your base profile 82 | - Check "Build DLC" 83 | - Set "Name of the DLC to build." to "ModSkeletonExamplePluginA" 84 | 85 | ![Cook Release Settings](plugin_4_cook_release.jpg) 86 | 87 | #### 2.7 - Cooking "Advanced" Settings 88 | 89 | - Uncheck "Save packages without versions" 90 | - Check "Store all contents in a single file (UnrealPak)" 91 | 92 | ![Cook Advanced Settings](main_5_cook_advanced.jpg) 93 | 94 | #### 2.8 - Package Locally 95 | 96 | - Set to Package and Store Locally 97 | 98 | ![Package and Store Locally](main_6_package.jpg) 99 | 100 | #### 2.9 - Do Not Deploy 101 | 102 | ![Do Not Deploy](main_7_deploy.jpg) 103 | -------------------------------------------------------------------------------- /doc/build_profiles/main_1_name.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/doc/build_profiles/main_1_name.jpg -------------------------------------------------------------------------------- /doc/build_profiles/main_2_build.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/doc/build_profiles/main_2_build.jpg -------------------------------------------------------------------------------- /doc/build_profiles/main_3_cook_book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/doc/build_profiles/main_3_cook_book.jpg -------------------------------------------------------------------------------- /doc/build_profiles/main_4_cook_release.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/doc/build_profiles/main_4_cook_release.jpg -------------------------------------------------------------------------------- /doc/build_profiles/main_5_cook_advanced.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/doc/build_profiles/main_5_cook_advanced.jpg -------------------------------------------------------------------------------- /doc/build_profiles/main_6_package.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/doc/build_profiles/main_6_package.jpg -------------------------------------------------------------------------------- /doc/build_profiles/main_7_deploy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/doc/build_profiles/main_7_deploy.jpg -------------------------------------------------------------------------------- /doc/build_profiles/new_profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/doc/build_profiles/new_profile.jpg -------------------------------------------------------------------------------- /doc/build_profiles/open_project_launcher.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/doc/build_profiles/open_project_launcher.jpg -------------------------------------------------------------------------------- /doc/build_profiles/plugin_1_name.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/doc/build_profiles/plugin_1_name.jpg -------------------------------------------------------------------------------- /doc/build_profiles/plugin_4_cook_release.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calben/ModSkeleton/cadc82dd349d2a12773cadb2c3dd31c046a8ce24/doc/build_profiles/plugin_4_cook_release.jpg -------------------------------------------------------------------------------- /ue4build.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const fs = require('fs') 5 | const path = require('path') 6 | const childProcess = require('child_process') 7 | 8 | const PLUGIN_DIR = 'Plugins' 9 | const CONFIG_FILENAME = '.ue4build.json' 10 | 11 | // load custom config 12 | let config = {} 13 | if (fs.existsSync(CONFIG_FILENAME)) { 14 | config = JSON.parse(fs.readFileSync(CONFIG_FILENAME)) 15 | } else { 16 | console.log(`${CONFIG_FILENAME} not found. Generating.`) 17 | generateConfig() 18 | process.exit(0) 19 | } 20 | 21 | // log helpers 22 | let logfh = null 23 | function log (data) { 24 | if (!logfh) { 25 | logfh = fs.openSync(config.buildLog[1], 'w') 26 | } 27 | fs.writeSync(logfh, data) 28 | } 29 | 30 | // the modified 'outputPath' with '_#' appended to avoid conflicts 31 | let outputDir = null 32 | 33 | // make sure we can read the project file before proceeding 34 | let project = JSON.parse(fs.readFileSync(config.projectFile[1])) 35 | let projectBackup = config.projectFile[1] + '.bak' 36 | 37 | runBuild() 38 | 39 | // entrypoint build sequence. 40 | // disables all mod plugins 41 | // executes the main project build 42 | function runBuild () { 43 | // first, backup the project file 44 | try { fs.unlinkSync(projectBackup) } catch (e) { /* pass */ } 45 | 46 | copyFile(config.projectFile[1], projectBackup).then(() => { 47 | // disable all dlc/mod plugins 48 | console.log('Disabling all mod plugins') 49 | for (let modPlug of config.modPlugins[1]) { 50 | if (modPlug.asMod[1]) { 51 | setPluginEnabled(modPlug.name[1], false) 52 | } 53 | } 54 | fs.writeFileSync(config.projectFile[1], JSON.stringify(project, null, '\t')) 55 | 56 | console.log('= Execute Main Build =') 57 | runUAT(getMainBuildParams()).then(runBuildStep2, (err) => { 58 | console.error('Exited with code: ' + err.code) 59 | process.exit(1) 60 | }) 61 | }) 62 | } 63 | 64 | // step 2 of the build sequence 65 | // re-enables all mod plugins 66 | // executes individual mod(dlc) builds in sequence 67 | function runBuildStep2 () { 68 | // re-enable all dlc/mod plugins 69 | console.log('Reenabling all mod plugins') 70 | for (let modPlug of config.modPlugins[1]) { 71 | if (modPlug.asMod[1]) { 72 | setPluginEnabled(modPlug.name[1], true) 73 | } 74 | } 75 | fs.writeFileSync(config.projectFile[1], JSON.stringify(project, null, '\t')) 76 | 77 | // build all the mods 78 | let modsToBuild = [] 79 | for (let modPlug of config.modPlugins[1]) { 80 | if (modPlug.asMod[1]) { 81 | modsToBuild.push(modPlug.name[1]) 82 | } 83 | } 84 | let buildNext = () => { 85 | console.log('evaluating whether to run build step 3') 86 | if (!modsToBuild.length) { 87 | runBuildStep3() 88 | return 89 | } 90 | let modName = modsToBuild.shift() 91 | console.log('= Execute Mod Build - ' + modName + ' =') 92 | runUAT(getModBuildParams(modName)).then(buildNext, (err) => { 93 | // check for update resource bug 94 | // if (err.result.indexOf("Program.Main: ERROR: AutomationTool terminated with exception: System.Exception: Couldn't update resource") > -1 && err.result.indexOf('Project.RunUnrealPak: UnrealPak Done') > -1) { 95 | // log('^-- NOTE: this is an editor bug... the build failed but not before generating\nthe needed .pak and AssetRegistry.bin files. Everything is OK, proceeding...\n') 96 | buildNext() 97 | return 98 | // } 99 | // console.error('Exited with code: ' + err.code) 100 | // process.exit(1) 101 | }) 102 | } 103 | buildNext() 104 | } 105 | 106 | // step 3 of the build sequence 107 | // copies the main build to the output dir 108 | function runBuildStep3 () { 109 | let source = path.resolve(path.normalize(`Saved/StagedBuilds/${config.platformDirName[1]}`)) 110 | let dest = config.outputPath[1] 111 | outputDir = dest 112 | let num = 1 113 | while (fs.existsSync(outputDir)) { 114 | outputDir = dest + '_' + num 115 | ++num 116 | } 117 | console.log('Copying ' + source + ' to ' + outputDir) 118 | copyDir(source, outputDir).then(runBuildStep4, (err) => { 119 | console.error(err) 120 | process.exit(1) 121 | }) 122 | } 123 | 124 | // step 4 of the build sequence 125 | // copies all mod .pak and .bin files to the output dir 126 | function runBuildStep4 () { 127 | // build all the mods 128 | let modsToCopy = [] 129 | for (let modPlug of config.modPlugins[1]) { 130 | if (modPlug.asMod[1]) { 131 | modsToCopy.push(modPlug.name[1]) 132 | } 133 | } 134 | let buildNext = () => { 135 | if (!modsToCopy.length) { 136 | runBuildStep5() 137 | return 138 | } 139 | let modName = modsToCopy.shift() 140 | let all = [] 141 | let source = path.resolve(path.normalize(`Plugins/${modName}/Saved/Cooked/${config.platformDirName[1]}/${config.projectName[1]}/AssetRegistry.bin`)) 142 | let dest = path.resolve(path.normalize(`${outputDir}/${config.projectName[1]}/Content/Paks/${modName}.bin`)) 143 | console.log('Copying ' + source + ' to ' + dest) 144 | all.push(copyFile(source, dest)) 145 | source = path.resolve(path.normalize(`Plugins/${modName}/Saved/StagedBuilds/${config.platformDirName[1]}/${config.projectName[1]}/Content/Paks/${config.projectName[1]}-${config.platformDirName[1]}.pak`)) 146 | dest = path.resolve(path.normalize(`${outputDir}/${config.projectName[1]}/Content/Paks/${modName}.pak`)) 147 | console.log('Copying ' + source + ' to ' + dest) 148 | all.push(copyFile(source, dest)) 149 | Promise.all(all).then(() => { 150 | buildNext() 151 | }, (err) => { 152 | console.error(err) 153 | process.exit(1) 154 | }) 155 | } 156 | buildNext() 157 | } 158 | 159 | // step 5 of the build sequence 160 | // cleans up backup files and closes the log file 161 | function runBuildStep5 () { 162 | // cleanup backup file 163 | try { fs.unlinkSync(projectBackup) } catch (e) { /* pass */ } 164 | if (logfh) { 165 | fs.closeSync(logfh) 166 | logfh = null 167 | } 168 | 169 | console.log('ue4build Complete. -> ' + outputDir) 170 | } 171 | 172 | // search through the .uproject json to enable/disable a plugin 173 | function setPluginEnabled (name, enabled) { 174 | if (!('Plugins' in project)) { 175 | project.Plugins = [] 176 | } 177 | for (let plug of project.Plugins) { 178 | if (plug.Name === name) { 179 | plug.Enabled = enabled 180 | return 181 | } 182 | } 183 | project.Plugins.push({ 184 | Name: name, 185 | Enabled: enabled 186 | }) 187 | } 188 | 189 | // if we don't have a config yet... scan the directory and build a default 190 | function generateConfig () { 191 | const MOD_TPL = { 192 | name: ['# Name of the Mod (DLC) plugin', ''], 193 | asMod: ["# if 'true' will be handled as a mod", false] 194 | } 195 | 196 | config = { 197 | projectName: ['# the project name', ''], 198 | projectFile: ['# the .uproject file to build', ''], 199 | buildConfig: ["# 'DebugGame', 'Development', or 'Shipping'", 'Development'], 200 | targetPlatform: ['# Unreal build target platform', 'Win64'], 201 | platformDirName: ['# The name unreal will give to StagedBuild platform directories', 'WindowsNoEditor'], 202 | buildLog: ['# where to output AutomationTool.exe messages', '.ue4build.log'], 203 | outputPath: ['# will copy the build tree to this destination', 'Releases/ModBuild'], 204 | uatCommand: ['# path to the RunUAT batch or shell script', '/path/to/RunUAT'], 205 | modPlugins: ['# list of plugins to treat as dlc mods', []] 206 | } 207 | 208 | config.outputPath[1] = path.resolve(path.normalize(config.outputPath[1])) 209 | 210 | // determine project name 211 | let projectList = fs.readdirSync('.').filter((item) => { 212 | if (fs.statSync(item).isFile() && path.extname(item) === '.uproject') { 213 | return true 214 | } 215 | }) 216 | if (projectList.length) { 217 | config.projectFile[1] = path.resolve(path.normalize(projectList[0])) 218 | } 219 | config.projectName[1] = path.parse(config.projectFile[1]).name 220 | 221 | // determine engine version 222 | let project = JSON.parse(fs.readFileSync(config.projectFile[1])) 223 | let engineVersion = project.EngineAssociation 224 | 225 | let disabledPlugins = {} 226 | if ('Plugins' in project) { 227 | for (let p of project.Plugins) { 228 | if (p.Enabled === false) { 229 | disabledPlugins[p.Name] = true 230 | } 231 | } 232 | } 233 | 234 | // determine target platform 235 | switch (process.platform) { 236 | case 'darwin': 237 | config.targetPlatform[1] = 'Darwin' 238 | config.platformDirName[1] = 'MacNoEditor' 239 | break 240 | default: 241 | config.targetPlatform[1] = 'Win64' 242 | config.platformDirName[1] = 'WindowsNoEditor' 243 | break 244 | } 245 | 246 | // determine uat path 247 | let uatSearch = [ 248 | path.resolve(path.normalize(`C:\\Program Files\\Epic Games\\UE_${engineVersion}\\Engine\\Build\\BatchFiles\\RunUAT.bat`)) 249 | ] 250 | for (let cmd of uatSearch) { 251 | if (fs.existsSync(cmd)) { 252 | config.uatCommand[1] = cmd 253 | break 254 | } 255 | } 256 | 257 | // scan for plugins 258 | let pluginList = fs.readdirSync(PLUGIN_DIR).filter((item) => { 259 | if (fs.statSync(path.join(PLUGIN_DIR, item)).isDirectory()) { 260 | return true 261 | } 262 | }) 263 | 264 | let modPlugins = config.modPlugins[1] 265 | for (let pluginName of pluginList) { 266 | let mod = JSON.parse(JSON.stringify(MOD_TPL)) 267 | mod.name[1] = pluginName 268 | mod.asMod[1] = !disabledPlugins[pluginName] 269 | modPlugins.push(mod) 270 | } 271 | 272 | // finish up 273 | fs.writeFileSync(CONFIG_FILENAME, JSON.stringify(config, null, ' ')) 274 | console.log('generated config, please double check that everything is correct:') 275 | console.log(JSON.stringify(config, null, ' ')) 276 | } 277 | 278 | // RunUAT parameters for doing a main project build 279 | function getMainBuildParams () { 280 | return [ 281 | 'BuildCookRun', 282 | '-project="' + config.projectFile[1] + '"', 283 | '-noP4', 284 | '-clientconfig=' + config.buildConfig[1], 285 | '-serverconfig=' + config.buildConfig[1], 286 | '-nocompile', 287 | '-nocompileeditor', 288 | '-installed', 289 | '-ue4exe=UE4Editor-Cmd.exe', 290 | '-utf8output', 291 | '-platform=' + config.targetPlatform[1], 292 | '-targetplatform=' + config.targetPlatform[1], 293 | '-build', 294 | '-cook', 295 | '-map=', 296 | '-pak', 297 | '-createreleaseversion=1.0', 298 | '-compressed', 299 | '-stage', 300 | '-package' 301 | ] 302 | } 303 | 304 | // RunUAT parameters for doing a mod(dlc) build 305 | function getModBuildParams (modName) { 306 | return [ 307 | 'BuildCookRun', 308 | '-project="' + config.projectFile[1] + '"', 309 | '-noP4', 310 | '-clientconfig=' + config.buildConfig[1], 311 | '-serverconfig=' + config.buildConfig[1], 312 | '-nocompile', 313 | '-nocompileeditor', 314 | '-installed', 315 | '-ue4exe=UE4Editor-Cmd.exe', 316 | '-utf8output', 317 | '-platform=' + config.targetPlatform[1], 318 | '-targetplatform=' + config.targetPlatform[1], 319 | '-build', 320 | '-cook', 321 | '-map=', 322 | '-pak', 323 | '-dlcname=' + modName, 324 | '-basedonreleaseversion=1.0', 325 | '-compressed', 326 | '-stage', 327 | '-package', 328 | '-DLCIncludeEngineContent' 329 | ] 330 | } 331 | 332 | // Execute the actual RunUAT process in a sub-shell-process 333 | function runUAT (args) { 334 | let result = '' 335 | return new Promise((resolve, reject) => { 336 | let cmd = '"' + config.uatCommand[1] + '"' 337 | console.log(cmd + ' ' + args.join(' ')) 338 | let proc = childProcess.spawn(cmd, args, { 339 | shell: true 340 | }) 341 | proc.stdout.on('data', (data) => { 342 | result += data.toString() 343 | log(data.toString()) 344 | process.stdout.write('.') 345 | }) 346 | proc.stderr.on('data', (data) => { 347 | process.stderr.write(data) 348 | }) 349 | proc.on('close', (code) => { 350 | process.stdout.write('\n') 351 | if (code === 0) { 352 | resolve(result) 353 | } else { 354 | let err = new Error() 355 | err.code = code 356 | err.result = result 357 | reject(err) 358 | } 359 | }) 360 | }) 361 | } 362 | 363 | // copy a single file 364 | function copyFile (source, target) { 365 | return new Promise((resolve, reject) => { 366 | var rd = fs.createReadStream(source) 367 | rd.on('error', rejectCleanup) 368 | var wr = fs.createWriteStream(target) 369 | wr.on('error', rejectCleanup) 370 | function rejectCleanup (err) { 371 | rd.destroy() 372 | wr.end() 373 | reject(err) 374 | } 375 | wr.on('finish', resolve) 376 | rd.pipe(wr) 377 | }) 378 | } 379 | 380 | // recursively copy a directory 381 | function copyDir (source, target) { 382 | return new Promise((resolve, reject) => { 383 | let all = [] 384 | fs.mkdirSync(target) 385 | let sub = fs.readdirSync(source) 386 | for (let file of sub) { 387 | let subtarget = path.join(target, file) 388 | file = path.join(source, file) 389 | let stat = fs.statSync(file) 390 | if (stat.isDirectory()) { 391 | all.push(copyDir(file, subtarget)) 392 | } else if (stat.isFile()) { 393 | all.push(copyFile(file, subtarget)) 394 | } 395 | } 396 | Promise.all(all).then(resolve, reject) 397 | }) 398 | } 399 | --------------------------------------------------------------------------------